diff --git a/tasmota/include/tasmota_configurations_ESP32.h b/tasmota/include/tasmota_configurations_ESP32.h
index dc827d7e1..6521ab0f1 100644
--- a/tasmota/include/tasmota_configurations_ESP32.h
+++ b/tasmota/include/tasmota_configurations_ESP32.h
@@ -629,6 +629,8 @@
#define USE_SPI // Hardware SPI using GPIO12(MISO), GPIO13(MOSI) and GPIO14(CLK) in addition to two user selectable GPIOs(CS and DC)
//#define USE_RC522 // Add support for MFRC522 13.56Mhz Rfid reader (+6k code)
+//#define USE_MCP2515 // Add support for can bus using MCP2515 (+7k code)
+//#define USE_CANSNIFFER // Add support for can bus sniffer using MCP2515 (+5k code)
#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
diff --git a/tasmota/include/tasmota_template.h b/tasmota/include/tasmota_template.h
index 0262e889e..14edc0fd0 100644
--- a/tasmota/include/tasmota_template.h
+++ b/tasmota/include/tasmota_template.h
@@ -553,7 +553,7 @@ const uint16_t kGpioNiceList[] PROGMEM = {
#ifdef USE_SDCARD
AGPIO(GPIO_SDCARD_CS), // SDCard in SPI mode
#endif // USE_SDCARD
-#ifdef USE_MCP2515
+#if defined(USE_MCP2515) || defined(USE_CANSNIFFER)
AGPIO(GPIO_MCP2515_CS),
#endif // USE_MCP2515
#endif // USE_SPI
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index fe52853f7..4c8a0e129 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -738,6 +738,8 @@
// #define USE_RC522 // Add support for MFRC522 13.56Mhz Rfid reader (+6k code)
// #define USE_RC522_DATA_FUNCTION // Add support for reading data block content (+0k4 code)
// #define USE_RC522_TYPE_INFORMATION // Add support for showing card type (+0k4 code)
+// #define USE_MCP2515 // Add support for can bus using MCP2515 (+7k code)
+// #define USE_CANSNIFFER // Add support for can bus sniffer using MCP2515 (+5k code)
#endif // USE_SPI
diff --git a/tasmota/tasmota_support/support_features.ino b/tasmota/tasmota_support/support_features.ino
index a1410007c..ca0cc27c9 100644
--- a/tasmota/tasmota_support/support_features.ino
+++ b/tasmota/tasmota_support/support_features.ino
@@ -829,9 +829,11 @@ void ResponseAppendFeatures(void)
feature9 |= 0x00000001; // xsns_98_sgp40.ino
#endif
#if defined(USE_I2C) && defined(USE_LUXV30B)
- feature9 |= 0x00000002;
+ feature9 |= 0x00000002; // xsns_99_luxv30b.ino
+#endif
+#if defined(USE_SPI) && defined(USE_CANSNIFFER)
+ feature9 |= 0x00000004; // xsns_87_can_sniffer.ino
#endif
-// feature9 |= 0x00000004;
// feature9 |= 0x00000008;
// feature9 |= 0x00000010;
diff --git a/tasmota/tasmota_xsns_sensor/xsns_87_can_sniffer.ino b/tasmota/tasmota_xsns_sensor/xsns_87_can_sniffer.ino
new file mode 100644
index 000000000..6d4f733be
--- /dev/null
+++ b/tasmota/tasmota_xsns_sensor/xsns_87_can_sniffer.ino
@@ -0,0 +1,222 @@
+/*
+ xsns_87_can_sniffer.ino - MCP2515 CAN bus support for Tasmota
+
+ Copyright (C) 2022 kwiatek6324 and Marius Bezuidenhout
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_SPI
+#ifdef USE_CANSNIFFER
+#ifdef USE_MCP2515
+#undef USE_MCP2515
+#warning **** USE_MCP2515 disabled in favour of USE_CANSNIFFER ****
+#endif
+/*********************************************************************************************\
+ * CAN sniffer using MCP2515 - Microchip CAN controller
+ *
+ * Connections:
+ * MCP2515 ESP8266 Tasmota
+ * ------- -------------- ----------
+ * INT not used
+ * SCK GPIO14 SPI CLK
+ * SI GPIO13 SPI MOSI
+ * SO GPIO12 SPI MISO
+ * CS GPIO0..5,15,16 MCP2515
+ * Gnd Gnd
+ * VCC Vin/5V
+\*********************************************************************************************/
+
+#define XSNS_87 87
+
+#ifndef MCP2515_BITRATE
+ #define MCP2515_BITRATE CAN_100KBPS
+#endif
+
+#ifndef MCP2515_CLOCK
+ #define MCP2515_CLOCK MCP_8MHZ
+#endif
+
+#ifndef MCP2515_MAX_FRAMES
+ #define MCP2515_MAX_FRAMES 8
+#endif
+
+#ifndef CAN_KEEP_ALIVE_SECS
+ #define CAN_KEEP_ALIVE_SECS 300
+#endif
+
+#ifndef MCP2515_TIMEOUT
+ #define MCP2515_TIMEOUT 10
+#endif
+
+#define D_PRFX_CAN "Can"
+#define D_CMND_CANSEND "Send"
+
+const char kCanCommands[] PROGMEM = D_PRFX_CAN "|" "|" D_CMND_CANSEND ;
+
+void (* const CanCommand[])(void) PROGMEM = { &CmndCan, &CmndCanSend};
+
+#include "mcp2515.h"
+
+struct MCP2515_Struct {
+ uint32_t lastFrameRecv = 0;
+ int8_t init_status = 0;
+} Mcp2515;
+
+struct can_frame canFrame;
+
+MCP2515 *mcp2515 = nullptr;
+
+/*********************************************************************************************\
+ * Commands
+\*********************************************************************************************/
+
+void CmndCan(void) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Can (%d) [%s][%s]"), XdrvMailbox.index, XdrvMailbox.topic, XdrvMailbox.data);
+ ResponseCmndChar_P(PSTR("OK"));
+}
+
+void CmndCanSend(void) {
+ JsonParser parser(XdrvMailbox.data);
+ JsonParserObject root = parser.getRootObject();
+
+ uint16_t id = root.getUInt(PSTR("ID"), 0); // case insensitive
+ uint16_t len = root.getUInt(PSTR("LEN"), 0); // case insensitive
+ JsonParserArray data = root[PSTR("DATA")];
+
+ struct can_frame canMsg;
+
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: CanSend (%d)->%d"), id,len);
+ canMsg.can_id =id;
+ canMsg.can_dlc=len;
+ for (uint8_t i=0;isendMessage(&canMsg);
+// delay(100);
+ ResponseCmndChar_P(PSTR("OK"));
+}
+
+char c2h(char c) {
+ return "0123456789ABCDEF"[0x0F & (unsigned char)c];
+}
+
+void MCP2515_FrameSizeError(uint8_t len, uint32_t id) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Unexpected length (%d) for ID 0x%x"), len, id);
+}
+
+void MCP2515_Init(void) {
+ if (PinUsed(GPIO_MCP2515_CS, GPIO_ANY) && TasmotaGlobal.spi_enabled) {
+ mcp2515 = new MCP2515(5);
+ if (MCP2515::ERROR_OK != mcp2515->reset()) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Failed to reset module"));
+ return;
+ }
+ if (MCP2515::ERROR_OK != mcp2515->setBitrate(MCP2515_BITRATE, MCP2515_CLOCK)) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Failed to set module bitrate"));
+ return;
+ }
+ if (MCP2515::ERROR_OK != mcp2515->setNormalMode()) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Failed to set normal mode"));
+ return;
+ }
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Sniffer Initialized"));
+ }
+}
+
+void MCP2515_Read() {
+ uint8_t nCounter = 0;
+ bool checkRcv;
+ char mqtt_data[128];
+
+ checkRcv = mcp2515->checkReceive();
+
+ while (checkRcv && nCounter <= MCP2515_MAX_FRAMES) {
+ mcp2515->checkReceive();
+ nCounter++;
+ if (mcp2515->readMessage(&canFrame) == MCP2515::ERROR_OK) {
+ Mcp2515.lastFrameRecv = TasmotaGlobal.uptime;
+
+ char canMsg[17];
+ canMsg[0] = 0;
+ for (int i = 0; i < canFrame.can_dlc; i++) {
+ canMsg[i*2] = c2h(canFrame.data[i]>>4);
+ canMsg[i*2+1] = c2h(canFrame.data[i]);
+ }
+
+ if (canFrame.can_dlc > 0) {
+ canMsg[(canFrame.can_dlc - 1) * 2 + 2] = 0;
+ }
+// AddLog(LOG_LEVEL_INFO, PSTR("CAN: Received message 0x%s from ID 0x%x"), canMsg, (uint32_t)canFrame.can_id);
+
+// AddLog(LOG_LEVEL_INFO, PSTR("CAN: Received: ID: %d"), (uint32_t)canFrame.can_id);
+// AddLog(LOG_LEVEL_INFO, PSTR("CAN: Received: LEN: %d"), (uint32_t)canFrame.can_dlc);
+// for (int i = 0; i < canFrame.can_dlc; i++) {
+// AddLog(LOG_LEVEL_INFO, PSTR("CAN: Received: DATA[%d]: %d"), i,canFrame.data[i]);
+// }
+ Response_P(PSTR("{\"%s\":%d,\"%s\":%d"),
+ "ID",(uint32_t)canFrame.can_id,
+ "LEN",(uint32_t)canFrame.can_dlc
+ );
+ for (int i = 0; i < canFrame.can_dlc; i++) { ResponseAppend_P(PSTR(",\"D%d\":%d"),i,canFrame.data[i]); }
+ ResponseJsonEnd();
+
+
+ MqttPublishPrefixTopic_P(STAT, "CAN");
+ ResponseClear();
+
+
+ } else if (mcp2515->checkError()) {
+ uint8_t errFlags = mcp2515->getErrorFlags();
+ mcp2515->clearRXnOVRFlags();
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Received error %d"), errFlags);
+ break;
+ }
+ }
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xsns87(uint8_t function) {
+ bool result = false;
+
+ if (FUNC_INIT == function) {
+ MCP2515_Init();
+ }
+ else if (Mcp2515.init_status) {
+ switch (function) {
+ case FUNC_EVERY_50_MSECOND:
+ MCP2515_Read();
+ break;
+ case FUNC_COMMAND:
+ result = DecodeCommand(kCanCommands, CanCommand);
+ break;
+ case FUNC_JSON_APPEND:
+// MCP2515_Show(1);
+ break;
+ #ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+// MCP2515_Show(0);
+ break;
+ #endif // USE_WEBSERVER
+ }
+ }
+ return result;
+}
+
+#endif // USE_CANSNIFFER
+#endif // USE_SPI
\ No newline at end of file
diff --git a/tasmota/tasmota_xsns_sensor/xsns_87_mcp2515.ino b/tasmota/tasmota_xsns_sensor/xsns_87_mcp2515.ino
index 629cb9209..ceb3cce07 100644
--- a/tasmota/tasmota_xsns_sensor/xsns_87_mcp2515.ino
+++ b/tasmota/tasmota_xsns_sensor/xsns_87_mcp2515.ino
@@ -19,6 +19,10 @@
#ifdef USE_SPI
#ifdef USE_MCP2515
+#ifdef USE_CANSNIFFER
+#undef USE_CANSNIFFER
+#warning **** USE_CANSNIFFER disabled in favour of USE_MCP2515 ****
+#endif
/*********************************************************************************************\
* MCP2515 - Microchip CAN controller
*
@@ -106,10 +110,13 @@ struct BMS_Struct {
#endif
-int8_t mcp2515_init_status = 1;
+struct MCP2515_Struct {
+ uint32_t lastFrameRecv = 0;
+ int8_t init_status = 0;
+} Mcp2515;
-uint32_t lastFrameRecv = 0;
struct can_frame canFrame;
+
MCP2515 *mcp2515 = nullptr;
char c2h(char c)
@@ -118,182 +125,182 @@ char c2h(char c)
}
void MCP2515_FrameSizeError(uint8_t len, uint32_t id) {
- AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Unexpected length (%d) for ID 0x%x"), len, id);
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CAN: Unexpected length (%d) for ID 0x%x"), len, id);
}
void MCP2515_Init(void) {
- mcp2515 = new MCP2515(5);
- if (MCP2515::ERROR_OK != mcp2515->reset()) {
- AddLog(LOG_LEVEL_ERROR, PSTR("MCP2515: Failed to reset module"));
- mcp2515_init_status = 0;
- }
+ if (PinUsed(GPIO_MCP2515_CS, GPIO_ANY) && TasmotaGlobal.spi_enabled) {
+ mcp2515 = new MCP2515(5);
+ if (MCP2515::ERROR_OK != mcp2515->reset()) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Failed to reset module"));
+ return;
+ }
+ if (MCP2515::ERROR_OK != mcp2515->setBitrate(MCP2515_BITRATE, MCP2515_CLOCK)) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Failed to set module bitrate"));
+ return;
+ }
+ if (MCP2515::ERROR_OK != mcp2515->setNormalMode()) {
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Failed to set normal mode"));
+ return;
+ }
+ AddLog(LOG_LEVEL_INFO, PSTR("CAN: Initialized"));
- if (MCP2515::ERROR_OK != mcp2515->setBitrate(MCP2515_BITRATE, MCP2515_CLOCK)) {
- AddLog(LOG_LEVEL_ERROR, PSTR("MCP2515: Failed to set module bitrate"));
- mcp2515_init_status = 0;
- }
-
- if (mcp2515_init_status && MCP2515::ERROR_OK != mcp2515->setNormalMode()) {
- AddLog(LOG_LEVEL_ERROR, PSTR("MCP2515: Failed to set normal mode"));
- mcp2515_init_status = 0;
- }
#ifdef MCP2515_BMS_FREEDWON
// TODO: Filter CAN bus messages
//mcp2515->setFilterMask();
//mcp2515->setFilter();
#endif
+ }
}
void MCP2515_Read() {
uint8_t nCounter = 0;
bool checkRcv;
- if (mcp2515_init_status) {
- checkRcv = mcp2515->checkReceive();
+ checkRcv = mcp2515->checkReceive();
- while (checkRcv && nCounter <= MCP2515_MAX_FRAMES) {
- mcp2515->checkReceive();
- nCounter++;
- if (mcp2515->readMessage(&canFrame) == MCP2515::ERROR_OK) {
- lastFrameRecv = TasmotaGlobal.uptime;
+ while (checkRcv && nCounter <= MCP2515_MAX_FRAMES) {
+ mcp2515->checkReceive();
+ nCounter++;
+ if (mcp2515->readMessage(&canFrame) == MCP2515::ERROR_OK) {
+ Mcp2515.lastFrameRecv = TasmotaGlobal.uptime;
#ifdef MCP2515_BMS_CLIENT
- #ifdef MCP2515_BMS_FREEDWON
- switch (canFrame.can_id) {
- // Charge/Discharge parameters
- case 0x351:
- if (8 == canFrame.can_dlc) {
- bms.chargeVoltLimit = (canFrame.data[1] << 8) | canFrame.data[0];
- bms.maxChargeCurrent = (canFrame.data[3] << 8) | canFrame.data[2];
- bms.maxDischargeCurrent = (canFrame.data[5] << 8) | canFrame.data[4];
- bms.dischargeVolt = (canFrame.data[7] << 8) | canFrame.data[6];
- bms.setFields |= BMS_CHARGE_VOLT_MAX | BMS_CHARGE_VOLT_MIN | BMS_CHARGE_AMP_MAX | BMS_DISCHARGE_AMP_MAX;
- } else {
- MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
- }
- break;
- // State of Charge/Health
- case 0x355:
- if (6 >= canFrame.can_dlc) {
- bms.stateOfCharge = (canFrame.data[1] << 8) | canFrame.data[0];
- bms.stateOfHealth = (canFrame.data[3] << 8) | canFrame.data[2];
- bms.setFields |= BMS_SOC | BMS_SOH;
- } else {
- MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
- }
- break;
- // Voltage/Current/Temperature
- case 0x356:
- if (6 >= canFrame.can_dlc) {
- bms.battVoltage = (canFrame.data[1] << 8) | canFrame.data[0];
- bms.battAmp = (canFrame.data[3] << 8) | canFrame.data[2];
- bms.battTemp = (canFrame.data[5] << 8) | canFrame.data[4]; // Convert to fahrenheit if SetOpion8 is set
- bms.setFields |= BMS_VOLT | BMS_AMP | BMS_TEMP;
- } else {
- MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
- }
- break;
- // Manufacturer name
- case 0x35E:
- for (int i = 0; i < canFrame.can_dlc; i++) {
- bms.manuf[i] = canFrame.data[i];
- }
- bms.setFields |= BMS_MANUFACTURER;
- bms.manuf[8] = 0; // Ensure that name is null terminated
- break;
- // Battery Model / Firmware version
- case 0x35F:
- if (4 == canFrame.can_dlc) {
- bms.model = (canFrame.data[1] << 8) | canFrame.data[0];
- bms.firmwareVer = (canFrame.data[3] << 8) | canFrame.data[2];
- bms.setFields |= BMS_MODEL | BMS_FIRMWARE_VER;
- } else {
- MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
- }
- break;
- // Battery / BMS name
- case 0x370:
- case 0x371:
- for (int i = 0; i < canFrame.can_dlc; i++) {
- uint8_t nameStrPos = i + ((canFrame.can_id & 0x1) * 8); // If can_id is 0x371 then fill from byte 8 onwards
- bms.name[nameStrPos] = canFrame.data[i];
- }
- if ((canFrame.can_id & 0x1) && (bms.name[0] > 0)) { // Upper and lower part of name has been set now
- bms.setFields |= BMS_NAME;
- }
- bms.name[16] = 0; // Ensure that name is null terminated
- break;
- // Modules status
- case 0x372:
- // Min/Max cell voltage/temperature
- case 0x373:
- // Min. cell voltage id string
- case 0x374:
- // Max. cell voltage id string
- case 0x375:
- // Min. cell temperature id string
- case 0x376:
- // Max. cell temperature id string
- case 0x377:
- break;
- // Installed capacity
- case 0x379:
- if (2 >= canFrame.can_dlc) {
- bms.capacityAh = (canFrame.data[1] << 8) | canFrame.data[0];
- bms.setFields |= BMS_CAPACITY;
- } else {
- MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
- }
- break;
- // Serial number
- case 0x380:
- case 0x381:
- for (int i = 0; i < canFrame.can_dlc; i++) {
- uint8_t serialNrStrPos = i + ((canFrame.can_id & 0x1) * 8); // If can_id is 0x381 then fill from byte 8 onwards
- bms.serialNr[serialNrStrPos] = canFrame.data[i];
- }
- if ((canFrame.can_id & 0x1) && (bms.serialNr[0] > 0)) { // Upper and lower part of serial number has been set now
- bms.setFields |= BMS_SERIAL;
- }
- bms.serialNr[16] = 0; // Ensure that serial nr is null terminated
- break;
- default:
- char canMsg[17];
- canMsg[0] = 0;
- for (int i = 0; i < canFrame.can_dlc; i++) {
- canMsg[i*2] = c2h(canFrame.data[i]>>4);
- canMsg[i*2+1] = c2h(canFrame.data[i]);
- }
- if (canFrame.can_dlc > 0) {
- canMsg[(canFrame.can_dlc - 1) * 2 + 2] = 0;
- }
- AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Received message 0x%s from ID 0x%x"), canMsg, (uint32_t)canFrame.can_id);
- break;
- }
- #endif // MCP2515_BMS_FREEDWON
-#endif // MCP2515_BMS_CLIENT
- } else if (mcp2515->checkError()) {
- uint8_t errFlags = mcp2515->getErrorFlags();
- mcp2515->clearRXnOVRFlags();
- AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Received error %d"), errFlags);
- break;
- }
+#ifdef MCP2515_BMS_FREEDWON
+ switch (canFrame.can_id) {
+ // Charge/Discharge parameters
+ case 0x351:
+ if (8 == canFrame.can_dlc) {
+ bms.chargeVoltLimit = (canFrame.data[1] << 8) | canFrame.data[0];
+ bms.maxChargeCurrent = (canFrame.data[3] << 8) | canFrame.data[2];
+ bms.maxDischargeCurrent = (canFrame.data[5] << 8) | canFrame.data[4];
+ bms.dischargeVolt = (canFrame.data[7] << 8) | canFrame.data[6];
+ bms.setFields |= BMS_CHARGE_VOLT_MAX | BMS_CHARGE_VOLT_MIN | BMS_CHARGE_AMP_MAX | BMS_DISCHARGE_AMP_MAX;
+ } else {
+ MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
+ }
+ break;
+ // State of Charge/Health
+ case 0x355:
+ if (6 >= canFrame.can_dlc) {
+ bms.stateOfCharge = (canFrame.data[1] << 8) | canFrame.data[0];
+ bms.stateOfHealth = (canFrame.data[3] << 8) | canFrame.data[2];
+ bms.setFields |= BMS_SOC | BMS_SOH;
+ } else {
+ MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
+ }
+ break;
+ // Voltage/Current/Temperature
+ case 0x356:
+ if (6 >= canFrame.can_dlc) {
+ bms.battVoltage = (canFrame.data[1] << 8) | canFrame.data[0];
+ bms.battAmp = (canFrame.data[3] << 8) | canFrame.data[2];
+ bms.battTemp = (canFrame.data[5] << 8) | canFrame.data[4]; // Convert to fahrenheit if SetOpion8 is set
+ bms.setFields |= BMS_VOLT | BMS_AMP | BMS_TEMP;
+ } else {
+ MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
+ }
+ break;
+ // Manufacturer name
+ case 0x35E:
+ for (int i = 0; i < canFrame.can_dlc; i++) {
+ bms.manuf[i] = canFrame.data[i];
+ }
+ bms.setFields |= BMS_MANUFACTURER;
+ bms.manuf[8] = 0; // Ensure that name is null terminated
+ break;
+ // Battery Model / Firmware version
+ case 0x35F:
+ if (4 == canFrame.can_dlc) {
+ bms.model = (canFrame.data[1] << 8) | canFrame.data[0];
+ bms.firmwareVer = (canFrame.data[3] << 8) | canFrame.data[2];
+ bms.setFields |= BMS_MODEL | BMS_FIRMWARE_VER;
+ } else {
+ MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
+ }
+ break;
+ // Battery / BMS name
+ case 0x370:
+ case 0x371:
+ for (int i = 0; i < canFrame.can_dlc; i++) {
+ uint8_t nameStrPos = i + ((canFrame.can_id & 0x1) * 8); // If can_id is 0x371 then fill from byte 8 onwards
+ bms.name[nameStrPos] = canFrame.data[i];
+ }
+ if ((canFrame.can_id & 0x1) && (bms.name[0] > 0)) { // Upper and lower part of name has been set now
+ bms.setFields |= BMS_NAME;
+ }
+ bms.name[16] = 0; // Ensure that name is null terminated
+ break;
+ // Modules status
+ case 0x372:
+ // Min/Max cell voltage/temperature
+ case 0x373:
+ // Min. cell voltage id string
+ case 0x374:
+ // Max. cell voltage id string
+ case 0x375:
+ // Min. cell temperature id string
+ case 0x376:
+ // Max. cell temperature id string
+ case 0x377:
+ break;
+ // Installed capacity
+ case 0x379:
+ if (2 >= canFrame.can_dlc) {
+ bms.capacityAh = (canFrame.data[1] << 8) | canFrame.data[0];
+ bms.setFields |= BMS_CAPACITY;
+ } else {
+ MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
+ }
+ break;
+ // Serial number
+ case 0x380:
+ case 0x381:
+ for (int i = 0; i < canFrame.can_dlc; i++) {
+ uint8_t serialNrStrPos = i + ((canFrame.can_id & 0x1) * 8); // If can_id is 0x381 then fill from byte 8 onwards
+ bms.serialNr[serialNrStrPos] = canFrame.data[i];
+ }
+ if ((canFrame.can_id & 0x1) && (bms.serialNr[0] > 0)) { // Upper and lower part of serial number has been set now
+ bms.setFields |= BMS_SERIAL;
+ }
+ bms.serialNr[16] = 0; // Ensure that serial nr is null terminated
+ break;
+ default:
+ char canMsg[17];
+ canMsg[0] = 0;
+ for (int i = 0; i < canFrame.can_dlc; i++) {
+ canMsg[i*2] = c2h(canFrame.data[i]>>4);
+ canMsg[i*2+1] = c2h(canFrame.data[i]);
+ }
+ if (canFrame.can_dlc > 0) {
+ canMsg[(canFrame.can_dlc - 1) * 2 + 2] = 0;
+ }
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CAN: Received message 0x%s from ID 0x%x"), canMsg, (uint32_t)canFrame.can_id);
+ break;
}
+#endif // MCP2515_BMS_FREEDWON
+#endif // MCP2515_BMS_CLIENT
+ } else if (mcp2515->checkError()) {
+ uint8_t errFlags = mcp2515->getErrorFlags();
+ mcp2515->clearRXnOVRFlags();
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CAN: Received error %d"), errFlags);
+ break;
+ }
+ }
#ifdef MCP2515_BMS_FREEDWON
- if (!(TasmotaGlobal.uptime%CAN_KEEP_ALIVE_SECS) && TasmotaGlobal.uptime>60) {
- canFrame.can_id = 0x305;
- canFrame.can_dlc = 0;
- if (MCP2515::ERROR_OK != mcp2515->sendMessage(&canFrame)) {
- AddLog(LOG_LEVEL_ERROR, PSTR("MCP2515: Failed to send keep alive frame"));
- }
+ if (!(TasmotaGlobal.uptime%CAN_KEEP_ALIVE_SECS) && TasmotaGlobal.uptime>60) {
+ canFrame.can_id = 0x305;
+ canFrame.can_dlc = 0;
+ if (MCP2515::ERROR_OK != mcp2515->sendMessage(&canFrame)) {
+ AddLog(LOG_LEVEL_ERROR, PSTR("CAN: Failed to send keep alive frame"));
}
-#endif
}
+#endif
}
void MCP2515_Show(bool Json) {
if (Json) {
- if (lastFrameRecv > 0 && TasmotaGlobal.uptime - lastFrameRecv <= MCP2515_TIMEOUT) {
+ if (Mcp2515.lastFrameRecv > 0 && TasmotaGlobal.uptime - Mcp2515.lastFrameRecv <= MCP2515_TIMEOUT) {
#ifdef MCP2515_BMS_CLIENT
if (bms.setFields & BMS_MANUFACTURER) {
bool jsonFirstField = true;
@@ -404,11 +411,11 @@ bool Xsns87(uint8_t function)
{
bool result = false;
- if (PinUsed(GPIO_MCP2515_CS, GPIO_ANY) && TasmotaGlobal.spi_enabled) {
+ if (FUNC_INIT == function) {
+ MCP2515_Init();
+ }
+ else if (Mcp2515.init_status) {
switch (function) {
- case FUNC_INIT:
- MCP2515_Init();
- break;
case FUNC_EVERY_50_MSECOND:
MCP2515_Read();
break;
diff --git a/tools/decode-status.py b/tools/decode-status.py
index 8b22a40cb..2d2ce4fa0 100755
--- a/tools/decode-status.py
+++ b/tools/decode-status.py
@@ -286,7 +286,7 @@ a_features = [[
"USE_PCF85363","USE_DS3502","USE_IMPROV","USE_FLOWRATEMETER",
"USE_BP5758D","USE_HYT","USE_SM2335","USE_DISPLAY_TM1621_SONOFF"
],[
- "USE_SGP40","USE_LUXV30B","","",
+ "USE_SGP40","USE_LUXV30B","USE_CANSNIFFER","",
"","","","",
"","","","",
"","","","",