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"
#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 {
uint16_t setFields; // Bitwise fields set list
char name[17];
uint16_t stateOfCharge;
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
int16_t battAmp; // 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;
#endif
int8_t mcp2515_init_status = 1;
@ -89,6 +117,10 @@ char c2h(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) {
mcp2515 = new MCP2515(5);
if (MCP2515::ERROR_OK != mcp2515->reset()) {
@ -100,7 +132,7 @@ void MCP2515_Init(void) {
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;
@ -129,38 +161,65 @@ void MCP2515_Read() {
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_CHARGE_AMP_MIN;
} 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 {
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Unexpected length (%d) for ID 0x355"), canFrame.can_dlc);
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.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 {
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Unexpected length (%d) for ID 0x356"), canFrame.can_dlc);
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++) {
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
@ -175,23 +234,39 @@ void MCP2515_Read() {
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++) {
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);
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
@ -218,24 +293,66 @@ void MCP2515_Read() {
void MCP2515_Show(bool Json) {
if (Json) {
if (lastFrameRecv > 0 && TasmotaGlobal.uptime - lastFrameRecv <= MCP2515_TIMEOUT ) {
if (lastFrameRecv > 0 && TasmotaGlobal.uptime - lastFrameRecv <= MCP2515_TIMEOUT) {
#ifdef MCP2515_BMS_CLIENT
ResponseAppend_P(PSTR(",\"MCP2515\":{\"SOC\":%d,\"SOH\":%d}"), \
bms.stateOfCharge,
bms.stateOfHealth
);
if (bms.setFields & BMS_MANUFACTURER) {
bool jsonFirstField = true;
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
}
#ifdef USE_WEBSERVER
} else {
#ifdef MCP2515_BMS_CLIENT
char ampStr[6];
dtostrf((float(bms.battAmp) / 10), 5, 1, ampStr);
WSContentSend_PD(HTTP_SNS_SOC, bms.name, bms.stateOfCharge);
WSContentSend_PD(HTTP_SNS_SOH, bms.name, bms.stateOfHealth);
WSContentSend_Voltage(bms.name, (float(bms.battVoltage) / 100));
WSContentSend_PD(HTTP_SNS_CURRENT, ampStr);
WSContentSend_Temp(bms.name, ConvertTemp(float(bms.battTemp) / 10));
if (bms.setFields & BMS_MANUFACTURER) {
char ampStr[6];
dtostrf((float(bms.battAmp) / 10), 5, 1, ampStr);
if (bms.setFields & BMS_SOC) {
WSContentSend_PD(HTTP_SNS_SOC, bms.manuf, bms.stateOfCharge);
}
if (bms.setFields & BMS_SOH) {
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 // USE_WEBSERVER
}