Merge pull request #12889 from mbezuidenhout/feature/can-bus

Feature/can bus
This commit is contained in:
Theo Arends 2021-08-15 14:58:12 +02:00 committed by GitHub
commit 035fab505e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 138 additions and 21 deletions

View File

@ -68,14 +68,42 @@
#include "mcp2515.h" #include "mcp2515.h"
#ifdef MCP2515_BMS_CLIENT #ifdef MCP2515_BMS_CLIENT
#define BMS_NAME 0x1
#define BMS_SOC 0x2
#define BMS_SOH 0x4
#define BMS_CHARGE_VOLT_MAX 0x8
#define BMS_CHARGE_VOLT_MIN 0x10
#define BMS_CHARGE_AMP_MAX 0x20
#define BMS_CHARGE_AMP_MIN 0x40
#define BMS_VOLT 0x80
#define BMS_AMP 0x100
#define BMS_TEMP 0x200
#define BMS_CAPACITY 0x400
#define BMS_MODEL 0x800
#define BMS_FIRMWARE_VER 0x1000
#define BMS_MANUFACTURER 0x2000
#define BMS_SERIAL 0x4000
struct BMS_Struct { struct BMS_Struct {
uint16_t setFields; // Bitwise fields set list
char name[17];
uint16_t stateOfCharge; uint16_t stateOfCharge;
uint16_t stateOfHealth; uint16_t stateOfHealth;
uint16_t chargeVoltLimit; // Div 10
uint16_t dischargeVolt; // Div 10
int16_t maxChargeCurrent; // Div 10
int16_t maxDischargeCurrent; // Div 10
uint16_t battVoltage; // Div 100 uint16_t battVoltage; // Div 100
int16_t battAmp; // Div 10 int16_t battAmp; // Div 10
int16_t battTemp; // Div 10 int16_t battTemp; // Div 10
char name[17]; uint16_t capacityAh;
uint16_t model;
uint16_t firmwareVer;
char manuf[9];
char serialNr[17];
} bms; } bms;
#endif #endif
int8_t mcp2515_init_status = 1; int8_t mcp2515_init_status = 1;
@ -89,6 +117,10 @@ char c2h(char c)
return "0123456789ABCDEF"[0x0F & (unsigned char)c]; return "0123456789ABCDEF"[0x0F & (unsigned 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);
}
void MCP2515_Init(void) { void MCP2515_Init(void) {
mcp2515 = new MCP2515(5); mcp2515 = new MCP2515(5);
if (MCP2515::ERROR_OK != mcp2515->reset()) { if (MCP2515::ERROR_OK != mcp2515->reset()) {
@ -129,14 +161,24 @@ void MCP2515_Read() {
switch (canFrame.can_id) { switch (canFrame.can_id) {
// Charge/Discharge parameters // Charge/Discharge parameters
case 0x351: 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_CHARGE_AMP_MIN;
} else {
MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
}
break; break;
// State of Charge/Health // State of Charge/Health
case 0x355: case 0x355:
if (6 >= canFrame.can_dlc) { if (6 >= canFrame.can_dlc) {
bms.stateOfCharge = (canFrame.data[1] << 8) | canFrame.data[0]; bms.stateOfCharge = (canFrame.data[1] << 8) | canFrame.data[0];
bms.stateOfHealth = (canFrame.data[3] << 8) | canFrame.data[2]; bms.stateOfHealth = (canFrame.data[3] << 8) | canFrame.data[2];
bms.setFields |= BMS_SOC | BMS_SOH;
} else { } else {
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Unexpected length (%d) for ID 0x355"), canFrame.can_dlc); MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
} }
break; break;
// Voltage/Current/Temperature // Voltage/Current/Temperature
@ -145,22 +187,39 @@ void MCP2515_Read() {
bms.battVoltage = (canFrame.data[1] << 8) | canFrame.data[0]; bms.battVoltage = (canFrame.data[1] << 8) | canFrame.data[0];
bms.battAmp = (canFrame.data[3] << 8) | canFrame.data[2]; 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.battTemp = (canFrame.data[5] << 8) | canFrame.data[4]; // Convert to fahrenheit if SetOpion8 is set
bms.setFields |= BMS_VOLT | BMS_AMP | BMS_TEMP;
} else { } else {
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Unexpected length (%d) for ID 0x356"), canFrame.can_dlc); MCP2515_FrameSizeError(canFrame.can_dlc, canFrame.can_id);
} }
break; break;
// Manufacturer name // Manufacturer name
case 0x35E: 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 // Battery Model / Firmware version
case 0x35F: 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; break;
// Battery / BMS name // Battery / BMS name
case 0x370: case 0x370:
case 0x371: case 0x371:
for(int i = 0; i < canFrame.can_dlc; i++) { 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 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]; 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 bms.name[16] = 0; // Ensure that name is null terminated
break; break;
// Modules status // Modules status
@ -175,23 +234,39 @@ void MCP2515_Read() {
case 0x376: case 0x376:
// Max. cell temperature id string // Max. cell temperature id string
case 0x377: case 0x377:
break;
// Installed capacity // Installed capacity
case 0x379: 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 // Serial number
case 0x380: case 0x380:
case 0x381: 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; break;
default: default:
char canMsg[17]; char canMsg[17];
canMsg[0] = 0; canMsg[0] = 0;
for(int i = 0; i < canFrame.can_dlc; i++) { for (int i = 0; i < canFrame.can_dlc; i++) {
canMsg[i*2] = c2h(canFrame.data[i]>>4); canMsg[i*2] = c2h(canFrame.data[i]>>4);
canMsg[i*2+1] = c2h(canFrame.data[i]); canMsg[i*2+1] = c2h(canFrame.data[i]);
} }
if (canFrame.can_dlc > 0) { if (canFrame.can_dlc > 0) {
canMsg[(canFrame.can_dlc - 1) * 2 + 2] = 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); AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Received message 0x%s from ID 0x%x"), canMsg, (uint32_t)canFrame.can_id);
break; break;
} }
#endif // MCP2515_BMS_FREEDWON #endif // MCP2515_BMS_FREEDWON
@ -218,24 +293,66 @@ void MCP2515_Read() {
void MCP2515_Show(bool Json) { void MCP2515_Show(bool Json) {
if (Json) { if (Json) {
if (lastFrameRecv > 0 && TasmotaGlobal.uptime - lastFrameRecv <= MCP2515_TIMEOUT ) { if (lastFrameRecv > 0 && TasmotaGlobal.uptime - lastFrameRecv <= MCP2515_TIMEOUT) {
#ifdef MCP2515_BMS_CLIENT #ifdef MCP2515_BMS_CLIENT
ResponseAppend_P(PSTR(",\"MCP2515\":{\"SOC\":%d,\"SOH\":%d}"), \ if (bms.setFields & BMS_MANUFACTURER) {
bms.stateOfCharge, bool jsonFirstField = true;
bms.stateOfHealth ResponseAppend_P(PSTR(",\"%s\":{"), bms.manuf);
);
if (bms.setFields & BMS_SOC) {
ResponseAppend_P(PSTR("\"SOC\":%d"), bms.stateOfCharge);
jsonFirstField = false;
}
if (bms.setFields & BMS_SOH) {
ResponseAppend_P(PSTR("%s\"SOH\":%d"), jsonFirstField ? PSTR("") : PSTR(","), bms.stateOfHealth);
jsonFirstField = false;
}
if (bms.setFields & BMS_VOLT) {
ResponseAppend_P(PSTR("%s\"BattVolt\":%d.%d"), jsonFirstField ? PSTR("") : PSTR(","), bms.battVoltage / 100, bms.battVoltage % 100);
jsonFirstField = false;
}
if (bms.setFields & BMS_AMP) {
int8_t bmsModNeg = 1;
if (bms.battAmp < 0) {
bmsModNeg = -1;
}
ResponseAppend_P(PSTR("%s\"BattAmp\":%d.%d"), jsonFirstField ? PSTR("") : PSTR(","), bms.battAmp / 10, (bms.battAmp % 10) * bmsModNeg);
jsonFirstField = false;
}
if (bms.setFields & BMS_TEMP) {
ResponseAppend_P(PSTR("%s\"BattTemp\":%d.%d"), jsonFirstField ? PSTR("") : PSTR(","), bms.battTemp / 10, bms.battTemp % 10);
jsonFirstField = false;
}
ResponseAppend_P(PSTR("}"));
}
} else {
bms.setFields = 0;
#endif // MCP2515_BMS_CLIENT #endif // MCP2515_BMS_CLIENT
} }
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
} else { } else {
#ifdef MCP2515_BMS_CLIENT #ifdef MCP2515_BMS_CLIENT
if (bms.setFields & BMS_MANUFACTURER) {
char ampStr[6]; char ampStr[6];
dtostrf((float(bms.battAmp) / 10), 5, 1, ampStr); dtostrf((float(bms.battAmp) / 10), 5, 1, ampStr);
WSContentSend_PD(HTTP_SNS_SOC, bms.name, bms.stateOfCharge); if (bms.setFields & BMS_SOC) {
WSContentSend_PD(HTTP_SNS_SOH, bms.name, bms.stateOfHealth); WSContentSend_PD(HTTP_SNS_SOC, bms.manuf, bms.stateOfCharge);
WSContentSend_Voltage(bms.name, (float(bms.battVoltage) / 100)); }
WSContentSend_PD(HTTP_SNS_CURRENT, ampStr); if (bms.setFields & BMS_SOH) {
WSContentSend_Temp(bms.name, ConvertTemp(float(bms.battTemp) / 10)); WSContentSend_PD(HTTP_SNS_SOH, bms.manuf, bms.stateOfHealth);
}
if (bms.setFields & BMS_VOLT) {
WSContentSend_Voltage(bms.manuf, (float(bms.battVoltage) / 100));
}
if (bms.setFields & BMS_AMP) {
WSContentSend_PD(PSTR("{s}%s " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"), bms.manuf, ampStr);
}
if (bms.setFields & BMS_TEMP) {
WSContentSend_Temp(bms.manuf, ConvertTemp(float(bms.battTemp) / 10));
}
} else {
WSContentSend_PD(PSTR("{s}MCP2515 {m} Waiting for data{e}"));
}
#endif // MCP2515_BMS_CLIENT #endif // MCP2515_BMS_CLIENT
#endif // USE_WEBSERVER #endif // USE_WEBSERVER
} }