From 500a6ab6e28f7c3d6c00ada6e589796f6bbb50d2 Mon Sep 17 00:00:00 2001 From: Marius Bezuidenhout Date: Mon, 19 Jul 2021 17:52:42 +0200 Subject: [PATCH] Only respond when required data has been received --- tasmota/xsns_87_mcp2515.ino | 166 +++++++++++++++++++++++++++++++----- 1 file changed, 145 insertions(+), 21 deletions(-) diff --git a/tasmota/xsns_87_mcp2515.ino b/tasmota/xsns_87_mcp2515.ino index c8bd14a29..09dfdd10a 100644 --- a/tasmota/xsns_87_mcp2515.ino +++ b/tasmota/xsns_87_mcp2515.ino @@ -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,73 @@ 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)); + } + if (bms.setFields & BMS_SERIAL) { + WSContentSend_PD(PSTR("{s}%s Serial nr.{m}%s{e}"), bms.manuf, bms.serialNr); + } + if (bms.setFields & BMS_MODEL) { + WSContentSend_PD(PSTR("{s}%s Model{m}%d{e}"), bms.manuf, bms.model); + } + if (bms.setFields & BMS_FIRMWARE_VER) { + WSContentSend_PD(PSTR("{s}%s Firmware ver.{m}%d{e}"), bms.manuf, bms.firmwareVer); + } + } #endif // MCP2515_BMS_CLIENT #endif // USE_WEBSERVER }