Auxiliary heater controller via CAN-bus

Hello from chilly Finland!

Im planning new project again. The idea is to build remote control (phone app) for fuel-burning auxiliary heater for my Volvo V50 -08. The thing is the heater is only controllable through dash menus, (on/off and two timers) via can bus. So there's no simple "apply voltage to wireX solution.

As i understand first thing i need to do is build a "Can sniffer" to figure out the correct CAN message to turn the heater on. That's done for example in blog here, but it's a bit different platform so the message might be be different in my car:

I guess the most feasible way to get phone app control is to use free app from webasto that's intended to be used with their own gsm control Thermo call TC4. It just send's SMS commands to desired phone number for control. All the commands can be found from the manual: https://pbautoelectrics.co.uk/wp-content/uploads/2017/11/car-technical-documentation-thermo-call-4.pdf

And the app looks like this:

When i first get the CAN-side somewhat working adding a GSM-shield on input side shouldn't be too complicated :slight_smile:

So firstly suggestions for hardware side that might fit/play together nicely? Ready made examples to get the can side sniffing started? Or other suggestion for control side?

Coding a phone app myself is obviously out of my scope so it has to be some sort of ready made and fairly polished solution.
And as someone will ask anyways i have couple years of experience playing with arduinos. Im not good at coding but with examples i can stitch things working together. 15 years of experience with automotive electronics by my profession and degree, but not so much with CAN-buss stuff. :slight_smile:

Is your goal to create a copy of a ThermoCall hardware device instead of buying one?

Does your car insurance policy cover unapproved modifications to factory hardware?

mikb55:
Is your goal to create a copy of a ThermoCall hardware device instead of buying one?

Does your car insurance policy cover unapproved modifications to factory hardware?

Not really. My goal is to create remote start for my heater.. If ThermoCall worked in this case i would have bought one..
However it will naturally become very similar like are couple of other similar products on market

And no, i don't think my insurance covers damage caused my unapproved modifications and i'm not asking it do so.. I don't think the case is any different than with elm327 adapter hanging on obd socket already or my arduino project lighting a fire in house for some odd reason.

That's all on grey area for sure. Just like changing your tire and being unable to secure the nuts properly and loosing the wheel on the road. Or connecting russian made commercial product to your vehicle CAN-bus thus granting them access to potetially any component of your car over the internet. Just saying ::slight_smile:

Well, since the safety seems to be greatest issue here lets just say i'm going to power all this through NC-relay controlled by 15 pole from "ignition lock" so no driving and CAN-messaging simultaneously. It's not needed for the project anyways..

Still thinking hardware wise if it's possible to use esp8266 as i have some wemos d1 lying around? And this CAN-module https://www.partco.fi/fi/elektroniikan-komponentit/aktiivit/mikropiirit-listana/ic-mc/19223-mcp2515-modul.html as it's easiest and fastest to get here.
It's suggested by the seller that module should work with 3.3V logic if modded to provide 3.3V to MCP2515 and 5V to TJA1050

Also wondering if the board is better to use AVR architecture since i ran across few libraries that wont compile for esp8266? but as i don't know which of those libraries actually work at all i can't know what i will be using..

And if the CAN-protocols matters at this point supposedly it is ISO 15765-4 29-bit ID
The transfer speed on the HS CAN is 500 Kbps. LS CAN is 125 Kbps.
I have tested that with ELM327 + android terminal app but it just fills the buffer way too fast to capture enough traffic to judge whats happening.

Auxiliary heater (CPM) is connected directly to LS CAN so communication with it should be relatively straight forward

The Central Electronic Module (CEM). is connected to the Data Link Connector (a.k.a OBDII-connector). This connection is continuously linked to CAN H and CAN L on both high and low speed busses even when diagnostic software is not accessing the network.

Allright, since the lack of better suggestions i bought two of these:

https://www.partco.fi/fi/elektroniikan-komponentit/aktiivit/mikropiirit-listana/ic-mc/19223-mcp2515-modul.html

And modified those like this:

Connected to Wemos D1 board:

SPI connections like suggested here:
https://wiki.wemos.cc/products:d1:d1_mini

CS currently in D8, INT = D4
VCC is connected to 3.3V pin on Wemos and my added 5 volt feed for TJA1050 is coupled to 5V on Wemos

Used this library:

Despite all the effort only the loop back examples are working. "Message succsfully sent" and serial shows the data it receives.

Receive or send examples however are not working. No data is sent either received. Not in vehicle or when two of my modules are connected together.

The loopback code that works:

/* CAN Loopback Example
 * This example sends a message once a second and receives that message
 *   no CAN bus is required.  This example will test the functionality 
 *   of the protocol controller, and connections to it.
 *   
 *   Written By: Cory J. Fowler - October 5th 2016
 */

#include <mcp_can.h>
#include <SPI.h>

// CAN TX Variables
unsigned long prevTX = 0;                                        // Variable to store last execution time
const unsigned int invlTX = 1000;                                // One second interval constant
byte data[] = {0xAA, 0x55, 0x01, 0x10, 0xFF, 0x12, 0x34, 0x56};  // Generic CAN data to send

// CAN RX Variables
long unsigned int rxId;
unsigned char len;
unsigned char rxBuf[8];

// Serial Output String Buffer
char msgString[128];

// CAN0 INT and CS
#define CAN0_INT D4                              // Set INT to pin D4
MCP_CAN CAN0(D8);                               // Set CS to pin D8


void setup()
{
  Serial.begin(115200);  // CAN is running at 500,000BPS; 115,200BPS is SLOW, not FAST, thus 9600 is crippling.
  
  // Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
  if(CAN0.begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) == CAN_OK)
    Serial.println("MCP2515 Initialized Successfully!");
  else
    Serial.println("Error Initializing MCP2515...");
  
  // Since we do not set NORMAL mode, we are in loopback mode by default.
  //CAN0.setMode(MCP_NORMAL);

  pinMode(CAN0_INT, INPUT);                           // Configuring pin for /INT input
  
  Serial.println("MCP2515 Library Loopback Example...");
}

void loop()
{
  if(!digitalRead(CAN0_INT))                          // If CAN0_INT pin is low, read receive buffer
  {
    CAN0.readMsgBuf(&rxId, &len, rxBuf);              // Read data: len = data length, buf = data byte(s)
    
    if((rxId & 0x80000000) == 0x80000000)             // Determine if ID is standard (11 bits) or extended (29 bits)
      sprintf(msgString, "Extended ID: 0x%.8lX  DLC: %1d  Data:", (rxId & 0x1FFFFFFF), len);
    else
      sprintf(msgString, "Standard ID: 0x%.3lX       DLC: %1d  Data:", rxId, len);
  
    Serial.print(msgString);
  
    if((rxId & 0x40000000) == 0x40000000){            // Determine if message is a remote request frame.
      sprintf(msgString, " REMOTE REQUEST FRAME");
      Serial.print(msgString);
    } else {
      for(byte i = 0; i<len; i++){
        sprintf(msgString, " 0x%.2X", rxBuf[i]);
        Serial.print(msgString);
      }
    }
        
    Serial.println();
  }
  
  if(millis() - prevTX >= invlTX){                    // Send this at a one second interval. 
    prevTX = millis();
    byte sndStat = CAN0.sendMsgBuf(0x100, 8, data);
    
    if(sndStat == CAN_OK)
      Serial.println("Message Sent Successfully!");
    else
      Serial.println("Error Sending Message...");

  }
}

/*********************************************************************************************************
  END FILE
*********************************************************************************************************/

Any thoughts what i'm doing wrong?

You are putting 5v into RXCAN.
See 5v mcp2551 to 3.3v mcp2515 | Microchip

If you haven't damaged the MCP2515 you could try a resistive voltage divider (check the signal integrity with an oscilloscope).

If you have damaged the MCP2515 then start with a new board and instead of modifying it you could run it at 5v and put a high speed level shifter on the SPI interface to the micro.

Damn, thats what i afraided actually.. But since there is lots of examples doing it like that i thought there is something i don't understand and it just works :o

Well i have have one unmodified can-module and level shifter. The level shifter is just 4 channel so i need to use voltage divider in one pin anyway. Ill try that, lets see if it helps

mikb55:
If you have damaged the MCP2515 then start with a new board and instead of modifying it you could run it at 5v and put a high speed level shifter on the SPI interface to the micro.

I tried this, connected 4-channel logic level shifter between Wemos and CAN-module. Didn't bother with INT pin at this point cause when we are sending it's not needed. However proper level shifting didn't make it work. Still when i'm using loopback mode serial shows:

"Setting Baudrate Successful!
MCP2515 Initialized Successfully!
MCP2515 Library Loopback Example...
Message Sent Successfully!"

But as soon as i switch to normal mode or try CAN_Send example it prints: "Error Sending Message..."
So exactly same result as before. Same products but unused and brand new, so i haven't surely managed to burn them yet :slight_smile:

Is your transmitter connected to anything? Do the baud rates match?
You need 2 active device on a CAN bus to do a transmit test as the sender will only report success when it gets an ACK from the receiving device.

Actually yes, it's working now. It wasn't connected to anything and right after my last post i realized there was actually voltage on the bus now when i was trying to send.

Now i'm using my modified board for receiving and level shifter version for sending and it works like a charm.

Im not actually sure what was the problem when i was first trying the modified CAN-module. Maybe the fact i was using D8 as CS pin and it has pull down resistor? At some point today i started using D4 for that and D0 as INT on receiving side.

So do you think the modified board is really doomed to destruction at some point even if it's working well now?

Aand now i tested it in vehicle too and i can confirm the 29 bit id Lowspeed-CAN messages are flowing 125 kbps out from OBD socket :slight_smile:

Soo, all the examples in coryjfowlers MCP_CAN_library are working just fine.

However, i have now tried to get GitHub - autowp/arduino-canhacker: CanHacker (lawicel) CAN adapter on Arduino + MCP2515 library to work to use the Canhacker program. Identifying the bus trafic should be much easier with that kind of tools.

I have managed to make it kind of work. After adapting the library for 8 mHz crystal on my MCP-board i got the connection working on bench. When sending a message from another MCP-board Canhacker reads just fine something like 400 messages / sec.

The problem arises when i connect it to my car. It reads fine about one second and then stops. Disconnect --> Connect and it always reads like a second and 30-40 messages.. Anyone else had similar problems with this? Or any ideas what's the problem this time?

And by adapting the library i mean i just changed in ''CanHacker.h'' CAN_CLOCK canClock = MCP_16MHZ to CAN_CLOCK canClock = MCP_8MHZ.

There's the main sketch, not much stuff in it as all the magic is in libraries and pc/android program:

#include <can.h>
#include <mcp2515.h>

#include <CanHacker.h>
#include <CanHackerLineReader.h>
#include <lib.h>

#include <SPI.h>

const int SPI_CS_PIN = D4;
const int INT_PIN = D0;


CanHackerLineReader *lineReader = NULL;
CanHacker *canHacker = NULL;

void setup() {
    Serial.begin(115200);
    SPI.begin();

    canHacker = new CanHacker(&Serial, NULL, SPI_CS_PIN);
    //canHacker->enableLoopback();            // Enable Loopback mode for offline tests
    lineReader = new CanHackerLineReader(canHacker);
    
    pinMode(INT_PIN, INPUT);
}

void loop() {
    if (digitalRead(INT_PIN) == LOW) {
        canHacker->processInterrupt();
    }
    if (Serial.available()) {
        lineReader->process();
    }
}

Ohh.. No expertise on this field on here or no one has an idea what i'm talking about? :confused:

Using a fast micro such as the ESP8266 may be causing problems.

There's a piece of commented code in the serial port output code that looks suspicious, and code underneath looks like it's guaranteed to corrupt the output data. The GUI app probably gets confused by the corrupt data.

CanHacker::ERROR CanHacker::writeStream(const char *buffer) {
    /*if (_stream->availableForWrite() < strlen(buffer)) {
        return ERROR_SERIAL_TX_OVERRUN;
    }*/
    size_t printed = _stream->print(buffer);
    if (printed != strlen(buffer)) {
        return ERROR_SERIAL_TX_OVERRUN;
    }
    return ERROR_OK;
}

To confirm, toggle a pin (or pins) to indicate the return value and check with an oscilloscope or logic analyser.

As a workaround you could limit the rate at which packets get processed effectively emulating the behaviour of a slow micro.