mirror of https://github.com/arendst/Tasmota.git
Merge pull request #12889 from mbezuidenhout/feature/can-bus
Feature/can bus
This commit is contained in:
commit
035fab505e
|
@ -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()) {
|
||||
|
@ -129,14 +161,24 @@ 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
|
||||
|
@ -145,22 +187,39 @@ void MCP2515_Read() {
|
|||
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 {
|
||||
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
|
||||
if (bms.setFields & BMS_MANUFACTURER) {
|
||||
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_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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue