From dd3d48921159c6e83f43ff7fa75b149d4d657a1d Mon Sep 17 00:00:00 2001 From: ruza87 <129524347+ruza87@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:11:56 +0200 Subject: [PATCH 01/39] [MAINTENANCE] Moved HAL class method implementations to cpp file, so the header can be included easily --- ESPController/include/HAL_ESP32.h | 130 +++--------------------------- ESPController/src/HAL_ESP32.cpp | 130 ++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 118 deletions(-) diff --git a/ESPController/include/HAL_ESP32.h b/ESPController/include/HAL_ESP32.h index 12ed52ac..a9b5cfa9 100644 --- a/ESPController/include/HAL_ESP32.h +++ b/ESPController/include/HAL_ESP32.h @@ -109,130 +109,24 @@ class HAL_ESP32 void CANBUSEnable(bool value); void ConfigureCAN(); - bool IsVSPIMutexAvailable() - { - if (xVSPIMutex == NULL) - return false; - - return (uxSemaphoreGetCount(xVSPIMutex) == 1); - } - - bool GetDisplayMutex() - { - if (xDisplayMutex == NULL) - return false; - - // Wait 50ms max - bool reply = (xSemaphoreTake(xDisplayMutex, pdMS_TO_TICKS(50)) == pdTRUE); - if (!reply) - { - ESP_LOGE(TAG, "Unable to get Display mutex"); - } - return reply; - } - bool ReleaseDisplayMutex() - { - if (xDisplayMutex == NULL) - return false; - - return (xSemaphoreGive(xDisplayMutex) == pdTRUE); - } - - bool GetVSPIMutex() - { - if (xVSPIMutex == NULL) - return false; - - // Wait 25ms max - bool reply = (xSemaphoreTake(xVSPIMutex, pdMS_TO_TICKS(25)) == pdTRUE); - if (!reply) - { - ESP_LOGE(TAG, "Unable to get VSPI mutex"); - } - return reply; - } - bool ReleaseVSPIMutex() - { - if (xVSPIMutex == NULL) - return false; - - bool reply = (xSemaphoreGive(xVSPIMutex) == pdTRUE); - if (!reply) - { - ESP_LOGE(TAG, "Unable to release VSPI mutex"); - } - return reply; - } + bool GetDisplayMutex(); + bool ReleaseDisplayMutex(); - bool Geti2cMutex() - { - if (xi2cMutex == NULL) - return false; - - // Wait 100ms max - bool reply = (xSemaphoreTake(xi2cMutex, pdMS_TO_TICKS(100)) == pdTRUE); - if (!reply) - { - ESP_LOGE(TAG, "Unable to get I2C mutex"); - } - return reply; - } - bool Releasei2cMutex() - { - if (xi2cMutex == NULL) - return false; - - bool reply = (xSemaphoreGive(xi2cMutex) == pdTRUE); - if (!reply) - { - ESP_LOGE(TAG, "Unable to release I2C mutex"); - } - return reply; - } + bool IsVSPIMutexAvailable(); + bool GetVSPIMutex(); + bool ReleaseVSPIMutex(); - bool GetRS485Mutex() - { - if (RS485Mutex == NULL) - return false; - - // Wait 100ms max - bool reply = (xSemaphoreTake(RS485Mutex, pdMS_TO_TICKS(100)) == pdTRUE); - if (!reply) - { - ESP_LOGE(TAG, "Unable to get RS485 mutex"); - } - return reply; - } - bool ReleaseRS485Mutex() - { - if (RS485Mutex == NULL) - return false; + bool Geti2cMutex(); + bool Releasei2cMutex(); - return (xSemaphoreGive(RS485Mutex) == pdTRUE); - } + bool GetRS485Mutex();; + bool ReleaseRS485Mutex(); // Infinite loop flashing the LED RED/WHITE - void Halt(RGBLED colour) - { - ESP_LOGE(TAG, "SYSTEM HALTED"); - - while (true) - { - Led(RGBLED::Red); - delay(700); - Led(colour); - delay(300); - } - } + void Halt(RGBLED colour); - uint8_t LastTCA6408Value() - { - return TCA6408_Input; - } - uint8_t LastTCA9534APWRValue() - { - return TCA9534APWR_Input; - } + uint8_t LastTCA6408Value(); + uint8_t LastTCA9534APWRValue(); bool MountSDCard(); void UnmountSDCard(); TouchScreenValues TouchScreenUpdate(); diff --git a/ESPController/src/HAL_ESP32.cpp b/ESPController/src/HAL_ESP32.cpp index d603eb7a..055bf2f7 100644 --- a/ESPController/src/HAL_ESP32.cpp +++ b/ESPController/src/HAL_ESP32.cpp @@ -51,6 +51,136 @@ void HAL_ESP32::UnmountSDCard() } } +bool HAL_ESP32::IsVSPIMutexAvailable() +{ + if (xVSPIMutex == NULL) + return false; + return (uxSemaphoreGetCount(xVSPIMutex) == 1); +} + +bool HAL_ESP32::GetDisplayMutex() +{ + if (xDisplayMutex == NULL) + return false; + + // Wait 50ms max + bool reply = (xSemaphoreTake(xDisplayMutex, pdMS_TO_TICKS(50)) == pdTRUE); + if (!reply) + { + ESP_LOGE(TAG, "Unable to get Display mutex"); + } + return reply; +} + +bool HAL_ESP32::ReleaseDisplayMutex() +{ + if (xDisplayMutex == NULL) + return false; + + return (xSemaphoreGive(xDisplayMutex) == pdTRUE); +} + +bool HAL_ESP32::GetVSPIMutex() +{ + if (xVSPIMutex == NULL) + return false; + + // Wait 25ms max + bool reply = (xSemaphoreTake(xVSPIMutex, pdMS_TO_TICKS(25)) == pdTRUE); + if (!reply) + { + ESP_LOGE(TAG, "Unable to get VSPI mutex"); + } + return reply; +} + +bool HAL_ESP32::ReleaseVSPIMutex() +{ + if (xVSPIMutex == NULL) + return false; + + bool reply = (xSemaphoreGive(xVSPIMutex) == pdTRUE); + if (!reply) + { + ESP_LOGE(TAG, "Unable to release VSPI mutex"); + } + return reply; +} + +bool HAL_ESP32::Geti2cMutex() +{ + if (xi2cMutex == NULL) + return false; + + // Wait 100ms max + bool reply = (xSemaphoreTake(xi2cMutex, pdMS_TO_TICKS(100)) == pdTRUE); + if (!reply) + { + ESP_LOGE(TAG, "Unable to get I2C mutex"); + } + return reply; +} + +bool HAL_ESP32::Releasei2cMutex() +{ + if (xi2cMutex == NULL) + return false; + + bool reply = (xSemaphoreGive(xi2cMutex) == pdTRUE); + if (!reply) + { + ESP_LOGE(TAG, "Unable to release I2C mutex"); + } + return reply; +} + +bool HAL_ESP32::GetRS485Mutex() +{ + if (RS485Mutex == NULL) + return false; + + // Wait 100ms max + bool reply = (xSemaphoreTake(RS485Mutex, pdMS_TO_TICKS(100)) == pdTRUE); + if (!reply) + { + ESP_LOGE(TAG, "Unable to get RS485 mutex"); + } + return reply; +} + +bool HAL_ESP32::ReleaseRS485Mutex() +{ + if (RS485Mutex == NULL) + return false; + + return (xSemaphoreGive(RS485Mutex) == pdTRUE); +} + +// Infinite loop flashing the LED RED/WHITE +void HAL_ESP32::Halt(RGBLED colour) +{ + ESP_LOGE(TAG, "SYSTEM HALTED"); + + while (true) + { + Led(RGBLED::Red); + delay(700); + Led(colour); + delay(300); + } +} + +uint8_t HAL_ESP32::LastTCA6408Value() +{ + return TCA6408_Input; +} + +uint8_t HAL_ESP32::LastTCA9534APWRValue() +{ + return TCA9534APWR_Input; +} + + uint8_t HAL_ESP32::readByte(i2c_port_t i2c_num, uint8_t dev, uint8_t reg) { // We use the native i2c commands for ESP32 as the Arduino library From 582145fe6dac41a0037292eb26dec66366313384 Mon Sep 17 00:00:00 2001 From: ruza87 <129524347+ruza87@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:13:42 +0200 Subject: [PATCH 02/39] [FEATURE] Pylon RS485 emulation integrated into diyBMS. --- ESPController/include/defines.h | 9 +- ESPController/include/pylon_rs485.h | 55 ++++ ESPController/include/webserver_json_post.h | 1 + ESPController/src/Rules.cpp | 6 +- ESPController/src/main.cpp | 266 ++++++++-------- ESPController/src/pylon_rs485.cpp | 293 ++++++++++++++++++ ESPController/src/settings.cpp | 14 +- ESPController/src/webserver_json_post.cpp | 12 +- ESPController/src/webserver_json_requests.cpp | 4 +- ESPController/web_src/default.htm | 11 +- ESPController/web_src/pagecode.js | 2 +- 11 files changed, 523 insertions(+), 150 deletions(-) create mode 100644 ESPController/include/pylon_rs485.h create mode 100644 ESPController/src/pylon_rs485.cpp diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index b901528e..20de3bba 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -89,11 +89,12 @@ enum RelayType : uint8_t RELAY_PULSE = 0x01 }; -enum CanBusProtocolEmulation : uint8_t +enum ProtocolEmulation : uint8_t { - CANBUS_DISABLED = 0x00, + EMULATION_DISABLED = 0x00, CANBUS_VICTRON = 0x01, - CANBUS_PYLONTECH = 0x02 + CANBUS_PYLONTECH = 0x02, + RS485_PYLONTECH = 0x03 }; enum CurrentMonitorDevice : uint8_t @@ -174,7 +175,7 @@ struct diybms_eeprom_settings char language[2 + 1]; - CanBusProtocolEmulation canbusprotocol; + ProtocolEmulation protocol; uint16_t nominalbatcap; // Maximum charge voltage - scale 0.1 uint16_t chargevolt; diff --git a/ESPController/include/pylon_rs485.h b/ESPController/include/pylon_rs485.h new file mode 100644 index 00000000..2fbab373 --- /dev/null +++ b/ESPController/include/pylon_rs485.h @@ -0,0 +1,55 @@ +#ifndef DIYBMS_PYLON_RS485_H_ +#define DIYBMS_PYLON_RS485_H_ + +#include "defines.h" +#include "Rules.h" +#include "HAL_ESP32.h" + + +class PylonRS485 { + public: + /** + * @brief Constructor of the PylonRS485 class + */ + PylonRS485(uart_port_t portNum, diybms_eeprom_settings& settings, Rules& rules, currentmonitoring_struct& currentMonitor, + ControllerState& controllerState, HAL_ESP32& hal); + + /** + * @brief Call this to periodically check queries from inverter and to form a reply + */ + void handle_rx(); + + private: + typedef struct { + uint8_t soh; + uint16_t ver; + uint16_t addr; + uint16_t cid1; + uint16_t cid2; + char length[4]; + } __attribute__((packed)) THeader; + + uart_port_t uart_num; + diybms_eeprom_settings& settings; + Rules& rules; + currentmonitoring_struct& current_monitor; + ControllerState& controller_state; + HAL_ESP32& hal; + + uint16_t pack_voltage; + uint16_t charge_voltage; + uint16_t discharge_voltage; + uint16_t charge_current_limit; + uint16_t discharge_current_limit; + bool stop_charging; + bool stop_discharging; + uint8_t flags; + char tmp_buf[150]; + + uint32_t hex2int(char *hex, char len); + void recomputeFlags(); + void insertLength(char *buf, int payload_len); + int appendChecksum(char *buf, int buf_size, int payload_len); +}; + +#endif \ No newline at end of file diff --git a/ESPController/include/webserver_json_post.h b/ESPController/include/webserver_json_post.h index 3c934b7f..be583991 100644 --- a/ESPController/include/webserver_json_post.h +++ b/ESPController/include/webserver_json_post.h @@ -16,6 +16,7 @@ extern HAL_ESP32 hal; extern fs::SDFS SD; extern TaskHandle_t avrprog_task_handle; +extern TaskHandle_t rs485_rx_task_handle; extern uint32_t canbus_messages_received; extern uint32_t canbus_messages_sent; extern uint32_t canbus_messages_failed_sent; diff --git a/ESPController/src/Rules.cpp b/ESPController/src/Rules.cpp index 43a39079..32769b0d 100644 --- a/ESPController/src/Rules.cpp +++ b/ESPController/src/Rules.cpp @@ -401,7 +401,7 @@ void Rules::RunRules( bool Rules::SharedChargingDischargingRules(diybms_eeprom_settings *mysettings) { - if (mysettings->canbusprotocol == CanBusProtocolEmulation::CANBUS_DISABLED) + if (mysettings->protocol == ProtocolEmulation::EMULATION_DISABLED) return false; if (invalidModuleCount > 0) @@ -496,7 +496,7 @@ void Rules::CalculateDynamicChargeCurrent(diybms_eeprom_settings *mysettings, Ce // Remember dynamicChargeCurrent scale is 0.1 dynamicChargeCurrent = mysettings->chargecurrent; - if (!mysettings->dynamiccharge || mysettings->canbusprotocol == CanBusProtocolEmulation::CANBUS_DISABLED) + if (!mysettings->dynamiccharge || mysettings->protocol == ProtocolEmulation::EMULATION_DISABLED) { // Its switched off, use default return; @@ -549,7 +549,7 @@ void Rules::CalculateDynamicChargeCurrent(diybms_eeprom_settings *mysettings, Ce // Output is cached in variable dynamicChargeVoltage as its used in multiple places void Rules::CalculateDynamicChargeVoltage(diybms_eeprom_settings *mysettings, CellModuleInfo *cellarray) { - if (!mysettings->dynamiccharge || mysettings->canbusprotocol == CanBusProtocolEmulation::CANBUS_DISABLED) + if (!mysettings->dynamiccharge || mysettings->protocol == ProtocolEmulation::EMULATION_DISABLED) { // Its switched off, use default voltage dynamicChargeVoltage = mysettings->chargevolt; diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index c2865310..528882ff 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -70,6 +70,7 @@ static constexpr const char *const TAG = "diybms"; #include "mqtt.h" #include "victron_canbus.h" #include "pylon_canbus.h" +#include "pylon_rs485.h" #include "string_utils.h" #include @@ -178,6 +179,9 @@ uint16_t sequence = 0; ControllerState _controller_state = ControllerState::Unknown; +// Create PylonTech RS485 protocol emulation instance, passing references to necessary variables +PylonRS485 pylon_rs485(rs485_uart_num, mysettings, rules, currentMonitor, _controller_state, hal); + uint32_t time100 = 0; uint32_t time20 = 0; uint32_t time10 = 0; @@ -1274,7 +1278,7 @@ void ProcessRules() rules.SetWarning(InternalWarningCode::NoExternalTempSensor); } - if (mysettings.canbusprotocol != CanBusProtocolEmulation::CANBUS_DISABLED) + if (mysettings.protocol != ProtocolEmulation::EMULATION_DISABLED) { if (!rules.IsChargeAllowed(&mysettings)) { @@ -2372,7 +2376,7 @@ void CurrentMonitorSetAdvancedSettings(currentmonitoring_struct newvalues) mysettings.currentMonitoring_shunttempcoefficient = newvalues.modbus.shunttempcoefficient; ValidateConfiguration(&mysettings); SaveConfiguration(&mysettings); - + if (hal.GetVSPIMutex()) { currentmon_internal.Configure( @@ -2624,7 +2628,7 @@ void canbus_tx(void *param) // Delay 1 second vTaskDelay(pdMS_TO_TICKS(1000)); - if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_PYLONTECH) + if (mysettings.protocol == ProtocolEmulation::CANBUS_PYLONTECH) { // Pylon Tech Battery Emulation // https://github.com/PaulSturbo/DIY-BMS-CAN/blob/main/SEPLOS%20BMS%20CAN%20Protocoll%20V1.0.pdf @@ -2664,7 +2668,7 @@ void canbus_tx(void *param) // vTaskDelay(pdMS_TO_TICKS(100)); } - if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_VICTRON) + if (mysettings.protocol == ProtocolEmulation::CANBUS_VICTRON) { // minimum CAN-IDs required for the core functionality are 0x351, 0x355, 0x356 and 0x35A. @@ -2703,7 +2707,7 @@ void canbus_rx(void *param) for (;;) { - if (mysettings.canbusprotocol != CanBusProtocolEmulation::CANBUS_DISABLED) + if (mysettings.protocol != ProtocolEmulation::EMULATION_DISABLED && mysettings.protocol != ProtocolEmulation::RS485_PYLONTECH) { // Wait for message to be received @@ -2822,147 +2826,156 @@ void rs485_rx(void *param) { for (;;) { - // Wait until this task is triggered (sending queue task triggers it) - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - // Delay 50ms for the data to arrive - vTaskDelay(pdMS_TO_TICKS(50)); - - int len = 0; - - if (hal.GetRS485Mutex()) + // If Pylon Tech RS485 protocol emulation is enabled, MODBUS current shunt can't be used + if (mysettings.protocol == ProtocolEmulation::RS485_PYLONTECH) { - // Wait 200ms before timeout - len = uart_read_bytes(rs485_uart_num, frame, sizeof(frame), pdMS_TO_TICKS(200)); - hal.ReleaseRS485Mutex(); + // For this protocol emulation, RS485 on diyBMS acts as a slave, waiting for queries from the (master) inverter + pylon_rs485.handle_rx(); } - - // Min packet length of 5 bytes - if (len > 5) + else { - uint8_t id = frame[0]; + // Original RS485 RX handler, the RS485 port acts as a master + int len = 0; - uint16_t crc = ((frame[len - 2] << 8) | frame[len - 1]); // combine the crc Low & High bytes + // Wait until this task is triggered (sending queue task triggers it, or save of savechargeconfig form) + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - uint16_t temp = calculateCRC(frame, len - 2); - // Swap bytes to match MODBUS ordering - uint16_t calculatedCRC = (temp << 8) | (temp >> 8); + // Delay 50ms for the data to arrive + vTaskDelay(pdMS_TO_TICKS(50)); - // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + if (hal.GetRS485Mutex()) + { + // Wait 200ms before timeout + len = uart_read_bytes(rs485_uart_num, frame, sizeof(frame), pdMS_TO_TICKS(200)); + hal.ReleaseRS485Mutex(); + } - if (calculatedCRC == crc) - { - // if the calculated crc matches the recieved crc continue to process data... - uint8_t RS485Error = frame[1] & B10000000; - if (RS485Error == 0) + // Min packet length of 5 bytes + if (len > 5) { - uint8_t cmd = frame[1] & B01111111; - uint8_t length = frame[2]; + uint8_t id = frame[0]; - ESP_LOGD(TAG, "Recv %i bytes, id=%u, cmd=%u", len, id, cmd); - // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + uint16_t crc = ((frame[len - 2] << 8) | frame[len - 1]); // combine the crc Low & High bytes - if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::PZEM_017) - { - if (cmd == 6 && id == 248) - { - ESP_LOGI(TAG, "Reply to broadcast/change address"); - } - if (cmd == 6 && id == mysettings.currentMonitoringModBusAddress) - { - ESP_LOGI(TAG, "Reply to set param"); - } - else if (cmd == 3 && id == mysettings.currentMonitoringModBusAddress) - { - // 75mV shunt (hard coded for PZEM) - currentMonitor.modbus.shuntmillivolt = 75; + uint16_t temp = calculateCRC(frame, len - 2); + // Swap bytes to match MODBUS ordering + uint16_t calculatedCRC = (temp << 8) | (temp >> 8); - // Shunt type 0x0000 - 0x0003 (100A/50A/200A/300A) - switch (((uint32_t)frame[9] << 8 | (uint32_t)frame[10])) - { - case 0: - currentMonitor.modbus.shuntmaxcurrent = 100; - break; - case 1: - currentMonitor.modbus.shuntmaxcurrent = 50; - break; - case 2: - currentMonitor.modbus.shuntmaxcurrent = 200; - break; - case 3: - currentMonitor.modbus.shuntmaxcurrent = 300; - break; - default: - currentMonitor.modbus.shuntmaxcurrent = 0; - } - } - else if (cmd == 4 && id == mysettings.currentMonitoringModBusAddress && len == 21) - { - // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - - // memset(¤tMonitor.modbus, 0, sizeof(currentmonitor_raw_modbus)); - currentMonitor.validReadings = true; - currentMonitor.timestamp = esp_timer_get_time(); - // voltage in 0.01V - currentMonitor.modbus.voltage = (float)((uint32_t)frame[3] << 8 | (uint32_t)frame[4]) / (float)100.0; - // current in 0.01A - currentMonitor.modbus.current = (float)((uint32_t)frame[5] << 8 | (uint32_t)frame[6]) / (float)100.0; - // power in 0.1W - currentMonitor.modbus.power = ((uint32_t)frame[7] << 8 | (uint32_t)frame[8] | (uint32_t)frame[9] << 24 | (uint32_t)frame[10] << 16) / 10.0; - } - else - { - // Dump out unhandled reply - ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - } - } - // ESP_LOGD(TAG, "CRC pass Id=%u F=%u L=%u", id, cmd, length); - if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_MODBUS) - { - if (id == mysettings.currentMonitoringModBusAddress && cmd == 3) - { - ProcessDIYBMSCurrentMonitorRegisterReply(length); + // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - if (_tft_screen_available) - { - // Refresh the TFT display - xTaskNotify(updatetftdisplay_task_handle, 0x00, eNotifyAction::eNoAction); - } - } - else if (id == mysettings.currentMonitoringModBusAddress && cmd == 16) + if (calculatedCRC == crc) { - ESP_LOGI(TAG, "Write multiple regs, success"); + // if the calculated crc matches the recieved crc continue to process data... + uint8_t RS485Error = frame[1] & B10000000; + if (RS485Error == 0) + { + uint8_t cmd = frame[1] & B01111111; + uint8_t length = frame[2]; + + ESP_LOGD(TAG, "Recv %i bytes, id=%u, cmd=%u", len, id, cmd); + // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + + if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::PZEM_017) + { + if (cmd == 6 && id == 248) + { + ESP_LOGI(TAG, "Reply to broadcast/change address"); + } + if (cmd == 6 && id == mysettings.currentMonitoringModBusAddress) + { + ESP_LOGI(TAG, "Reply to set param"); + } + else if (cmd == 3 && id == mysettings.currentMonitoringModBusAddress) + { + // 75mV shunt (hard coded for PZEM) + currentMonitor.modbus.shuntmillivolt = 75; + + // Shunt type 0x0000 - 0x0003 (100A/50A/200A/300A) + switch (((uint32_t)frame[9] << 8 | (uint32_t)frame[10])) + { + case 0: + currentMonitor.modbus.shuntmaxcurrent = 100; + break; + case 1: + currentMonitor.modbus.shuntmaxcurrent = 50; + break; + case 2: + currentMonitor.modbus.shuntmaxcurrent = 200; + break; + case 3: + currentMonitor.modbus.shuntmaxcurrent = 300; + break; + default: + currentMonitor.modbus.shuntmaxcurrent = 0; + } + } + else if (cmd == 4 && id == mysettings.currentMonitoringModBusAddress && len == 21) + { + // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + + // memset(¤tMonitor.modbus, 0, sizeof(currentmonitor_raw_modbus)); + currentMonitor.validReadings = true; + currentMonitor.timestamp = esp_timer_get_time(); + // voltage in 0.01V + currentMonitor.modbus.voltage = (float)((uint32_t)frame[3] << 8 | (uint32_t)frame[4]) / (float)100.0; + // current in 0.01A + currentMonitor.modbus.current = (float)((uint32_t)frame[5] << 8 | (uint32_t)frame[6]) / (float)100.0; + // power in 0.1W + currentMonitor.modbus.power = ((uint32_t)frame[7] << 8 | (uint32_t)frame[8] | (uint32_t)frame[9] << 24 | (uint32_t)frame[10] << 16) / 10.0; + } + else + { + // Dump out unhandled reply + ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + } + } + // ESP_LOGD(TAG, "CRC pass Id=%u F=%u L=%u", id, cmd, length); + if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_MODBUS) + { + if (id == mysettings.currentMonitoringModBusAddress && cmd == 3) + { + ProcessDIYBMSCurrentMonitorRegisterReply(length); + + if (_tft_screen_available) + { + // Refresh the TFT display + xTaskNotify(updatetftdisplay_task_handle, 0x00, eNotifyAction::eNoAction); + } + } + else if (id == mysettings.currentMonitoringModBusAddress && cmd == 16) + { + ESP_LOGI(TAG, "Write multiple regs, success"); + } + else + { + // Dump out unhandled reply + ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + } + } + } + else + { + ESP_LOGE(TAG, "RS485 error"); + ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + } } else { - // Dump out unhandled reply - ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + ESP_LOGE(TAG, "CRC error"); } - } } else { - ESP_LOGE(TAG, "RS485 error"); - ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + // We didn't receive anything on RS485, record error and mark current monitor as invalid + ESP_LOGE(TAG, "Short packet %i bytes", len); + + // Indicate that the current monitor values are now invalid/unknown + currentMonitor.validReadings = false; } - } - else - { - ESP_LOGE(TAG, "CRC error"); - } - } - else - { - // We didn't receive anything on RS485, record error and mark current monitor as invalid - ESP_LOGE(TAG, "Short packet %i bytes", len); - // Indicate that the current monitor values are now invalid/unknown - currentMonitor.validReadings = false; + // Notify sending queue, to continue + xTaskNotify(service_rs485_transmit_q_task_handle, 0x00, eNotifyAction::eNoAction); } - - // Notify sending queue, to continue - xTaskNotify(service_rs485_transmit_q_task_handle, 0x00, eNotifyAction::eNoAction); - } // infinite loop } @@ -3587,8 +3600,8 @@ void setup() ESP_LOGI(TAG, R"RAW( - _ __ - _| o |_) |\/| (_ + _ __ + _| o |_) |\/| (_ (_| | \/ |_) | | __) / @@ -3741,6 +3754,9 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)RAW", xTaskCreate(replyqueue_task, "rxq", 2000, nullptr, configMAX_PRIORITIES - 2, &replyqueue_task_handle); xTaskCreate(lazy_tasks, "lazyt", 2500, nullptr, 0, &lazy_task_handle); + // Create PylonTech RS485 protocol emulation handler + + // Set relay defaults for (int8_t y = 0; y < RELAY_TOTAL; y++) { diff --git a/ESPController/src/pylon_rs485.cpp b/ESPController/src/pylon_rs485.cpp new file mode 100644 index 00000000..c1b41cbc --- /dev/null +++ b/ESPController/src/pylon_rs485.cpp @@ -0,0 +1,293 @@ +/*------------------------------------------------------------------------ + * + * Project: PylonTech protocol emulation library for diyBMS + * Using RS485 @ 9600 baud + * + * Author: Michal Ruzek (ruza87) + * + * ----------------------------------------------------------------------- + */ + + +#include "pylon_rs485.h" + +#define PYL_VERSION 0x3832 // '28' in ASCII +#define PYL_ADDR 0x3230 // '02' in ASCII +#define PYL_CID1 0x3634 // '46' in ASCII + +#define CMD_VERSION 0x4634 // '4F' in ASCII +#define CMD_ANALOG_VAL 0x3234 // '42' in ASCII +#define CMD_CHARGE_MGMT 0x3239 // '92' in ASCII + + +PylonRS485::PylonRS485(uart_port_t portNum, diybms_eeprom_settings& settings, Rules& rules, currentmonitoring_struct& currentMonitor, + ControllerState& controllerState, HAL_ESP32& hal) + : uart_num(portNum), + settings(settings), + rules(rules), + current_monitor(currentMonitor), + controller_state(controllerState), + hal(hal) { + +} + + +uint32_t PylonRS485::hex2int(char *hex, char len) { + uint32_t val = 0; + char count = 0; + while (count < len) { + // get current character then increment + uint8_t byte = *hex++; + count++; + // transform hex character to the 4bit equivalent number, using the ascii table indexes + if (byte >= '0' && byte <= '9') byte = byte - '0'; + else if (byte >= 'a' && byte <='f') byte = byte - 'a' + 10; + else if (byte >= 'A' && byte <='F') byte = byte - 'A' + 10; + // shift 4 to make space for new digit, and add the 4 bits of the new digit + val = (val << 4) | (byte & 0xF); + } + return val; +} + + +void PylonRS485::recomputeFlags() { + uint8_t tmp = 0x00; + + if (!stop_charging) { + tmp |= 0x80; + } + + if (!stop_discharging) { + tmp |= 0x40; + } + + //single assignment at the end + flags = tmp; +} + + +void PylonRS485::insertLength(char *buf, int payload_len) { + //do not include header into payload len + payload_len -= sizeof(THeader); + uint8_t sum = payload_len & 0x0F; //first 4 digits + sum += (payload_len >> 4) & 0x0F; //second 4 digits + sum += (payload_len >> 8) & 0x0F; //third 4 digits + sum = sum % 16; // modulo 16 + sum = ~sum; // invert bits + sum = (sum + 1) & 0x0F; // add one. + + //print into temporary buffer (trailing \0 present) + char tmp[6]; + snprintf(tmp, sizeof(tmp), "%01X%03X", sum, payload_len); + + //insert into payload on proper position + THeader* hdr_ptr = (THeader*)buf; + for (char i=0; i<4; i++) { + hdr_ptr->length[i] = tmp[i]; + } +} + + +int PylonRS485::appendChecksum(char *buf, int buf_size, int payload_len) { + uint16_t sum = 0; + //do not include SOF into chksum (start at 1) + for (int i=1; i sizeof(hdr)) { + // at least size of header, read it out + rx_read = uart_read_bytes(uart_num, (uint8_t*)&hdr, sizeof(hdr), 100); + if (rx_read != sizeof(hdr)) { + //wrong size, bail out + uart_flush_input(uart_num); + hal.ReleaseRS485Mutex(); + return; + } + + // parse header, check values + if (hdr.soh != '~' || hdr.cid1 != PYL_CID1) { + // invalid message format, drop + uart_flush_input(uart_num); + hal.ReleaseRS485Mutex(); + return; + } + + // read the rest of the message (optional INFO + 4B of ascii chksum + terminator) + uint32_t body_len = (hex2int(hdr.length, 4)) & 0x0FFF; // mask to remove lchksum + body_len += 5; //add length of message checksum (4 chars in ASCII) and terminator '\r' + if (body_len > sizeof(tmp_buf) || body_len + rx_read > rx_avail) { + // requested more data than available, fail + uart_flush_input(uart_num); + hal.ReleaseRS485Mutex(); + return; + } + if (uart_read_bytes(uart_num, tmp_buf, body_len, 100) != body_len) { + //wrong size, bail out + uart_flush_input(uart_num); + hal.ReleaseRS485Mutex(); + return; + } + + // TODO: check payload checksum in tmp_buf?? + + // skip if not for us + if (hdr.addr != PYL_ADDR) { + hal.ReleaseRS485Mutex(); + return; + } + + // header seems good, parse command + switch (hdr.cid2) { + case CMD_VERSION: + //do not check version for this command + response_len = snprintf(tmp_buf, sizeof(tmp_buf),"~" // SOH + "28" // VER + "02" // ADDR + "4600" // CID1 + RET of zero + "0000" // Zero LENGTH + ); + break; + + case CMD_ANALOG_VAL: + //skip if version doesn't match + if (hdr.ver != PYL_VERSION) break; + + // If current shunt is installed, use the voltage from that as it should be more accurate + if (settings.currentMonitoringEnabled && current_monitor.validReadings) { + pack_voltage = current_monitor.modbus.voltage * 1000.0; + } + else { + // Use highest bank voltage calculated by controller and modules + pack_voltage = rules.highestBankVoltage; + } + + response_len = snprintf(tmp_buf, sizeof(tmp_buf),"~" // SOH + "28" // VER + "02" // ADDR + "4600" // CID1 + RET of zero + "XXXX" // Placeholder for length (computed later) + "11" // Info flags + "02" // Command == ADDR + "10" // Cell count = 16 (10h) + "0D200D200D200D200D200D200D200D200D200D200D200D200D200D200D200D20" // 16x cell voltage 3360mv as hexa ascii (taken from doc example) + "010BC30BC30BC30BCD0BCD" // 5x temperature sensor with value in kelvins (taken from doc example) + "0000" // current = 0 (resolution = tenths of mA, realValue = thisValue * 100) + "%04X" // pack voltage (mV) + "FFFF02" // remain capacity, user def = 02 + "FFFF" // total capacity + "0002" // num of cycles + , pack_voltage); + //add length + insertLength(tmp_buf, response_len); + break; + + case CMD_CHARGE_MGMT: + //skip if version doesn't match + if (hdr.ver != PYL_VERSION) break; + + // Defaults (do nothing) + charge_current_limit = 1; + discharge_current_limit = 1; + charge_voltage = rules.DynamicChargeVoltage(); + discharge_voltage = settings.dischargevolt; + if (rules.IsChargeAllowed(&settings)) { + if (rules.numberOfBalancingModules > 0 && settings.stopchargebalance == true) { + // Balancing is active, so stop charging + charge_current_limit = 1; + } + else { + // Default - normal behaviour + charge_current_limit = rules.DynamicChargeCurrent(); + } + } + if (rules.IsDischargeAllowed(&settings)) { + discharge_current_limit = settings.dischargecurrent; + } + + // My inverter doesn't read "Alarm message 0x44h" -> use status field of this message to stop chrg/dischrg + // when alarm occurs. + stop_charging = !rules.IsChargeAllowed(&settings); + stop_discharging = !rules.IsDischargeAllowed(&settings); + + // Battery high voltage alarm -> stop charging + stop_charging |= rules.rule_outcome[Rule::BankOverVoltage] | rules.rule_outcome[Rule::CurrentMonitorOverVoltage]; + + // Battery low voltage alarm -> stop discharging + stop_discharging |= rules.rule_outcome[Rule::BankUnderVoltage] | rules.rule_outcome[Rule::CurrentMonitorUnderVoltage]; + + // Cell high voltage alarm -> stop charging + stop_charging |= rules.rule_outcome[Rule::ModuleOverVoltage]; + + // Cell low voltage alarm -> stop discharging + stop_discharging |= rules.rule_outcome[Rule::ModuleUnderVoltage]; + + // Battery temperature alarm, stop charging & discharging + if (rules.moduleHasExternalTempSensor) { + if (rules.rule_outcome[Rule::ModuleOverTemperatureExternal] || rules.rule_outcome[Rule::ModuleUnderTemperatureExternal]) { + stop_charging = true; + stop_discharging = true; + } + } + + // Comms error? Stop charging & discharging + if (rules.rule_outcome[Rule::BMSError] | rules.rule_outcome[Rule::EmergencyStop] | (controller_state != ControllerState::Running)) { + stop_charging = true; + stop_discharging = true; + } + + // Place bolean flags into byte status field + recomputeFlags(); + + response_len = snprintf(tmp_buf, sizeof(tmp_buf),"~" // SOH + "28" // VER + "02" // ADDR + "4600" // CID1 + RET of zero + "XXXX" // Placeholder for length (computed later) + "02" // Command == ADDR + "%04X" // Charge limit + "%04X" // Discharge limit + "%04X" // Charge current + "%04X" // Discharge current + "%02X" // Status flags + , charge_voltage, discharge_voltage, charge_current_limit, discharge_current_limit, flags); + //add length + insertLength(tmp_buf, response_len); + break; + + default: + //ignore unsupported commands + break; + } + + if (response_len > 0) { + //append checksum and trailer to the response (leading SOH is skipped during computation) + response_len = appendChecksum(tmp_buf, sizeof(tmp_buf), response_len); + + //send through UART + uart_write_bytes(uart_num, tmp_buf, response_len); + } + } + hal.ReleaseRS485Mutex(); + } +} + diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index e7970469..172f85c2 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -39,7 +39,7 @@ static const char influxdb_databasebucket_JSONKEY[] = "bucket"; static const char influxdb_orgid_JSONKEY[] = "org"; static const char influxdb_serverurl_JSONKEY[] = "url"; static const char influxdb_loggingFreqSeconds_JSONKEY[] = "logfreq"; -static const char canbusprotocol_JSONKEY[] = "canbusprotocol"; +static const char protocol_JSONKEY[] = "protocol"; static const char nominalbatcap_JSONKEY[] = "nominalbatcap"; static const char chargevolt_JSONKEY[] = "chargevolt"; static const char chargecurrent_JSONKEY[] = "chargecurrent"; @@ -109,7 +109,7 @@ static const char rs485baudrate_NVSKEY[] = "485baudrate"; static const char rs485databits_NVSKEY[] = "485databits"; static const char rs485parity_NVSKEY[] = "485parity"; static const char rs485stopbits_NVSKEY[] = "485stopbits"; -static const char canbusprotocol_NVSKEY[] = "canbusprotocol"; +static const char protocol_NVSKEY[] = "protocol"; static const char nominalbatcap_NVSKEY[] = "nominalbatcap"; static const char chargevolt_NVSKEY[] = "cha_volt"; static const char chargecurrent_NVSKEY[] = "cha_current"; @@ -354,7 +354,7 @@ void SaveConfiguration(diybms_eeprom_settings *settings) MACRO_NVSWRITE_UINT8(rs485databits); MACRO_NVSWRITE_UINT8(rs485parity); MACRO_NVSWRITE_UINT8(rs485stopbits); - MACRO_NVSWRITE_UINT8(canbusprotocol); + MACRO_NVSWRITE_UINT8(protocol); MACRO_NVSWRITE(currentMonitoring_shuntmv); MACRO_NVSWRITE(currentMonitoring_shuntmaxcur); @@ -486,7 +486,7 @@ void LoadConfiguration(diybms_eeprom_settings *settings) MACRO_NVSREAD_UINT8(rs485databits); MACRO_NVSREAD_UINT8(rs485parity); MACRO_NVSREAD_UINT8(rs485stopbits); - MACRO_NVSREAD_UINT8(canbusprotocol); + MACRO_NVSREAD_UINT8(protocol); MACRO_NVSREAD(nominalbatcap); MACRO_NVSREAD(chargevolt); MACRO_NVSREAD(chargecurrent); @@ -556,7 +556,7 @@ void DefaultConfiguration(diybms_eeprom_settings *_myset) // EEPROM settings are invalid so default configuration _myset->mqtt_enabled = false; - _myset->canbusprotocol = CanBusProtocolEmulation::CANBUS_DISABLED; + _myset->protocol = ProtocolEmulation::EMULATION_DISABLED; _myset->nominalbatcap = 280; // Scale 1 _myset->chargevolt = 565; // Scale 0.1 _myset->chargecurrent = 650; // Scale 0.1 @@ -970,7 +970,7 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin } } // end for - root[canbusprotocol_JSONKEY] = (uint8_t)settings->canbusprotocol; + root[protocol_JSONKEY] = (uint8_t)settings->protocol; root[nominalbatcap_JSONKEY] = settings->nominalbatcap; root[chargevolt_JSONKEY] = settings->chargevolt; @@ -1065,7 +1065,7 @@ void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) strncpy(settings->language, root[language_JSONKEY].as().c_str(), sizeof(settings->language)); - settings->canbusprotocol = (CanBusProtocolEmulation)root[canbusprotocol_JSONKEY]; + settings->protocol = (ProtocolEmulation)root[protocol_JSONKEY]; settings->nominalbatcap = root[nominalbatcap_JSONKEY]; settings->chargevolt = root[chargevolt_JSONKEY]; settings->chargecurrent = root[chargecurrent_JSONKEY]; diff --git a/ESPController/src/webserver_json_post.cpp b/ESPController/src/webserver_json_post.cpp index d837ec4c..4b0013f0 100644 --- a/ESPController/src/webserver_json_post.cpp +++ b/ESPController/src/webserver_json_post.cpp @@ -519,14 +519,14 @@ esp_err_t post_savers485settings_json_handler(httpd_req_t *req, bool urlEncoded) esp_err_t post_savechargeconfig_json_handler(httpd_req_t *req, bool urlEncoded) { uint8_t temp; - if (GetKeyValue(httpbuf, "canbusprotocol", &temp, urlEncoded)) + if (GetKeyValue(httpbuf, "protocol", &temp, urlEncoded)) { - mysettings.canbusprotocol = (CanBusProtocolEmulation)temp; + mysettings.protocol = (ProtocolEmulation)temp; } else { // Field not found/invalid, so disable - mysettings.canbusprotocol = CanBusProtocolEmulation::CANBUS_DISABLED; + mysettings.protocol = ProtocolEmulation::EMULATION_DISABLED; } if (GetKeyValue(httpbuf, "nominalbatcap", &mysettings.nominalbatcap, urlEncoded)) { @@ -614,6 +614,12 @@ esp_err_t post_savechargeconfig_json_handler(httpd_req_t *req, bool urlEncoded) saveConfiguration(); + // Notify the RS458 RX task that might be in bocking state (after enabling the RS485_PYLONTECH emulation) + if (rs485_rx_task_handle != NULL) + { + xTaskNotify(rs485_rx_task_handle, 0x00, eNotifyAction::eNoAction); + } + return SendSuccess(req); } diff --git a/ESPController/src/webserver_json_requests.cpp b/ESPController/src/webserver_json_requests.cpp index 495fa09b..6ff57c74 100644 --- a/ESPController/src/webserver_json_requests.cpp +++ b/ESPController/src/webserver_json_requests.cpp @@ -531,7 +531,7 @@ esp_err_t content_handler_chargeconfig(httpd_req_t *req) JsonObject root = doc.to(); JsonObject settings = root.createNestedObject("chargeconfig"); - settings["canbusprotocol"] = mysettings.canbusprotocol; + settings["protocol"] = mysettings.protocol; settings["nominalbatcap"] = mysettings.nominalbatcap; settings["chargevolt"] = mysettings.chargevolt; settings["chargecurrent"] = mysettings.chargecurrent; @@ -841,7 +841,7 @@ esp_err_t content_handler_monitor2(httpd_req_t *req) canbus_messages_received, &CookieValue[sizeof(CookieValue) - 3], prg.queueLength()); - if (mysettings.canbusprotocol != CanBusProtocolEmulation::CANBUS_DISABLED && mysettings.dynamiccharge) + if (mysettings.protocol != ProtocolEmulation::EMULATION_DISABLED && mysettings.dynamiccharge) { bufferused += snprintf(&httpbuf[bufferused], BUFSIZE - bufferused, "\"dyncv\":%u,\"dyncc\":%u,", diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index 68ba1f6c..b6a48aee 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -1117,11 +1117,11 @@

Charge/Discharge configuration

- This feature allows diyBMS to provide battery and BMS data to CANBUS devices using various 3rd party + This feature allows diyBMS to provide battery and BMS data to CANBUS or RS485 devices using various 3rd party protocols.

- These settings require an external inverter/charger to be able to integrate using CANBUS to control + These settings require an external inverter/charger to be able to integrate using CANBUS/RS485 to control charge/discharge parameters.

@@ -1133,11 +1133,12 @@

Charge/Discharge configuration

- - - + +
diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index fe86e561..3343941a 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -1774,7 +1774,7 @@ $(function () { $.getJSON("/api/chargeconfig", function (data) { - $("#canbusprotocol").val(data.chargeconfig.canbusprotocol); + $("#protocol").val(data.chargeconfig.protocol); $("#nominalbatcap").val(data.chargeconfig.nominalbatcap); $("#chargevolt").val((data.chargeconfig.chargevolt / 10.0).toFixed(1)); From 5c135d306a102b37c8ec1ab1b087325395656f01 Mon Sep 17 00:00:00 2001 From: ruza87 <129524347+ruza87@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:07:34 +0200 Subject: [PATCH 03/39] [BUGFIX] Invalid voltage scale for pylonRS485 --- ESPController/src/pylon_rs485.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ESPController/src/pylon_rs485.cpp b/ESPController/src/pylon_rs485.cpp index c1b41cbc..c1996e65 100644 --- a/ESPController/src/pylon_rs485.cpp +++ b/ESPController/src/pylon_rs485.cpp @@ -208,8 +208,8 @@ void PylonRS485::handle_rx() { // Defaults (do nothing) charge_current_limit = 1; discharge_current_limit = 1; - charge_voltage = rules.DynamicChargeVoltage(); - discharge_voltage = settings.dischargevolt; + charge_voltage = rules.DynamicChargeVoltage() * 100; //convert to mV scale + discharge_voltage = settings.dischargevolt * 100; //convert to mV scale if (rules.IsChargeAllowed(&settings)) { if (rules.numberOfBalancingModules > 0 && settings.stopchargebalance == true) { // Balancing is active, so stop charging From f49f29f053c872add6b61020e10880b10ffd4944 Mon Sep 17 00:00:00 2001 From: ruza87 <129524347+ruza87@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:51:06 +0200 Subject: [PATCH 04/39] [MAINTENANCE] Code cleanup --- ESPController/include/pylon_rs485.h | 1 - ESPController/src/pylon_rs485.cpp | 61 +++++++++++------------------ ESPController/web_src/default.htm | 2 +- 3 files changed, 23 insertions(+), 41 deletions(-) diff --git a/ESPController/include/pylon_rs485.h b/ESPController/include/pylon_rs485.h index 2fbab373..45cabaf9 100644 --- a/ESPController/include/pylon_rs485.h +++ b/ESPController/include/pylon_rs485.h @@ -47,7 +47,6 @@ class PylonRS485 { char tmp_buf[150]; uint32_t hex2int(char *hex, char len); - void recomputeFlags(); void insertLength(char *buf, int payload_len); int appendChecksum(char *buf, int buf_size, int payload_len); }; diff --git a/ESPController/src/pylon_rs485.cpp b/ESPController/src/pylon_rs485.cpp index c1996e65..b3d9b2c7 100644 --- a/ESPController/src/pylon_rs485.cpp +++ b/ESPController/src/pylon_rs485.cpp @@ -11,6 +11,8 @@ #include "pylon_rs485.h" +static constexpr const char * const TAG = "diybms-pylon485"; + #define PYL_VERSION 0x3832 // '28' in ASCII #define PYL_ADDR 0x3230 // '02' in ASCII #define PYL_CID1 0x3634 // '46' in ASCII @@ -50,22 +52,6 @@ uint32_t PylonRS485::hex2int(char *hex, char len) { } -void PylonRS485::recomputeFlags() { - uint8_t tmp = 0x00; - - if (!stop_charging) { - tmp |= 0x80; - } - - if (!stop_discharging) { - tmp |= 0x40; - } - - //single assignment at the end - flags = tmp; -} - - void PylonRS485::insertLength(char *buf, int payload_len) { //do not include header into payload len payload_len -= sizeof(THeader); @@ -229,35 +215,31 @@ void PylonRS485::handle_rx() { stop_charging = !rules.IsChargeAllowed(&settings); stop_discharging = !rules.IsDischargeAllowed(&settings); - // Battery high voltage alarm -> stop charging - stop_charging |= rules.rule_outcome[Rule::BankOverVoltage] | rules.rule_outcome[Rule::CurrentMonitorOverVoltage]; - - // Battery low voltage alarm -> stop discharging - stop_discharging |= rules.rule_outcome[Rule::BankUnderVoltage] | rules.rule_outcome[Rule::CurrentMonitorUnderVoltage]; + // The IsChargeAllowed / IsDischargeAllowed methods already cover: + // - Internal errors, Emergency stop + // - Low & High module temperature alarms (set in Rules, stops both chrg & dischrg) + // - Bank overvoltage & undervoltage alarm (set in Rules, stops both chrg & dischrg) + // - External temperature alarm (set on Charging page, stops both chrg & dischrg) + // - Low bank voltage (set on Charging page, stops discharging) + // - High bank voltage (set on Charging page, stops charging) + // - Cell undervoltage (set on Charging page, stops discharging) + // - Cell overvoltage (set on Charging page, stops charging) - // Cell high voltage alarm -> stop charging - stop_charging |= rules.rule_outcome[Rule::ModuleOverVoltage]; + // Battery high voltage alarm from Current monitor -> stop charging + stop_charging |= rules.rule_outcome[Rule::CurrentMonitorOverVoltage]; - // Cell low voltage alarm -> stop discharging - stop_discharging |= rules.rule_outcome[Rule::ModuleUnderVoltage]; + // Battery low voltage alarm from Current monitor -> stop discharging + stop_discharging |= rules.rule_outcome[Rule::CurrentMonitorUnderVoltage]; - // Battery temperature alarm, stop charging & discharging - if (rules.moduleHasExternalTempSensor) { - if (rules.rule_outcome[Rule::ModuleOverTemperatureExternal] || rules.rule_outcome[Rule::ModuleUnderTemperatureExternal]) { - stop_charging = true; - stop_discharging = true; - } + // Place bolean flags into byte status field + flags = 0; + if (!stop_charging) { + flags |= 0x80; } - - // Comms error? Stop charging & discharging - if (rules.rule_outcome[Rule::BMSError] | rules.rule_outcome[Rule::EmergencyStop] | (controller_state != ControllerState::Running)) { - stop_charging = true; - stop_discharging = true; + if (!stop_discharging) { + flags |= 0x40; } - // Place bolean flags into byte status field - recomputeFlags(); - response_len = snprintf(tmp_buf, sizeof(tmp_buf),"~" // SOH "28" // VER "02" // ADDR @@ -276,6 +258,7 @@ void PylonRS485::handle_rx() { default: //ignore unsupported commands + ESP_LOGD(TAG, "Unsupported command 0x%04X", hdr.cid2); break; } diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index b6a48aee..c1344347 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -1265,7 +1265,7 @@

Charge/Discharge configuration

Warning - CANBUS BMS integration is currently at an EXPERIMENTAL stage, please report issues. + CANBUS/RS485 BMS integration is currently at an EXPERIMENTAL stage, please report issues.

Remember to install terminator resistors at both ends of CAN connection. On the controller, jumper JP1 can be From dd67c8e3628ecd3ff446802371a06e9dc9d395f4 Mon Sep 17 00:00:00 2001 From: ruza87 <129524347+ruza87@users.noreply.github.com> Date: Fri, 30 Jun 2023 09:40:59 +0200 Subject: [PATCH 05/39] [BUGFIX] Add delay when no RX data to read --- ESPController/src/pylon_rs485.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ESPController/src/pylon_rs485.cpp b/ESPController/src/pylon_rs485.cpp index b3d9b2c7..af142c38 100644 --- a/ESPController/src/pylon_rs485.cpp +++ b/ESPController/src/pylon_rs485.cpp @@ -102,7 +102,7 @@ void PylonRS485::handle_rx() { ESP_ERROR_CHECK(uart_get_buffered_data_len(uart_num, (size_t*)&rx_avail)); if (rx_avail > sizeof(hdr)) { // at least size of header, read it out - rx_read = uart_read_bytes(uart_num, (uint8_t*)&hdr, sizeof(hdr), 100); + rx_read = uart_read_bytes(uart_num, (uint8_t*)&hdr, sizeof(hdr), pdMS_TO_TICKS(500)); if (rx_read != sizeof(hdr)) { //wrong size, bail out uart_flush_input(uart_num); @@ -127,7 +127,7 @@ void PylonRS485::handle_rx() { hal.ReleaseRS485Mutex(); return; } - if (uart_read_bytes(uart_num, tmp_buf, body_len, 100) != body_len) { + if (uart_read_bytes(uart_num, tmp_buf, body_len, pdMS_TO_TICKS(500)) != body_len) { //wrong size, bail out uart_flush_input(uart_num); hal.ReleaseRS485Mutex(); @@ -269,8 +269,15 @@ void PylonRS485::handle_rx() { //send through UART uart_write_bytes(uart_num, tmp_buf, response_len); } + + hal.ReleaseRS485Mutex(); + } + else { + // Got nothing or buffer too small, suspend the task + hal.ReleaseRS485Mutex(); + vTaskDelay(pdMS_TO_TICKS(200)); } - hal.ReleaseRS485Mutex(); + } } From fb366605b0bfb770ee14b99cb6b9a60373cee13f Mon Sep 17 00:00:00 2001 From: ruza87 <129524347+ruza87@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:34:41 +0200 Subject: [PATCH 06/39] [MAINTENANCE] After merge fixes --- ESPController/include/Rules.h | 17 ++--------------- ESPController/src/Rules.cpp | 21 +++++++++++++++++++-- ESPController/src/main.cpp | 2 +- ESPController/src/pylon_rs485.cpp | 6 +++--- ESPController/src/webserver_json_post.cpp | 6 +++--- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/ESPController/include/Rules.h b/ESPController/include/Rules.h index f083f082..1393d54b 100644 --- a/ESPController/include/Rules.h +++ b/ESPController/include/Rules.h @@ -147,14 +147,7 @@ class Rules /// @brief Set a rule status /// @param r Rule to change /// @param value True = rule is active - void setRuleStatus(Rule r, bool value) - { - if (ruleOutcome(r) != value) - { - rule_outcome.at(r) = value; - ESP_LOGI(TAG, "Rule %s state=%u", RuleTextDescription.at(r).c_str(), (uint8_t)value); - } - } + void setRuleStatus(Rule r, bool value); // True if at least 1 module has an external temp sensor fitted bool moduleHasExternalTempSensor; @@ -177,13 +170,7 @@ class Rules { return chargemode; } - void setChargingMode(ChargingMode newMode) - { - if (chargemode == newMode) - return; - ESP_LOGI(TAG, "Charging mode changed %u", newMode); - chargemode = newMode; - } + void setChargingMode(ChargingMode newMode); // Number of modules which have not yet reported back to the controller uint8_t invalidModuleCount; diff --git a/ESPController/src/Rules.cpp b/ESPController/src/Rules.cpp index 5c3f3563..84e3b3c4 100644 --- a/ESPController/src/Rules.cpp +++ b/ESPController/src/Rules.cpp @@ -703,7 +703,7 @@ uint16_t Rules::StateOfChargeWithRulesApplied(const diybms_eeprom_settings *myse void Rules::CalculateChargingMode(const diybms_eeprom_settings *mysettings, const currentmonitoring_struct *currentMonitor) { // If we are not using CANBUS - ignore the charge mode, it doesn't mean anything - if (mysettings->canbusprotocol == CanBusProtocolEmulation::CANBUS_DISABLED) + if (mysettings->protocol == ProtocolEmulation::EMULATION_DISABLED) { return; } @@ -721,7 +721,7 @@ void Rules::CalculateChargingMode(const diybms_eeprom_settings *mysettings, cons // or battery is below resume level so normal charging operation in progress // No difference in STANDARD or DYNAMIC modes - purely visual on screen/cosmetic - if (mysettings->dynamiccharge == true && mysettings->canbusprotocol != CanBusProtocolEmulation::CANBUS_DISABLED) + if (mysettings->dynamiccharge == true && mysettings->protocol != ProtocolEmulation::EMULATION_DISABLED) { setChargingMode(ChargingMode::dynamic); } @@ -763,4 +763,21 @@ void Rules::CalculateChargingMode(const diybms_eeprom_settings *mysettings, cons return; } } +} + +void Rules::setRuleStatus(Rule r, bool value) +{ + if (ruleOutcome(r) != value) + { + rule_outcome.at(r) = value; + ESP_LOGI(TAG, "Rule %s state=%u", RuleTextDescription.at(r).c_str(), (uint8_t)value); + } +} + +void Rules::setChargingMode(ChargingMode newMode) +{ + if (chargemode == newMode) + return; + ESP_LOGI(TAG, "Charging mode changed %u", newMode); + chargemode = newMode; } \ No newline at end of file diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index 9795b004..ab1b14fa 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -2785,7 +2785,7 @@ void send_canbus_message(uint32_t identifier, const uint8_t *buffer, const uint8 { for (;;) { - while (mysettings.protocol == ProtocolEmulation::CANBUS_DISABLED || mysettings.protocol == ProtocolEmulation::RS485_PYLONTECH) + while (mysettings.protocol == ProtocolEmulation::EMULATION_DISABLED || mysettings.protocol == ProtocolEmulation::RS485_PYLONTECH) { // Canbus is disbled, sleep until this changes.... vTaskDelay(pdMS_TO_TICKS(2000)); diff --git a/ESPController/src/pylon_rs485.cpp b/ESPController/src/pylon_rs485.cpp index af142c38..fb6fbaac 100644 --- a/ESPController/src/pylon_rs485.cpp +++ b/ESPController/src/pylon_rs485.cpp @@ -226,10 +226,10 @@ void PylonRS485::handle_rx() { // - Cell overvoltage (set on Charging page, stops charging) // Battery high voltage alarm from Current monitor -> stop charging - stop_charging |= rules.rule_outcome[Rule::CurrentMonitorOverVoltage]; + stop_charging |= rules.ruleOutcome(Rule::CurrentMonitorOverVoltage); // Battery low voltage alarm from Current monitor -> stop discharging - stop_discharging |= rules.rule_outcome[Rule::CurrentMonitorUnderVoltage]; + stop_discharging |= rules.ruleOutcome(Rule::CurrentMonitorUnderVoltage); // Place bolean flags into byte status field flags = 0; @@ -277,7 +277,7 @@ void PylonRS485::handle_rx() { hal.ReleaseRS485Mutex(); vTaskDelay(pdMS_TO_TICKS(200)); } - + } } diff --git a/ESPController/src/webserver_json_post.cpp b/ESPController/src/webserver_json_post.cpp index a92071f5..71c3cd1c 100644 --- a/ESPController/src/webserver_json_post.cpp +++ b/ESPController/src/webserver_json_post.cpp @@ -476,7 +476,7 @@ esp_err_t post_savechargeconfig_json_handler(httpd_req_t *req, bool urlEncoded) else { // Field not found/invalid, so disable - mysettings.protocol = ProtocolEmulation::CANBUS_DISABLED; + mysettings.protocol = ProtocolEmulation::EMULATION_DISABLED; mysettings.canbusinverter = CanBusInverter::INVERTER_GENERIC; } @@ -557,7 +557,7 @@ esp_err_t post_savechargeconfig_json_handler(httpd_req_t *req, bool urlEncoded) GetKeyValue(httpbuf, "floattimer", &mysettings.floatvoltagetimer, urlEncoded); GetKeyValue(httpbuf, "socresume", &mysettings.stateofchargeresumevalue, urlEncoded); - if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_DISABLED) + if (mysettings.protocol == ProtocolEmulation::EMULATION_DISABLED) { // Reset CAN counters if its disabled. canbus_messages_received = 0; @@ -567,7 +567,7 @@ esp_err_t post_savechargeconfig_json_handler(httpd_req_t *req, bool urlEncoded) } // Default GENERIC inverter for VICTRON integration - if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_VICTRON) + if (mysettings.protocol == ProtocolEmulation::CANBUS_VICTRON) { mysettings.canbusinverter = CanBusInverter::INVERTER_GENERIC; } From b58632f6a1fc73167c0c5b0d1d3d308b4037d226 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:17:55 +0000 Subject: [PATCH 07/39] Store total amp-hour counts into NVS --- ESPController/include/defines.h | 6 ++- ESPController/include/settings.h | 6 ++- ESPController/include/webserver.h | 6 +-- .../include/webserver_helper_funcs.h | 2 + ESPController/src/main.cpp | 30 +++++++---- ESPController/src/settings.cpp | 51 ++++++++++++++----- 6 files changed, 72 insertions(+), 29 deletions(-) diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index 861d657d..4ef491fa 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -20,7 +20,7 @@ #define SERIAL_RS485 Serial1 // Total number of cells a single controler can handle (memory limitation) -#define maximum_controller_cell_modules 128 +#define maximum_controller_cell_modules 192 typedef union { @@ -254,6 +254,10 @@ struct diybms_eeprom_settings uint8_t canbus_equipment_addr; // battery index on the same canbus for PYLONFORCE, 0 - 15, default 0 char homeassist_apikey[24+1]; + + // State of health variables + uint32_t soh_total_milliamphour_out; + uint32_t soh_total_milliamphour_in; }; typedef union diff --git a/ESPController/include/settings.h b/ESPController/include/settings.h index f75c9649..429236bf 100644 --- a/ESPController/include/settings.h +++ b/ESPController/include/settings.h @@ -14,7 +14,7 @@ bool getSetting(nvs_handle_t handle, const char *key, float *out_value); bool getSetting(nvs_handle_t handle, const char *key, uint8_t *out_value); bool getSetting(nvs_handle_t handle, const char *key, int8_t *out_value); bool getSetting(nvs_handle_t handle, const char *key, uint16_t *out_value); -// bool getSetting(nvs_handle_t handle, const char *key, uint32_t *out_value); +bool getSetting(nvs_handle_t handle, const char *key, uint32_t *out_value); bool getSetting(nvs_handle_t handle, const char *key, int32_t *out_value); bool getSetting(nvs_handle_t handle, const char *key, int16_t *out_value); bool getSetting(nvs_handle_t handle, const char *key, bool *out_value); @@ -22,7 +22,7 @@ bool getSetting(nvs_handle_t handle, const char *key, bool *out_value); bool getSettingBlob(nvs_handle_t handle, const char *key, void *out_value, size_t size); void InitializeNVS(); -void SaveConfiguration(diybms_eeprom_settings *settings); +void SaveConfiguration(const diybms_eeprom_settings *settings); void LoadConfiguration(diybms_eeprom_settings *settings); void ValidateConfiguration(diybms_eeprom_settings *settings); void DefaultConfiguration(diybms_eeprom_settings *settings); @@ -37,6 +37,8 @@ void writeSetting(nvs_handle_t handle, const char *key, bool value); void writeSetting(nvs_handle_t handle, const char *key, uint8_t value); void writeSetting(nvs_handle_t handle, const char *key, uint16_t value); void writeSetting(nvs_handle_t handle, const char *key, int16_t value); +void writeSetting(nvs_handle_t handle, const char *key, int32_t value); +void writeSetting(nvs_handle_t handle, const char *key, uint32_t value); void writeSetting(nvs_handle_t handle, const char *key, int8_t value); void writeSetting(nvs_handle_t handle, const char *key, const char *value); void writeSettingBlob(nvs_handle_t handle, const char *key, const void *value, size_t length); diff --git a/ESPController/include/webserver.h b/ESPController/include/webserver.h index 3c4830d7..494a45f7 100644 --- a/ESPController/include/webserver.h +++ b/ESPController/include/webserver.h @@ -27,8 +27,8 @@ int printBoolean(char *buffer, size_t bufferLen, const char *fieldName, boolean void generateUUID(); void StartServer(); -void resetModuleMinMaxVoltage(uint8_t module); -void clearModuleValues(uint8_t module); +void resetModuleMinMaxVoltage(uint8_t m); +void clearModuleValues(uint8_t m); httpd_handle_t start_webserver(void); void stop_webserver(httpd_handle_t server); @@ -47,6 +47,6 @@ extern RelayState previousRelayState[RELAY_TOTAL]; extern currentmonitoring_struct currentMonitor; extern void suspendTasksDuringFirmwareUpdate(); extern void resumeTasksAfterFirmwareUpdateFailure(); -extern void SaveConfiguration(diybms_eeprom_settings *settings); +extern void SaveConfiguration(const diybms_eeprom_settings *settings); extern esp_err_t content_handler_coredumpdownloadfile(httpd_req_t *req); #endif \ No newline at end of file diff --git a/ESPController/include/webserver_helper_funcs.h b/ESPController/include/webserver_helper_funcs.h index 610c6a81..1428c19d 100644 --- a/ESPController/include/webserver_helper_funcs.h +++ b/ESPController/include/webserver_helper_funcs.h @@ -30,6 +30,8 @@ bool validateXSSWithPOST(httpd_req_t *req, const char *postbuffer, bool urlEncod void setCookieValue(); void setCookie(httpd_req_t *req); +void randomCharacters(char* value, int length); + // These are borrowed from the new ESP IDF framework, will need to be removed if framework is upgraded esp_err_t httpd_req_get_cookie_val(httpd_req_t *req, const char *cookie_name, char *val, size_t *val_size); esp_err_t httpd_cookie_key_value(const char *cookie_str, const char *key, char *val, size_t *val_size); diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index b7959d40..6426a436 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -1584,14 +1584,14 @@ void ShutdownAllNetworkServices() } /// @brief Count of events of RSSI low -uint16_t wifi_count_rssi_low=0; -uint16_t wifi_count_sta_start=0; +uint16_t wifi_count_rssi_low = 0; +uint16_t wifi_count_sta_start = 0; /// @brief Count of events for WIFI connect -uint16_t wifi_count_sta_connected=0; +uint16_t wifi_count_sta_connected = 0; /// @brief Count of events for WIFI disconnect -uint16_t wifi_count_sta_disconnected=0; -uint16_t wifi_count_sta_lost_ip=0; -uint16_t wifi_count_sta_got_ip=0; +uint16_t wifi_count_sta_disconnected = 0; +uint16_t wifi_count_sta_lost_ip = 0; +uint16_t wifi_count_sta_got_ip = 0; /// @brief WIFI Event Handler /// @param @@ -2767,7 +2767,7 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c // Delay a little whilst sending packets to give ESP32 some breathing room and not flood the CANBUS // vTaskDelay(pdMS_TO_TICKS(100)); } - else if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_PYLONFORCEH2 ) + else if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_PYLONFORCEH2) { pylonforce_handle_tx(); } @@ -2823,10 +2823,10 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c canbus_messages_received++; ESP_LOGD(TAG, "CANBUS received message ID: %0x, DLC: %d, flags: %0x", message.identifier, message.data_length_code, message.flags); - if (!(message.flags & TWAI_MSG_FLAG_RTR)) // we do not answer to Remote-Transmission-Requests + if (!(message.flags & TWAI_MSG_FLAG_RTR)) // we do not answer to Remote-Transmission-Requests { -// ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG); - if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_PYLONFORCEH2 ) + // ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG); + if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_PYLONFORCEH2) { pylonforce_handle_rx(&message); } @@ -3195,6 +3195,7 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c // Screen off tftsleep(); } + } } @@ -3233,6 +3234,12 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c // Has day rolled over? if (year_day != timeinfo.tm_yday) { + + mysettings.soh_total_milliamphour_out += currentMonitor.modbus.daily_milliamphour_out; + mysettings.soh_total_milliamphour_in += currentMonitor.modbus.daily_milliamphour_in; + + SaveConfiguration(&mysettings); + // Reset the current monitor at midnight (ish) CurrentMonitorResetDailyAmpHourCounters(); @@ -3833,11 +3840,12 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)", LoadConfiguration(&mysettings); ValidateConfiguration(&mysettings); + if (strlen(mysettings.homeassist_apikey) == 0) { // Generate new key memset(&mysettings.homeassist_apikey, 0, sizeof(mysettings.homeassist_apikey)); - randomCharacters(mysettings.homeassist_apikey, sizeof(mysettings.homeassist_apikey) - 1); + randomCharacters(mysettings.homeassist_apikey, sizeof(mysettings.homeassist_apikey) - 1); saveConfiguration(); } ESP_LOGI(TAG, "homeassist_apikey=%s", mysettings.homeassist_apikey); diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index 993a08c4..72c9b3b4 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -91,6 +91,9 @@ static const char floatvoltagetimer_JSONKEY[] = "floatvoltagetimer"; static const char stateofchargeresumevalue_JSONKEY[] = "stateofchargeresumevalue"; static const char homeassist_apikey_JSONKEY[] = "homeassistapikey"; +static const char soh_total_milliamphour_out_JSONKEY[] = "soh_mah_out"; +static const char soh_total_milliamphour_in_JSONKEY[] = "soh_mah_in"; + /* NVS KEYS THESE STRINGS ARE USED TO HOLD THE PARAMETER IN NVS FLASH, MAXIMUM LENGTH OF 16 CHARACTERS */ @@ -122,7 +125,7 @@ static const char rs485stopbits_NVSKEY[] = "485stopbits"; static const char canbusprotocol_NVSKEY[] = "canbusprotocol"; static const char canbusinverter_NVSKEY[] = "canbusinverter"; static const char canbusbaud_NVSKEY[] = "canbusbaud"; -static const char canbus_equipment_addr_NVSKEY[]="canbusequip"; +static const char canbus_equipment_addr_NVSKEY[] = "canbusequip"; static const char nominalbatcap_NVSKEY[] = "nominalbatcap"; static const char chargevolt_NVSKEY[] = "cha_volt"; static const char chargecurrent_NVSKEY[] = "cha_current"; @@ -183,6 +186,10 @@ static const char floatvoltagetimer_NVSKEY[] = "floatVtimer"; static const char stateofchargeresumevalue_NVSKEY[] = "socresume"; static const char homeassist_apikey_NVSKEY[] = "haapikey"; +static const char soh_total_milliamphour_out_NVSKEY[] = "soh_mah_out"; +static const char soh_total_milliamphour_in_NVSKEY[] = "soh_mah_in"; + + #define MACRO_NVSWRITE(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, settings->VARNAME); #define MACRO_NVSWRITE_UINT8(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, (uint8_t)settings->VARNAME); #define MACRO_NVSWRITESTRING(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, &settings->VARNAME[0]); @@ -201,7 +208,6 @@ bool ValidateGetSetting(esp_err_t err, const char *key) case ESP_OK: ESP_LOGD(TAG, "Read key (%s)", key); return true; - break; case ESP_ERR_NVS_NOT_FOUND: ESP_LOGW(TAG, "Key not initialized (%s)", key); break; @@ -310,6 +316,11 @@ void writeSetting(nvs_handle_t handle, const char *key, int16_t value) ESP_LOGD(TAG, "Writing (%s)=%i", key, value); ESP_ERROR_CHECK(nvs_set_i16(handle, key, value)); } +void writeSetting(nvs_handle_t handle, const char *key, uint32_t value) +{ + ESP_LOGD(TAG, "Writing (%s)=%i", key, value); + ESP_ERROR_CHECK(nvs_set_u32(handle, key, value)); +} void writeSetting(nvs_handle_t handle, const char *key, int32_t value) { ESP_LOGD(TAG, "Writing (%s)=%i", key, value); @@ -331,7 +342,7 @@ void writeSettingBlob(nvs_handle_t handle, const char *key, const void *value, s ESP_ERROR_CHECK(nvs_set_blob(handle, key, value, length)); } -void SaveConfiguration(diybms_eeprom_settings *settings) +void SaveConfiguration(const diybms_eeprom_settings *settings) { const char *partname = "diybms-ctrl"; ESP_LOGI(TAG, "Write config"); @@ -442,6 +453,9 @@ void SaveConfiguration(diybms_eeprom_settings *settings) MACRO_NVSWRITESTRING(homeassist_apikey); + MACRO_NVSWRITE(soh_total_milliamphour_out) + MACRO_NVSWRITE(soh_total_milliamphour_in) + ESP_ERROR_CHECK(nvs_commit(nvs_handle)); nvs_close(nvs_handle); } @@ -571,6 +585,9 @@ void LoadConfiguration(diybms_eeprom_settings *settings) MACRO_NVSREADSTRING(homeassist_apikey); + MACRO_NVSREAD(soh_total_milliamphour_out); + MACRO_NVSREAD(soh_total_milliamphour_in); + nvs_close(nvs_handle); } @@ -605,7 +622,7 @@ void DefaultConfiguration(diybms_eeprom_settings *_myset) _myset->canbusinverter = CanBusInverter::INVERTER_GENERIC; _myset->canbus_equipment_addr = 0; - _myset->canbusbaud=500; + _myset->canbusbaud = 500; _myset->nominalbatcap = 280; // Scale 1 _myset->chargevolt = 565; // Scale 0.1 _myset->chargecurrent = 650; // Scale 0.1 @@ -753,6 +770,10 @@ void DefaultConfiguration(diybms_eeprom_settings *_myset) _myset->floatvoltagetimer = 6 * 60; // Once battery SoC drops below this value, resume normal charging operation _myset->stateofchargeresumevalue = 96; + + // State of health + _myset->soh_total_milliamphour_out = 0; + _myset->soh_total_milliamphour_in = 0; } /// @brief Save WIFI settings into FLASH NVS @@ -1015,7 +1036,7 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin root[rs485stopbits_JSONKEY] = settings->rs485stopbits; root[language_JSONKEY] = settings->language; - root[homeassist_apikey_JSONKEY]=settings->homeassist_apikey; + root[homeassist_apikey_JSONKEY] = settings->homeassist_apikey; JsonObject mqtt = root.createNestedObject("mqtt"); mqtt[mqtt_enabled_JSONKEY] = settings->mqtt_enabled; @@ -1074,7 +1095,7 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin root[canbusprotocol_JSONKEY] = (uint8_t)settings->canbusprotocol; root[canbusinverter_JSONKEY] = (uint8_t)settings->canbusinverter; root[canbusbaud_JSONKEY] = settings->canbusbaud; - root[canbus_equipment_addr_JSONKEY]=settings->canbus_equipment_addr; + root[canbus_equipment_addr_JSONKEY] = settings->canbus_equipment_addr; root[nominalbatcap_JSONKEY] = settings->nominalbatcap; root[chargevolt_JSONKEY] = settings->chargevolt; @@ -1113,6 +1134,9 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin } // wifi["password"] = DIYBMSSoftAP::Config().wifi_passphrase; + + root[soh_total_milliamphour_out_JSONKEY] = settings->soh_total_milliamphour_out; + root[soh_total_milliamphour_in_JSONKEY] = settings->soh_total_milliamphour_in; } void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) @@ -1177,7 +1201,7 @@ void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) settings->canbusprotocol = (CanBusProtocolEmulation)root[canbusprotocol_JSONKEY]; settings->canbusinverter = (CanBusInverter)root[canbusinverter_JSONKEY]; settings->canbusbaud = root[canbusbaud_JSONKEY]; - settings->canbus_equipment_addr=root[canbus_equipment_addr_JSONKEY]; + settings->canbus_equipment_addr = root[canbus_equipment_addr_JSONKEY]; settings->nominalbatcap = root[nominalbatcap_JSONKEY]; settings->chargevolt = root[chargevolt_JSONKEY]; settings->chargecurrent = root[chargecurrent_JSONKEY]; @@ -1201,10 +1225,13 @@ void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) settings->current_value1 = root[current_value1_JSONKEY]; settings->current_value2 = root[current_value2_JSONKEY]; - settings->absorptiontimer=root[absorptiontimer_JSONKEY]; - settings->floatvoltage=root[floatvoltage_JSONKEY]; - settings->floatvoltagetimer=root[floatvoltagetimer_JSONKEY]; - settings->stateofchargeresumevalue=root[stateofchargeresumevalue_JSONKEY]; + settings->absorptiontimer = root[absorptiontimer_JSONKEY]; + settings->floatvoltage = root[floatvoltage_JSONKEY]; + settings->floatvoltagetimer = root[floatvoltagetimer_JSONKEY]; + settings->stateofchargeresumevalue = root[stateofchargeresumevalue_JSONKEY]; + + settings->soh_total_milliamphour_out = root[soh_total_milliamphour_out_JSONKEY]; + settings->soh_total_milliamphour_in = root[soh_total_milliamphour_in_JSONKEY]; strncpy(settings->homeassist_apikey, root[homeassist_apikey_JSONKEY].as().c_str(), sizeof(settings->homeassist_apikey)); @@ -1212,7 +1239,7 @@ void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) if (!mqtt.isNull()) { settings->mqtt_enabled = mqtt[mqtt_enabled_JSONKEY]; - settings->mqtt_basic_cell_reporting=mqtt[mqtt_basic_cell_reporting_JSONKEY]; + settings->mqtt_basic_cell_reporting = mqtt[mqtt_basic_cell_reporting_JSONKEY]; strncpy(settings->mqtt_uri, mqtt[mqtt_uri_JSONKEY].as().c_str(), sizeof(settings->mqtt_uri)); strncpy(settings->mqtt_topic, mqtt[mqtt_topic_JSONKEY].as().c_str(), sizeof(settings->mqtt_topic)); strncpy(settings->mqtt_username, mqtt[mqtt_username_JSONKEY].as().c_str(), sizeof(settings->mqtt_username)); From a74fe405ee8c45c0ee5ee597257c6e061f1ef381 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:27:40 +0000 Subject: [PATCH 08/39] SOH calculations --- ESPController/include/defines.h | 12 +++++++++++- ESPController/src/Rules.cpp | 2 +- ESPController/src/main.cpp | 28 +++++++++++++++++++++++----- ESPController/src/pylon_canbus.cpp | 3 +-- ESPController/src/settings.cpp | 21 ++++++++++++++++++--- ESPController/src/victron_canbus.cpp | 4 ++-- 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index 4ef491fa..29a409d6 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -161,6 +161,7 @@ struct diybms_eeprom_settings uint16_t currentMonitoring_shuntmv; uint16_t currentMonitoring_shuntmaxcur; + /// @brief Amp-hours battery capacity uint16_t currentMonitoring_batterycapacity; uint16_t currentMonitoring_fullchargevolt; uint16_t currentMonitoring_tailcurrent; @@ -255,9 +256,18 @@ struct diybms_eeprom_settings uint8_t canbus_equipment_addr; // battery index on the same canbus for PYLONFORCE, 0 - 15, default 0 char homeassist_apikey[24+1]; - // State of health variables + /// @brief State of health variables - total lifetime mAh output (discharge) uint32_t soh_total_milliamphour_out; + /// @brief State of health variables - total lifetime mAh input (charge) uint32_t soh_total_milliamphour_in; + /// @brief State of health variables - total expected lifetime cycles of battery (6000) + uint16_t soh_lifetime_battery_cycles; + /// @brief State of health variables - estimated number of cycles + uint16_t soh_estimated_battery_cycles; + /// @brief State of health variables - discharge depth (80%) + uint8_t soh_discharge_depth; + /// @brief Calculated percentage calculation of health + float soh_percent; }; typedef union diff --git a/ESPController/src/Rules.cpp b/ESPController/src/Rules.cpp index d4bfeb93..6c8cea9c 100644 --- a/ESPController/src/Rules.cpp +++ b/ESPController/src/Rules.cpp @@ -667,7 +667,7 @@ void Rules::CalculateDynamicChargeVoltage(const diybms_eeprom_settings *mysettin /// @return SoC is rounded down to nearest integer and limits output range between 0 and 100. uint16_t Rules::StateOfChargeWithRulesApplied(const diybms_eeprom_settings *mysettings, float realSOC) const { - uint16_t value = floor(realSOC); + auto value = (uint16_t)floor(realSOC); // Deliberately force SoC to be reported as 2%, to trick external CANBUS devices into trickle charging (if they support it) if (mysettings->socforcelow) diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index 6426a436..dc438ce5 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -2821,11 +2821,11 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c if (res == ESP_OK) { canbus_messages_received++; - ESP_LOGD(TAG, "CANBUS received message ID: %0x, DLC: %d, flags: %0x", - message.identifier, message.data_length_code, message.flags); + // ESP_LOGD(TAG, "CANBUS received message ID: %0x, DLC: %d, flags: %0x",message.identifier, message.data_length_code, message.flags); + if (!(message.flags & TWAI_MSG_FLAG_RTR)) // we do not answer to Remote-Transmission-Requests { - // ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG); + // ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG); if (mysettings.canbusprotocol == CanBusProtocolEmulation::CANBUS_PYLONFORCEH2) { pylonforce_handle_rx(&message); @@ -3195,10 +3195,25 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c // Screen off tftsleep(); } - } } +void CalculateStateOfHealth(diybms_eeprom_settings *settings) +{ + // Value indicating what a typical discharge cycle looks like in amp-hours (normally 80% of cell for LFP) + float depth = 1000.0F * ((float)settings->currentMonitoring_batterycapacity/100.0F * (float)settings->soh_discharge_depth); + float in = (float)settings->soh_total_milliamphour_in / depth; + float out = (float)settings->soh_total_milliamphour_out / depth; + //Take worst case + float cycles = max(in, out); + + settings->soh_percent =100-((cycles / (float)settings->soh_lifetime_battery_cycles) * 100.0F); + + settings->soh_estimated_battery_cycles=(uint16_t)round(cycles); + + ESP_LOGI(TAG, "State of health calc %f %, estimated cycles=%f", settings->soh_percent, cycles); +} + // Do activities which are not critical to the system like background loading of config, or updating timing results etc. [[noreturn]] void lazy_tasks(void *) { @@ -3240,6 +3255,8 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c SaveConfiguration(&mysettings); + CalculateStateOfHealth(&mysettings); + // Reset the current monitor at midnight (ish) CurrentMonitorResetDailyAmpHourCounters(); @@ -3840,7 +3857,6 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)", LoadConfiguration(&mysettings); ValidateConfiguration(&mysettings); - if (strlen(mysettings.homeassist_apikey) == 0) { // Generate new key @@ -3883,6 +3899,8 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)", currentmon_internal.GuessSOC(); currentmon_internal.TakeReadings(); + + CalculateStateOfHealth(&mysettings); } else { diff --git a/ESPController/src/pylon_canbus.cpp b/ESPController/src/pylon_canbus.cpp index 7d8ffbfe..d9e51ca6 100644 --- a/ESPController/src/pylon_canbus.cpp +++ b/ESPController/src/pylon_canbus.cpp @@ -94,8 +94,7 @@ void pylon_message_355() data.stateofchargevalue = rules.StateOfChargeWithRulesApplied(&mysettings, currentMonitor.stateofcharge); // 2 SOH value un16 1 % - // TODO: Need to determine this based on age of battery/cycles etc. - data.stateofhealthvalue = 100; + data.stateofhealthvalue = (uint16_t)(trunc(mysettings.soh_percent)); send_canbus_message(0x355, (uint8_t *)&data, sizeof(data355)); } diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index 72c9b3b4..1f36185d 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -94,6 +94,9 @@ static const char homeassist_apikey_JSONKEY[] = "homeassistapikey"; static const char soh_total_milliamphour_out_JSONKEY[] = "soh_mah_out"; static const char soh_total_milliamphour_in_JSONKEY[] = "soh_mah_in"; +static const char soh_lifetime_battery_cycles_JSONKEY[] = "soh_batcycle"; +static const char soh_discharge_depth_JSONKEY[] = "soh_disdepth"; + /* NVS KEYS THESE STRINGS ARE USED TO HOLD THE PARAMETER IN NVS FLASH, MAXIMUM LENGTH OF 16 CHARACTERS */ @@ -188,7 +191,8 @@ static const char homeassist_apikey_NVSKEY[] = "haapikey"; static const char soh_total_milliamphour_out_NVSKEY[] = "soh_mah_out"; static const char soh_total_milliamphour_in_NVSKEY[] = "soh_mah_in"; - +static const char soh_lifetime_battery_cycles_NVSKEY[] = "soh_batcycle"; +static const char soh_discharge_depth_NVSKEY[] = "soh_disdepth"; #define MACRO_NVSWRITE(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, settings->VARNAME); #define MACRO_NVSWRITE_UINT8(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, (uint8_t)settings->VARNAME); @@ -455,6 +459,8 @@ void SaveConfiguration(const diybms_eeprom_settings *settings) MACRO_NVSWRITE(soh_total_milliamphour_out) MACRO_NVSWRITE(soh_total_milliamphour_in) + MACRO_NVSWRITE(soh_lifetime_battery_cycles) + MACRO_NVSWRITE_UINT8(soh_discharge_depth) ESP_ERROR_CHECK(nvs_commit(nvs_handle)); nvs_close(nvs_handle); @@ -585,8 +591,10 @@ void LoadConfiguration(diybms_eeprom_settings *settings) MACRO_NVSREADSTRING(homeassist_apikey); - MACRO_NVSREAD(soh_total_milliamphour_out); - MACRO_NVSREAD(soh_total_milliamphour_in); + MACRO_NVSREAD(soh_total_milliamphour_out) + MACRO_NVSREAD(soh_total_milliamphour_in) + MACRO_NVSREAD(soh_lifetime_battery_cycles) + MACRO_NVSREAD_UINT8(soh_discharge_depth) nvs_close(nvs_handle); } @@ -774,6 +782,9 @@ void DefaultConfiguration(diybms_eeprom_settings *_myset) // State of health _myset->soh_total_milliamphour_out = 0; _myset->soh_total_milliamphour_in = 0; + _myset->soh_lifetime_battery_cycles = 6000; + _myset->soh_discharge_depth = 80; + _myset->soh_percent = 100.0F; } /// @brief Save WIFI settings into FLASH NVS @@ -1137,6 +1148,8 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin root[soh_total_milliamphour_out_JSONKEY] = settings->soh_total_milliamphour_out; root[soh_total_milliamphour_in_JSONKEY] = settings->soh_total_milliamphour_in; + root[soh_lifetime_battery_cycles_JSONKEY] = settings->soh_lifetime_battery_cycles; + root[soh_discharge_depth_JSONKEY] = settings->soh_discharge_depth; } void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) @@ -1232,6 +1245,8 @@ void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) settings->soh_total_milliamphour_out = root[soh_total_milliamphour_out_JSONKEY]; settings->soh_total_milliamphour_in = root[soh_total_milliamphour_in_JSONKEY]; + settings->soh_lifetime_battery_cycles = root[soh_lifetime_battery_cycles_JSONKEY]; + settings->soh_discharge_depth = root[soh_discharge_depth_JSONKEY]; strncpy(settings->homeassist_apikey, root[homeassist_apikey_JSONKEY].as().c_str(), sizeof(settings->homeassist_apikey)); diff --git a/ESPController/src/victron_canbus.cpp b/ESPController/src/victron_canbus.cpp index efa26fd6..47f67e1a 100644 --- a/ESPController/src/victron_canbus.cpp +++ b/ESPController/src/victron_canbus.cpp @@ -183,7 +183,7 @@ void victron_message_355() struct data355 { uint16_t stateofchargevalue; - // uint16_t stateofhealthvalue; + uint16_t stateofhealthvalue; // uint16_t highresolutionsoc; }; @@ -193,7 +193,7 @@ void victron_message_355() // 0 SOC value un16 1 % data.stateofchargevalue = rules.StateOfChargeWithRulesApplied(&mysettings, currentMonitor.stateofcharge); // 2 SOH value un16 1 % - // data.stateofhealthvalue = 100; + data.stateofhealthvalue = (uint16_t)(trunc(mysettings.soh_percent)); send_canbus_message(0x355, (uint8_t *)&data, sizeof(data355)); } From c6eb40a361f3a176b928c826e1458d36108611e1 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:32:15 +0000 Subject: [PATCH 09/39] Code refactoring --- ESPController/src/settings.cpp | 336 ++++++++++++++++----------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index 1f36185d..dd9b8654 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -365,11 +365,11 @@ void SaveConfiguration(const diybms_eeprom_settings *settings) MACRO_NVSWRITE(baudRate) MACRO_NVSWRITE(interpacketgap) - MACRO_NVSWRITEBLOB(rulevalue); - MACRO_NVSWRITEBLOB(rulehysteresis); - MACRO_NVSWRITEBLOB(rulerelaystate); - MACRO_NVSWRITEBLOB(rulerelaydefault); - MACRO_NVSWRITEBLOB(relaytype); + MACRO_NVSWRITEBLOB(rulevalue) + MACRO_NVSWRITEBLOB(rulehysteresis) + MACRO_NVSWRITEBLOB(rulerelaystate) + MACRO_NVSWRITEBLOB(rulerelaydefault) + MACRO_NVSWRITEBLOB(relaytype) MACRO_NVSWRITE(graph_voltagehigh) MACRO_NVSWRITE(graph_voltagelow) @@ -383,33 +383,33 @@ void SaveConfiguration(const diybms_eeprom_settings *settings) MACRO_NVSWRITE(currentMonitoringEnabled) MACRO_NVSWRITE(currentMonitoringModBusAddress) - MACRO_NVSWRITE_UINT8(currentMonitoringDevice); + MACRO_NVSWRITE_UINT8(currentMonitoringDevice) MACRO_NVSWRITE(rs485baudrate) - MACRO_NVSWRITE_UINT8(rs485databits); - MACRO_NVSWRITE_UINT8(rs485parity); - MACRO_NVSWRITE_UINT8(rs485stopbits); - MACRO_NVSWRITE_UINT8(canbusprotocol); - MACRO_NVSWRITE_UINT8(canbusinverter); - MACRO_NVSWRITE(canbusbaud); - MACRO_NVSWRITE_UINT8(canbus_equipment_addr); - - MACRO_NVSWRITE(currentMonitoring_shuntmv); - MACRO_NVSWRITE(currentMonitoring_shuntmaxcur); - MACRO_NVSWRITE(currentMonitoring_batterycapacity); - MACRO_NVSWRITE(currentMonitoring_fullchargevolt); - MACRO_NVSWRITE(currentMonitoring_tailcurrent); - MACRO_NVSWRITE(currentMonitoring_chargeefficiency); - MACRO_NVSWRITE(currentMonitoring_shuntcal); - MACRO_NVSWRITE(currentMonitoring_temperaturelimit); - MACRO_NVSWRITE(currentMonitoring_overvoltagelimit); - MACRO_NVSWRITE(currentMonitoring_undervoltagelimit); - MACRO_NVSWRITE(currentMonitoring_overcurrentlimit); - MACRO_NVSWRITE(currentMonitoring_undercurrentlimit); - MACRO_NVSWRITE(currentMonitoring_overpowerlimit); - MACRO_NVSWRITE(currentMonitoring_shunttempcoefficient); - MACRO_NVSWRITE(currentMonitoring_tempcompenabled); - - MACRO_NVSWRITE(nominalbatcap); + MACRO_NVSWRITE_UINT8(rs485databits) + MACRO_NVSWRITE_UINT8(rs485parity) + MACRO_NVSWRITE_UINT8(rs485stopbits) + MACRO_NVSWRITE_UINT8(canbusprotocol) + MACRO_NVSWRITE_UINT8(canbusinverter) + MACRO_NVSWRITE(canbusbaud) + MACRO_NVSWRITE_UINT8(canbus_equipment_addr) + + MACRO_NVSWRITE(currentMonitoring_shuntmv) + MACRO_NVSWRITE(currentMonitoring_shuntmaxcur) + MACRO_NVSWRITE(currentMonitoring_batterycapacity) + MACRO_NVSWRITE(currentMonitoring_fullchargevolt) + MACRO_NVSWRITE(currentMonitoring_tailcurrent) + MACRO_NVSWRITE(currentMonitoring_chargeefficiency) + MACRO_NVSWRITE(currentMonitoring_shuntcal) + MACRO_NVSWRITE(currentMonitoring_temperaturelimit) + MACRO_NVSWRITE(currentMonitoring_overvoltagelimit) + MACRO_NVSWRITE(currentMonitoring_undervoltagelimit) + MACRO_NVSWRITE(currentMonitoring_overcurrentlimit) + MACRO_NVSWRITE(currentMonitoring_undercurrentlimit) + MACRO_NVSWRITE(currentMonitoring_overpowerlimit) + MACRO_NVSWRITE(currentMonitoring_shunttempcoefficient) + MACRO_NVSWRITE(currentMonitoring_tempcompenabled) + + MACRO_NVSWRITE(nominalbatcap) MACRO_NVSWRITE(chargevolt) MACRO_NVSWRITE(chargecurrent) MACRO_NVSWRITE(dischargecurrent) @@ -417,45 +417,45 @@ void SaveConfiguration(const diybms_eeprom_settings *settings) MACRO_NVSWRITE(cellminmv) MACRO_NVSWRITE(cellmaxmv) MACRO_NVSWRITE(kneemv) - MACRO_NVSWRITE(sensitivity); - MACRO_NVSWRITE(current_value1); - MACRO_NVSWRITE(current_value2); - MACRO_NVSWRITE(cellmaxspikemv); - MACRO_NVSWRITE(chargetemplow); - MACRO_NVSWRITE(chargetemphigh); - MACRO_NVSWRITE(dischargetemplow); - MACRO_NVSWRITE(dischargetemphigh); - MACRO_NVSWRITE(stopchargebalance); - MACRO_NVSWRITE(socoverride); - MACRO_NVSWRITE(socforcelow); - - MACRO_NVSWRITE(dynamiccharge); - MACRO_NVSWRITE(preventcharging); - MACRO_NVSWRITE(preventdischarge); - MACRO_NVSWRITE(mqtt_enabled); - MACRO_NVSWRITE(mqtt_basic_cell_reporting); - MACRO_NVSWRITE(influxdb_enabled); - MACRO_NVSWRITE(influxdb_loggingFreqSeconds); - - MACRO_NVSWRITEBLOB(tileconfig); - - MACRO_NVSWRITESTRING(ntpServer); - MACRO_NVSWRITESTRING(language); - MACRO_NVSWRITESTRING(mqtt_uri); - MACRO_NVSWRITESTRING(mqtt_topic); - MACRO_NVSWRITESTRING(mqtt_username); - MACRO_NVSWRITESTRING(mqtt_password); - MACRO_NVSWRITESTRING(influxdb_serverurl); - MACRO_NVSWRITESTRING(influxdb_databasebucket); - MACRO_NVSWRITESTRING(influxdb_apitoken); - MACRO_NVSWRITESTRING(influxdb_orgid); - - MACRO_NVSWRITE(absorptiontimer); - MACRO_NVSWRITE(floatvoltage); - MACRO_NVSWRITE(floatvoltagetimer); - MACRO_NVSWRITE(stateofchargeresumevalue); - - MACRO_NVSWRITESTRING(homeassist_apikey); + MACRO_NVSWRITE(sensitivity) + MACRO_NVSWRITE(current_value1) + MACRO_NVSWRITE(current_value2) + MACRO_NVSWRITE(cellmaxspikemv) + MACRO_NVSWRITE(chargetemplow) + MACRO_NVSWRITE(chargetemphigh) + MACRO_NVSWRITE(dischargetemplow) + MACRO_NVSWRITE(dischargetemphigh) + MACRO_NVSWRITE(stopchargebalance) + MACRO_NVSWRITE(socoverride) + MACRO_NVSWRITE(socforcelow) + + MACRO_NVSWRITE(dynamiccharge) + MACRO_NVSWRITE(preventcharging) + MACRO_NVSWRITE(preventdischarge) + MACRO_NVSWRITE(mqtt_enabled) + MACRO_NVSWRITE(mqtt_basic_cell_reporting) + MACRO_NVSWRITE(influxdb_enabled) + MACRO_NVSWRITE(influxdb_loggingFreqSeconds) + + MACRO_NVSWRITEBLOB(tileconfig) + + MACRO_NVSWRITESTRING(ntpServer) + MACRO_NVSWRITESTRING(language) + MACRO_NVSWRITESTRING(mqtt_uri) + MACRO_NVSWRITESTRING(mqtt_topic) + MACRO_NVSWRITESTRING(mqtt_username) + MACRO_NVSWRITESTRING(mqtt_password) + MACRO_NVSWRITESTRING(influxdb_serverurl) + MACRO_NVSWRITESTRING(influxdb_databasebucket) + MACRO_NVSWRITESTRING(influxdb_apitoken) + MACRO_NVSWRITESTRING(influxdb_orgid) + + MACRO_NVSWRITE(absorptiontimer) + MACRO_NVSWRITE(floatvoltage) + MACRO_NVSWRITE(floatvoltagetimer) + MACRO_NVSWRITE(stateofchargeresumevalue) + + MACRO_NVSWRITESTRING(homeassist_apikey) MACRO_NVSWRITE(soh_total_milliamphour_out) MACRO_NVSWRITE(soh_total_milliamphour_in) @@ -493,103 +493,103 @@ void LoadConfiguration(diybms_eeprom_settings *settings) else { // Open - MACRO_NVSREAD(totalNumberOfBanks); - MACRO_NVSREAD(totalNumberOfSeriesModules); - MACRO_NVSREAD(baudRate); - MACRO_NVSREAD(interpacketgap); - - MACRO_NVSREADBLOB(rulevalue); - MACRO_NVSREADBLOB(rulehysteresis); - MACRO_NVSREADBLOB(rulerelaystate); - MACRO_NVSREADBLOB(rulerelaydefault); - MACRO_NVSREADBLOB(relaytype); - - MACRO_NVSREAD(graph_voltagehigh); - MACRO_NVSREAD(graph_voltagelow); - MACRO_NVSREAD(BypassOverTempShutdown); - MACRO_NVSREAD(BypassThresholdmV); - MACRO_NVSREAD(timeZone); - MACRO_NVSREAD(minutesTimeZone); - MACRO_NVSREAD(daylight); - MACRO_NVSREAD(loggingEnabled); - MACRO_NVSREAD(loggingFrequencySeconds); - - MACRO_NVSREAD(currentMonitoringEnabled); - MACRO_NVSREAD(currentMonitoringModBusAddress); - MACRO_NVSREAD_UINT8(currentMonitoringDevice); - - MACRO_NVSREAD(currentMonitoring_shuntmv); - MACRO_NVSREAD(currentMonitoring_shuntmaxcur); - MACRO_NVSREAD(currentMonitoring_batterycapacity); - MACRO_NVSREAD(currentMonitoring_fullchargevolt); - MACRO_NVSREAD(currentMonitoring_tailcurrent); - MACRO_NVSREAD(currentMonitoring_chargeefficiency); - MACRO_NVSREAD(currentMonitoring_shuntcal); - MACRO_NVSREAD(currentMonitoring_temperaturelimit); - MACRO_NVSREAD(currentMonitoring_overvoltagelimit); - MACRO_NVSREAD(currentMonitoring_undervoltagelimit); - MACRO_NVSREAD(currentMonitoring_overcurrentlimit); - MACRO_NVSREAD(currentMonitoring_undercurrentlimit); - MACRO_NVSREAD(currentMonitoring_overpowerlimit); - MACRO_NVSREAD(currentMonitoring_shunttempcoefficient); - MACRO_NVSREAD(currentMonitoring_tempcompenabled); - - MACRO_NVSREAD(rs485baudrate); - MACRO_NVSREAD_UINT8(rs485databits); - MACRO_NVSREAD_UINT8(rs485parity); - MACRO_NVSREAD_UINT8(rs485stopbits); - MACRO_NVSREAD_UINT8(canbusprotocol); - MACRO_NVSREAD_UINT8(canbusinverter); - MACRO_NVSREAD(canbusbaud); + MACRO_NVSREAD(totalNumberOfBanks) + MACRO_NVSREAD(totalNumberOfSeriesModules) + MACRO_NVSREAD(baudRate) + MACRO_NVSREAD(interpacketgap) + + MACRO_NVSREADBLOB(rulevalue) + MACRO_NVSREADBLOB(rulehysteresis) + MACRO_NVSREADBLOB(rulerelaystate) + MACRO_NVSREADBLOB(rulerelaydefault) + MACRO_NVSREADBLOB(relaytype) + + MACRO_NVSREAD(graph_voltagehigh) + MACRO_NVSREAD(graph_voltagelow) + MACRO_NVSREAD(BypassOverTempShutdown) + MACRO_NVSREAD(BypassThresholdmV) + MACRO_NVSREAD(timeZone) + MACRO_NVSREAD(minutesTimeZone) + MACRO_NVSREAD(daylight) + MACRO_NVSREAD(loggingEnabled) + MACRO_NVSREAD(loggingFrequencySeconds) + + MACRO_NVSREAD(currentMonitoringEnabled) + MACRO_NVSREAD(currentMonitoringModBusAddress) + MACRO_NVSREAD_UINT8(currentMonitoringDevice) + + MACRO_NVSREAD(currentMonitoring_shuntmv) + MACRO_NVSREAD(currentMonitoring_shuntmaxcur) + MACRO_NVSREAD(currentMonitoring_batterycapacity) + MACRO_NVSREAD(currentMonitoring_fullchargevolt) + MACRO_NVSREAD(currentMonitoring_tailcurrent) + MACRO_NVSREAD(currentMonitoring_chargeefficiency) + MACRO_NVSREAD(currentMonitoring_shuntcal) + MACRO_NVSREAD(currentMonitoring_temperaturelimit) + MACRO_NVSREAD(currentMonitoring_overvoltagelimit) + MACRO_NVSREAD(currentMonitoring_undervoltagelimit) + MACRO_NVSREAD(currentMonitoring_overcurrentlimit) + MACRO_NVSREAD(currentMonitoring_undercurrentlimit) + MACRO_NVSREAD(currentMonitoring_overpowerlimit) + MACRO_NVSREAD(currentMonitoring_shunttempcoefficient) + MACRO_NVSREAD(currentMonitoring_tempcompenabled) + + MACRO_NVSREAD(rs485baudrate) + MACRO_NVSREAD_UINT8(rs485databits) + MACRO_NVSREAD_UINT8(rs485parity) + MACRO_NVSREAD_UINT8(rs485stopbits) + MACRO_NVSREAD_UINT8(canbusprotocol) + MACRO_NVSREAD_UINT8(canbusinverter) + MACRO_NVSREAD(canbusbaud) MACRO_NVSREAD_UINT8(canbus_equipment_addr) - MACRO_NVSREAD(nominalbatcap); - MACRO_NVSREAD(chargevolt); - MACRO_NVSREAD(chargecurrent); - MACRO_NVSREAD(dischargecurrent); - MACRO_NVSREAD(dischargevolt); - MACRO_NVSREAD(cellminmv); - MACRO_NVSREAD(cellmaxmv); - MACRO_NVSREAD(kneemv); - MACRO_NVSREAD(sensitivity); - MACRO_NVSREAD(current_value1); - MACRO_NVSREAD(current_value2); - MACRO_NVSREAD(cellmaxspikemv); - MACRO_NVSREAD(chargetemplow); - MACRO_NVSREAD(chargetemphigh); - MACRO_NVSREAD(dischargetemplow); - MACRO_NVSREAD(dischargetemphigh); - MACRO_NVSREAD(stopchargebalance); - MACRO_NVSREAD(socoverride); - MACRO_NVSREAD(socforcelow); - - MACRO_NVSREAD(dynamiccharge); - MACRO_NVSREAD(preventcharging); - MACRO_NVSREAD(preventdischarge); - - MACRO_NVSREAD(mqtt_enabled); - MACRO_NVSREAD(mqtt_basic_cell_reporting); - MACRO_NVSREAD(influxdb_enabled); - MACRO_NVSREAD(influxdb_loggingFreqSeconds); - - MACRO_NVSREADBLOB(tileconfig); - - MACRO_NVSREADSTRING(ntpServer); - MACRO_NVSREADSTRING(language); - MACRO_NVSREADSTRING(mqtt_uri); - MACRO_NVSREADSTRING(mqtt_topic); - MACRO_NVSREADSTRING(mqtt_username); - MACRO_NVSREADSTRING(mqtt_password); - MACRO_NVSREADSTRING(influxdb_serverurl); - MACRO_NVSREADSTRING(influxdb_databasebucket); - MACRO_NVSREADSTRING(influxdb_apitoken); - MACRO_NVSREADSTRING(influxdb_orgid); - - MACRO_NVSREAD(absorptiontimer); - MACRO_NVSREAD(floatvoltage); - MACRO_NVSREAD(floatvoltagetimer); - MACRO_NVSREAD_UINT8(stateofchargeresumevalue); - - MACRO_NVSREADSTRING(homeassist_apikey); + MACRO_NVSREAD(nominalbatcap) + MACRO_NVSREAD(chargevolt) + MACRO_NVSREAD(chargecurrent) + MACRO_NVSREAD(dischargecurrent) + MACRO_NVSREAD(dischargevolt) + MACRO_NVSREAD(cellminmv) + MACRO_NVSREAD(cellmaxmv) + MACRO_NVSREAD(kneemv) + MACRO_NVSREAD(sensitivity) + MACRO_NVSREAD(current_value1) + MACRO_NVSREAD(current_value2) + MACRO_NVSREAD(cellmaxspikemv) + MACRO_NVSREAD(chargetemplow) + MACRO_NVSREAD(chargetemphigh) + MACRO_NVSREAD(dischargetemplow) + MACRO_NVSREAD(dischargetemphigh) + MACRO_NVSREAD(stopchargebalance) + MACRO_NVSREAD(socoverride) + MACRO_NVSREAD(socforcelow) + + MACRO_NVSREAD(dynamiccharge) + MACRO_NVSREAD(preventcharging) + MACRO_NVSREAD(preventdischarge) + + MACRO_NVSREAD(mqtt_enabled) + MACRO_NVSREAD(mqtt_basic_cell_reporting) + MACRO_NVSREAD(influxdb_enabled) + MACRO_NVSREAD(influxdb_loggingFreqSeconds) + + MACRO_NVSREADBLOB(tileconfig) + + MACRO_NVSREADSTRING(ntpServer) + MACRO_NVSREADSTRING(language) + MACRO_NVSREADSTRING(mqtt_uri) + MACRO_NVSREADSTRING(mqtt_topic) + MACRO_NVSREADSTRING(mqtt_username) + MACRO_NVSREADSTRING(mqtt_password) + MACRO_NVSREADSTRING(influxdb_serverurl) + MACRO_NVSREADSTRING(influxdb_databasebucket) + MACRO_NVSREADSTRING(influxdb_apitoken) + MACRO_NVSREADSTRING(influxdb_orgid) + + MACRO_NVSREAD(absorptiontimer) + MACRO_NVSREAD(floatvoltage) + MACRO_NVSREAD(floatvoltagetimer) + MACRO_NVSREAD_UINT8(stateofchargeresumevalue) + + MACRO_NVSREADSTRING(homeassist_apikey) MACRO_NVSREAD(soh_total_milliamphour_out) MACRO_NVSREAD(soh_total_milliamphour_in) @@ -878,12 +878,12 @@ void ValidateConfiguration(diybms_eeprom_settings *settings) settings->baudRate = defaults.baudRate; } - if (settings->graph_voltagehigh > 5000 || settings->graph_voltagehigh < 2000 || settings->graph_voltagehigh < 0) + if (settings->graph_voltagehigh > 5000 || settings->graph_voltagehigh < 2000) { settings->graph_voltagehigh = defaults.graph_voltagehigh; } - if (settings->graph_voltagelow > settings->graph_voltagehigh || settings->graph_voltagelow < 0) + if (settings->graph_voltagelow > settings->graph_voltagehigh) { settings->graph_voltagelow = 0; } From 5426e6d13faec9c095db4bfddf9e02afd2c79822 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:05:57 +0000 Subject: [PATCH 10/39] Front end changes for SOH --- ESPController/src/main.cpp | 2 +- ESPController/src/webserver_json_requests.cpp | 8 +++++++ ESPController/web_src/default.htm | 22 ++++++++++++++++++- ESPController/web_src/pagecode.js | 6 +++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index dc438ce5..a7106bb2 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -3201,7 +3201,7 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c void CalculateStateOfHealth(diybms_eeprom_settings *settings) { // Value indicating what a typical discharge cycle looks like in amp-hours (normally 80% of cell for LFP) - float depth = 1000.0F * ((float)settings->currentMonitoring_batterycapacity/100.0F * (float)settings->soh_discharge_depth); + float depth = 1000.0F * ((float)settings->nominalbatcap/100.0F * (float)settings->soh_discharge_depth); float in = (float)settings->soh_total_milliamphour_in / depth; float out = (float)settings->soh_total_milliamphour_out / depth; //Take worst case diff --git a/ESPController/src/webserver_json_requests.cpp b/ESPController/src/webserver_json_requests.cpp index e922680d..351e3ba6 100644 --- a/ESPController/src/webserver_json_requests.cpp +++ b/ESPController/src/webserver_json_requests.cpp @@ -604,6 +604,14 @@ esp_err_t content_handler_chargeconfig(httpd_req_t *req) settings["canbusbaud"] = mysettings.canbusbaud; settings["equip_addr"] = mysettings.canbus_equipment_addr; settings["nominalbatcap"] = mysettings.nominalbatcap; + + settings["depthofdischarge"] = mysettings.soh_discharge_depth; + settings["expectedlifetime_cycles"] = mysettings.soh_lifetime_battery_cycles; + settings["total_mah_charge"] = mysettings.soh_total_milliamphour_in; + settings["total_mah_discharge"] = mysettings.soh_total_milliamphour_out; + settings["estimatebatterycycle"] = mysettings.soh_estimated_battery_cycles; + settings["stateofhealth"] = mysettings.soh_percent; + settings["chargevolt"] = mysettings.chargevolt; settings["chargecurrent"] = mysettings.chargecurrent; settings["dischargecurrent"] = mysettings.dischargecurrent; diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index 9bbb122b..eb772b49 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -654,7 +654,7 @@

History

Date/time Voltage Current - SoC% + SoC%% mA In mA Out Highest Range @@ -1034,6 +1034,26 @@

Charge/Discharge configuration

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +

Charging

diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index 09be33bf..b412a188 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -2003,6 +2003,12 @@ $(function () { $("#canbusbaud").val(data.chargeconfig.canbusbaud); $("#nominalbatcap").val(data.chargeconfig.nominalbatcap); + $("#expected_cycles").val(data.chargeconfig.expectedlifetime_cycles); + $("#total_ah_charge").val(Math.trunc(data.chargeconfig.total_mah_charge/1000)); + $("#total_ah_discharge").val(Math.trunc(data.chargeconfig.total_mah_discharge/1000)); + $("#estimate_bat_cycle").val(data.chargeconfig.estimatebatterycycle); + $("#stateofhealth").val((data.chargeconfig.stateofhealth).toFixed(2)); + $("#chargevolt").val((data.chargeconfig.chargevolt / 10.0).toFixed(1)); $("#chargecurrent").val((data.chargeconfig.chargecurrent / 10.0).toFixed(1)); $("#dischargecurrent").val((data.chargeconfig.dischargecurrent / 10.0).toFixed(1)); From a4abab162f741e3b895798c8efd66c5398e29ebe Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:14:59 +0000 Subject: [PATCH 11/39] ArduinoJson@^6.21.4 --- ESPController/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ESPController/platformio.ini b/ESPController/platformio.ini index e07c64dc..69431a4c 100644 --- a/ESPController/platformio.ini +++ b/ESPController/platformio.ini @@ -60,7 +60,7 @@ platform_packages = board_build.partitions = diybms_partitions.csv lib_deps = - bblanchon/ArduinoJson@^6.21.3 + bblanchon/ArduinoJson@^6.21.4 bodmer/TFT_eSPI@^2.5.31 https://github.com/stuartpittaway/SerialEncoder.git From f3f825a1b6715cc9dbad999044d2b8d0ee4d2be3 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:15:17 +0000 Subject: [PATCH 12/39] SOH config web page changes --- ESPController/include/webserver_json_post.h | 1 + ESPController/src/settings.cpp | 4 +++ ESPController/src/webserver_json_post.cpp | 33 ++++++++++++------- ESPController/src/webserver_json_requests.cpp | 2 +- ESPController/web_src/default.htm | 6 +++- ESPController/web_src/pagecode.js | 19 ++++++----- 6 files changed, 42 insertions(+), 23 deletions(-) diff --git a/ESPController/include/webserver_json_post.h b/ESPController/include/webserver_json_post.h index 2d10e5d9..623d88c3 100644 --- a/ESPController/include/webserver_json_post.h +++ b/ESPController/include/webserver_json_post.h @@ -39,6 +39,7 @@ extern void configureSNTP(long gmtOffset_sec, int daylightOffset_sec, const char extern void DefaultConfiguration(diybms_eeprom_settings *_myset); extern bool SaveWIFIJson(const wifi_eeprom_settings* setting); extern void randomCharacters(char *value, int length); +extern void CalculateStateOfHealth(diybms_eeprom_settings *settings); esp_err_t post_savebankconfig_json_handler(httpd_req_t *req, bool urlEncoded); esp_err_t post_saventp_json_handler(httpd_req_t *req, bool urlEncoded); diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index dd9b8654..bf9c817e 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -999,6 +999,10 @@ void ValidateConfiguration(diybms_eeprom_settings *settings) { settings->stateofchargeresumevalue = settings->stateofchargeresumevalue; } + + if (settings->soh_discharge_depth==0 || settings->soh_discharge_depth>100) { + settings->soh_discharge_depth=80; + } } // Builds up a JSON document which mirrors the parameters inside "diybms_eeprom_settings" diff --git a/ESPController/src/webserver_json_post.cpp b/ESPController/src/webserver_json_post.cpp index 045c5fe1..9bf6c853 100644 --- a/ESPController/src/webserver_json_post.cpp +++ b/ESPController/src/webserver_json_post.cpp @@ -578,6 +578,20 @@ esp_err_t post_savechargeconfig_json_handler(httpd_req_t *req, bool urlEncoded) mysettings.canbusinverter = CanBusInverter::INVERTER_GENERIC; } + GetKeyValue(httpbuf, "expected_cycles", &mysettings.soh_lifetime_battery_cycles, urlEncoded); + GetKeyValue(httpbuf, "dischargedepth", &mysettings.soh_discharge_depth, urlEncoded); + + if (GetKeyValue(httpbuf, "total_ah_discharge", &mysettings.soh_total_milliamphour_out, urlEncoded)) + { + mysettings.soh_total_milliamphour_out = mysettings.soh_total_milliamphour_out * 1000; + } + if (GetKeyValue(httpbuf, "total_ah_charge", &mysettings.soh_total_milliamphour_in, urlEncoded)) + { + mysettings.soh_total_milliamphour_in = mysettings.soh_total_milliamphour_in * 1000; + } + + CalculateStateOfHealth(&mysettings); + saveConfiguration(); return SendSuccess(req); @@ -643,9 +657,9 @@ esp_err_t post_savecmrelay_json_handler(httpd_req_t *req, bool urlEncoded) } /// @brief Generates new home assistant API key and stored into flash -/// @param req -/// @param urlEncoded -/// @return +/// @param req +/// @param urlEncoded +/// @return esp_err_t post_homeassistant_apikey_json_handler(httpd_req_t *req, bool urlEncoded) { char buffer[32]; @@ -1063,6 +1077,9 @@ esp_err_t post_saverules_json_handler(httpd_req_t *req, bool urlEncoded) esp_err_t post_restoreconfig_json_handler(httpd_req_t *req, bool urlEncoded) { + // Needs to be large enough to de-serialize the JSON file + DynamicJsonDocument doc(8000); + bool success = false; if (_avrsettings.programmingModeEnabled) @@ -1081,9 +1098,7 @@ esp_err_t post_restoreconfig_json_handler(httpd_req_t *req, bool urlEncoded) } uint16_t flashram = 0; - if (GetKeyValue(httpbuf, "flashram", &flashram, urlEncoded)) - { - } + GetKeyValue(httpbuf, "flashram", &flashram, urlEncoded); if (flashram == 0) { @@ -1098,9 +1113,6 @@ esp_err_t post_restoreconfig_json_handler(httpd_req_t *req, bool urlEncoded) { ESP_LOGI(TAG, "Restore SD config from %s", filename); - // Needs to be large enough to de-serialize the JSON file - DynamicJsonDocument doc(5000); - File file = SD.open(filename, "r"); // Deserialize the JSON document @@ -1143,9 +1155,6 @@ esp_err_t post_restoreconfig_json_handler(httpd_req_t *req, bool urlEncoded) { ESP_LOGI(TAG, "Restore LittleFS config from %s", filename); - // Needs to be large enough to de-serialize the JSON file - DynamicJsonDocument doc(5500); - File file = LittleFS.open(filename, "r"); // Deserialize the JSON document diff --git a/ESPController/src/webserver_json_requests.cpp b/ESPController/src/webserver_json_requests.cpp index 351e3ba6..9e132807 100644 --- a/ESPController/src/webserver_json_requests.cpp +++ b/ESPController/src/webserver_json_requests.cpp @@ -605,7 +605,7 @@ esp_err_t content_handler_chargeconfig(httpd_req_t *req) settings["equip_addr"] = mysettings.canbus_equipment_addr; settings["nominalbatcap"] = mysettings.nominalbatcap; - settings["depthofdischarge"] = mysettings.soh_discharge_depth; + settings["dischargedepth"] = mysettings.soh_discharge_depth; settings["expectedlifetime_cycles"] = mysettings.soh_lifetime_battery_cycles; settings["total_mah_charge"] = mysettings.soh_total_milliamphour_in; settings["total_mah_discharge"] = mysettings.soh_total_milliamphour_out; diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index eb772b49..1acb9aeb 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -1035,9 +1035,13 @@

Charge/Discharge configuration

- +
+
+ + +
diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index b412a188..c2d035ad 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -1613,17 +1613,17 @@ $(function () { $("#networkForm").show(); - + $("#rssi_now").val(data.wifi.rssi); $("#bssid").val(data.wifi.bssid); $("#ssid").val(data.wifi.ssid); - + $("#rssi_low").val(data.wifi.rssi_low); $("#sta_start").val(data.wifi.sta_start); $("#sta_connected").val(data.wifi.sta_connected); $("#sta_disconnected").val(data.wifi.sta_disconnected); $("#sta_lost_ip").val(data.wifi.sta_lost_ip); - $("#sta_got_ip").val(data.wifi.sta_got_ip); + $("#sta_got_ip").val(data.wifi.sta_got_ip); }).fail(function () { $.notify("Request failed", { autoHide: true, globalPosition: 'top right', className: 'error' }); } ); @@ -1766,8 +1766,8 @@ $(function () { $("#influxOrgId").val(data.influxdb.orgid); $("#influxFreq").val(data.influxdb.frequency); - $("#haUrl").val(window.location.origin+"/ha"); - $("#haAPI").val(data.ha.api); + $("#haUrl").val(window.location.origin + "/ha"); + $("#haAPI").val(data.ha.api); $("#haForm").show(); $("#mqttForm").show(); @@ -2004,8 +2004,9 @@ $(function () { $("#nominalbatcap").val(data.chargeconfig.nominalbatcap); $("#expected_cycles").val(data.chargeconfig.expectedlifetime_cycles); - $("#total_ah_charge").val(Math.trunc(data.chargeconfig.total_mah_charge/1000)); - $("#total_ah_discharge").val(Math.trunc(data.chargeconfig.total_mah_discharge/1000)); + $("#dischargedepth").val(data.chargeconfig.dischargedepth); + $("#total_ah_charge").val(Math.trunc(data.chargeconfig.total_mah_charge / 1000)); + $("#total_ah_discharge").val(Math.trunc(data.chargeconfig.total_mah_discharge / 1000)); $("#estimate_bat_cycle").val(data.chargeconfig.estimatebatterycycle); $("#stateofhealth").val((data.chargeconfig.stateofhealth).toFixed(2)); @@ -2135,7 +2136,7 @@ $(function () { }, }); }); - + $("#haForm").unbind('submit').submit(function (e) { e.preventDefault(); @@ -2152,7 +2153,7 @@ $(function () { }, }); }); - + $("#rulesForm").unbind('submit').submit(function (e) { e.preventDefault(); From db0e666c33af1a1447f36f7317aca1842f6caf85 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Wed, 27 Dec 2023 11:56:47 +0000 Subject: [PATCH 13/39] Add watchdog to error LED flashes and use highest of the 3 temp sensors --- STM32All-In-One/src/main.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/STM32All-In-One/src/main.cpp b/STM32All-In-One/src/main.cpp index 6f002102..2f0e727c 100644 --- a/STM32All-In-One/src/main.cpp +++ b/STM32All-In-One/src/main.cpp @@ -733,6 +733,7 @@ void TakeOnboardInternalTempMeasurements(CellData &cd) // Spread the two internal temperature sensor values across all 16 cells (even though some may not be connected) // Odd cells are on the left of the balance board, near TH1/T1, even on right side TH2/T2 + // but this doesn't matter, as we use the highest recorded temperature from either sensor in safety checks for (size_t i = 0; i < 16; i += 2) { cd.at(i).setInternalTemperature(t1); @@ -763,6 +764,9 @@ void TakeOnboardInternalTempMeasurements(CellData &cd) delay(500); while (true) { + // make sure the code in this loop is executed in less than 2 seconds to leave 50% headroom for the timer reload. + IWatchdog.reload(); + for (size_t i = 0; i < number; i++) { NotificationLedOn(); @@ -771,6 +775,8 @@ void TakeOnboardInternalTempMeasurements(CellData &cd) delay(220); } + // make sure the code in this loop is executed in less than 2 seconds to leave 50% headroom for the timer reload. + IWatchdog.reload(); delay(2000); } } @@ -1029,7 +1035,9 @@ void loop() // Now process the ADC readings we have taken... DecimateRawADCCellVoltage(rawADC, celldata, number_of_active_cells); + // Three temperature sensors are recorded over cells 0/1/2, use the highest value auto highestTemp = max(celldata.at(0).getInternalTemperature(), celldata.at(1).getInternalTemperature()); + highestTemp = max(celldata.at(2).getInternalTemperature(), highestTemp); // If fan timer has expired, switch off FAN // This has the side effect of the fan switching off and on (not physically noticable) should the temperature still be too hot. From cbe2f3314cf6ac9e3db3e1cdb27aa386e6facbcc Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:47:00 +0000 Subject: [PATCH 14/39] Increase GZIP compression level --- ESPController/prebuild_compress.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ESPController/prebuild_compress.py b/ESPController/prebuild_compress.py index e63fa8e0..b24703c4 100644 --- a/ESPController/prebuild_compress.py +++ b/ESPController/prebuild_compress.py @@ -13,9 +13,9 @@ def prepare_www_files(): # WARNING - this script will DELETE your 'data' dir and recreate an empty one to copy/gzip files from 'data_src' - # so make sure to edit your files in 'data_src' folder as changes madt to files in 'data' woll be LOST + # so make sure to edit your files in 'data_src' folder as changes madt to files in 'data' will be LOST # - # If 'data_src' dir doesn't exist, and 'data' dir is found, the script will autimatically + # If 'data_src' dir doesn't exist, and 'data' dir is found, the script will automatically # rename 'data' to 'data_src # add filetypes (extensions only) to be gzipped before uploading. Everything else will be copied directly @@ -65,7 +65,7 @@ def prepare_www_files(): for file in files_to_gzip: print(' GZipping file: ' + file + ' to data dir') - with open(file, 'rb') as f_in, gzip.open(os.path.join(data_dir, os.path.basename(file) + '.gz'), 'wb') as f_out: + with open(file, 'rb') as f_in, gzip.GzipFile(filename=os.path.join(data_dir, os.path.basename(file) + '.gz'), mode='w', compresslevel=9) as f_out: shutil.copyfileobj(f_in, f_out) print('[/COPY/GZIP DATA FILES]') From eb95ebfdd4dc701ae1505a32ca704113b74e7e6f Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 6 Feb 2024 12:43:23 +0000 Subject: [PATCH 15/39] Experimental fixes for #276 --- ESPController/src/main.cpp | 278 ++++++++++++++++---------------- ESPController/src/webserver.cpp | 3 +- 2 files changed, 145 insertions(+), 136 deletions(-) diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index 3a5dbc3e..9670c8f8 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -1554,6 +1554,7 @@ void configureSNTP(long gmtOffset_sec, int daylightOffset_sec, const char *serve static void stopMDNS() { + ESP_LOGI(TAG, "stop mdns"); mdns_free(); } @@ -1576,13 +1577,19 @@ static void startMDNS() void ShutdownAllNetworkServices() { + ESP_LOGI(TAG, "ShutdownAllNetworkServices"); // Shut down all TCP/IP reliant services if (server_running) { - stop_webserver(_myserver); + if (_myserver != nullptr) + { + stop_webserver(_myserver); + _myserver = nullptr; + } + server_running = false; - _myserver = nullptr; } + stopMqtt(); stopMDNS(); } @@ -2771,7 +2778,7 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c // Delay a little whilst sending packets to give ESP32 some breathing room and not flood the CANBUS // vTaskDelay(pdMS_TO_TICKS(100)); } - else if (mysettings.protocol == ProtocolEmulation::CANBUS_PYLONFORCEH2 ) + else if (mysettings.protocol == ProtocolEmulation::CANBUS_PYLONFORCEH2) { pylonforce_handle_tx(); } @@ -2829,8 +2836,8 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c if (!(message.flags & TWAI_MSG_FLAG_RTR)) // we do not answer to Remote-Transmission-Requests { -// ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG); - if (mysettings.protocol == ProtocolEmulation::CANBUS_PYLONFORCEH2 ) + // ESP_LOG_BUFFER_HEXDUMP(TAG, message.data, message.data_length_code, ESP_LOG_DEBUG); + if (mysettings.protocol == ProtocolEmulation::CANBUS_PYLONFORCEH2) { pylonforce_handle_rx(&message); } @@ -2927,152 +2934,152 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c // If Pylon Tech RS485 protocol emulation is enabled, MODBUS current shunt can't be used if (mysettings.protocol == ProtocolEmulation::RS485_PYLONTECH) { - // For this protocol emulation, RS485 on diyBMS acts as a slave, waiting for queries from the inverter - pylon_rs485.handle_rx(); + // For this protocol emulation, RS485 on diyBMS acts as a slave, waiting for queries from the inverter + pylon_rs485.handle_rx(); } else { - // Original RS485 RX handler, RS485 port on diyBMS acts as a master - // Wait until this task is triggered (sending queue task triggers it, or save of savechargeconfig form) - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + // Original RS485 RX handler, RS485 port on diyBMS acts as a master + // Wait until this task is triggered (sending queue task triggers it, or save of savechargeconfig form) + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - // Delay 50ms for the data to arrive - vTaskDelay(pdMS_TO_TICKS(50)); + // Delay 50ms for the data to arrive + vTaskDelay(pdMS_TO_TICKS(50)); - uint16_t len = 0; + uint16_t len = 0; - if (hal.GetRS485Mutex()) - { - // Wait 200ms before timeout - len = (uint16_t)uart_read_bytes(rs485_uart_num, frame, sizeof(frame), pdMS_TO_TICKS(200)); - hal.ReleaseRS485Mutex(); - } + if (hal.GetRS485Mutex()) + { + // Wait 200ms before timeout + len = (uint16_t)uart_read_bytes(rs485_uart_num, frame, sizeof(frame), pdMS_TO_TICKS(200)); + hal.ReleaseRS485Mutex(); + } - // Min packet length of 5 bytes - if (len > 5) - { - uint8_t id = frame[0]; + // Min packet length of 5 bytes + if (len > 5) + { + uint8_t id = frame[0]; - auto crc = (uint16_t)((frame[len - 2] << 8) | frame[len - 1]); // combine the crc Low & High bytes + auto crc = (uint16_t)((frame[len - 2] << 8) | frame[len - 1]); // combine the crc Low & High bytes - auto temp = calculateCRC(frame, (uint8_t)(len - 2)); - // Swap bytes to match MODBUS ordering - auto calculatedCRC = (uint16_t)(temp << 8) | (uint16_t)(temp >> 8); + auto temp = calculateCRC(frame, (uint8_t)(len - 2)); + // Swap bytes to match MODBUS ordering + auto calculatedCRC = (uint16_t)(temp << 8) | (uint16_t)(temp >> 8); + + // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + + if (calculatedCRC == crc) + { + // if the calculated crc matches the recieved crc continue to process data... + uint8_t RS485Error = frame[1] & B10000000; + if (RS485Error == 0) + { + uint8_t cmd = frame[1] & B01111111; + uint8_t length = frame[2]; + ESP_LOGD(TAG, "Recv %i bytes, id=%u, cmd=%u", len, id, cmd); // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - if (calculatedCRC == crc) + if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::PZEM_017) { - // if the calculated crc matches the recieved crc continue to process data... - uint8_t RS485Error = frame[1] & B10000000; - if (RS485Error == 0) + if (cmd == 6 && id == 248) + { + ESP_LOGI(TAG, "Reply to broadcast/change address"); + } + if (cmd == 6 && id == mysettings.currentMonitoringModBusAddress) + { + ESP_LOGI(TAG, "Reply to set param"); + } + else if (cmd == 3 && id == mysettings.currentMonitoringModBusAddress) + { + // 75mV shunt (hard coded for PZEM) + currentMonitor.modbus.shuntmillivolt = 75; + + // Shunt type 0x0000 - 0x0003 (100A/50A/200A/300A) + switch (((uint32_t)frame[9] << 8 | (uint32_t)frame[10])) { - uint8_t cmd = frame[1] & B01111111; - uint8_t length = frame[2]; - - ESP_LOGD(TAG, "Recv %i bytes, id=%u, cmd=%u", len, id, cmd); - // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - - if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::PZEM_017) - { - if (cmd == 6 && id == 248) - { - ESP_LOGI(TAG, "Reply to broadcast/change address"); - } - if (cmd == 6 && id == mysettings.currentMonitoringModBusAddress) - { - ESP_LOGI(TAG, "Reply to set param"); - } - else if (cmd == 3 && id == mysettings.currentMonitoringModBusAddress) - { - // 75mV shunt (hard coded for PZEM) - currentMonitor.modbus.shuntmillivolt = 75; - - // Shunt type 0x0000 - 0x0003 (100A/50A/200A/300A) - switch (((uint32_t)frame[9] << 8 | (uint32_t)frame[10])) - { - case 0: - currentMonitor.modbus.shuntmaxcurrent = 100; - break; - case 1: - currentMonitor.modbus.shuntmaxcurrent = 50; - break; - case 2: - currentMonitor.modbus.shuntmaxcurrent = 200; - break; - case 3: - currentMonitor.modbus.shuntmaxcurrent = 300; - break; - default: - currentMonitor.modbus.shuntmaxcurrent = 0; - } - } - else if (cmd == 4 && id == mysettings.currentMonitoringModBusAddress && len == 21) - { - // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - - // memset(¤tMonitor.modbus, 0, sizeof(currentmonitor_raw_modbus)); - currentMonitor.validReadings = true; - currentMonitor.timestamp = esp_timer_get_time(); - // voltage in 0.01V - currentMonitor.modbus.voltage = (float)((uint32_t)frame[3] << 8 | (uint32_t)frame[4]) / (float)100.0; - // current in 0.01A - currentMonitor.modbus.current = (float)((uint32_t)frame[5] << 8 | (uint32_t)frame[6]) / (float)100.0; - // power in 0.1W - currentMonitor.modbus.power = ((uint32_t)frame[7] << 8 | (uint32_t)frame[8] | (uint32_t)frame[9] << 24 | (uint32_t)frame[10] << 16) / 10.0F; - } - else - { - // Dump out unhandled reply - ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - } - } - // ESP_LOGD(TAG, "CRC pass Id=%u F=%u L=%u", id, cmd, length); - if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_MODBUS) - { - if (id == mysettings.currentMonitoringModBusAddress && cmd == 3) - { - ProcessDIYBMSCurrentMonitorRegisterReply(length); - - if (_tft_screen_available) - { - // Refresh the TFT display - xTaskNotify(updatetftdisplay_task_handle, 0x00, eNotifyAction::eNoAction); - } - } - else if (id == mysettings.currentMonitoringModBusAddress && cmd == 16) - { - ESP_LOGI(TAG, "Write multiple regs, success"); - } - else - { - // Dump out unhandled reply - ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); - } - } - } - else - { - ESP_LOGE(TAG, "RS485 error"); - ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + case 0: + currentMonitor.modbus.shuntmaxcurrent = 100; + break; + case 1: + currentMonitor.modbus.shuntmaxcurrent = 50; + break; + case 2: + currentMonitor.modbus.shuntmaxcurrent = 200; + break; + case 3: + currentMonitor.modbus.shuntmaxcurrent = 300; + break; + default: + currentMonitor.modbus.shuntmaxcurrent = 0; } + } + else if (cmd == 4 && id == mysettings.currentMonitoringModBusAddress && len == 21) + { + // ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + + // memset(¤tMonitor.modbus, 0, sizeof(currentmonitor_raw_modbus)); + currentMonitor.validReadings = true; + currentMonitor.timestamp = esp_timer_get_time(); + // voltage in 0.01V + currentMonitor.modbus.voltage = (float)((uint32_t)frame[3] << 8 | (uint32_t)frame[4]) / (float)100.0; + // current in 0.01A + currentMonitor.modbus.current = (float)((uint32_t)frame[5] << 8 | (uint32_t)frame[6]) / (float)100.0; + // power in 0.1W + currentMonitor.modbus.power = ((uint32_t)frame[7] << 8 | (uint32_t)frame[8] | (uint32_t)frame[9] << 24 | (uint32_t)frame[10] << 16) / 10.0F; + } + else + { + // Dump out unhandled reply + ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + } } - else + // ESP_LOGD(TAG, "CRC pass Id=%u F=%u L=%u", id, cmd, length); + if (mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_MODBUS) { - ESP_LOGE(TAG, "CRC error"); + if (id == mysettings.currentMonitoringModBusAddress && cmd == 3) + { + ProcessDIYBMSCurrentMonitorRegisterReply(length); + + if (_tft_screen_available) + { + // Refresh the TFT display + xTaskNotify(updatetftdisplay_task_handle, 0x00, eNotifyAction::eNoAction); + } + } + else if (id == mysettings.currentMonitoringModBusAddress && cmd == 16) + { + ESP_LOGI(TAG, "Write multiple regs, success"); + } + else + { + // Dump out unhandled reply + ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + } } + } + else + { + ESP_LOGE(TAG, "RS485 error"); + ESP_LOG_BUFFER_HEXDUMP(TAG, frame, len, esp_log_level_t::ESP_LOG_DEBUG); + } } else { - // We didn't receive anything on RS485, record error and mark current monitor as invalid - ESP_LOGE(TAG, "Short packet %i bytes", len); - - // Indicate that the current monitor values are now invalid/unknown - currentMonitor.validReadings = false; + ESP_LOGE(TAG, "CRC error"); } + } + else + { + // We didn't receive anything on RS485, record error and mark current monitor as invalid + ESP_LOGE(TAG, "Short packet %i bytes", len); + + // Indicate that the current monitor values are now invalid/unknown + currentMonitor.validReadings = false; + } - // Notify sending queue, to continue - xTaskNotify(service_rs485_transmit_q_task_handle, 0x00, eNotifyAction::eNoAction); + // Notify sending queue, to continue + xTaskNotify(service_rs485_transmit_q_task_handle, 0x00, eNotifyAction::eNoAction); } } // infinite loop } @@ -3214,15 +3221,15 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c void CalculateStateOfHealth(diybms_eeprom_settings *settings) { // Value indicating what a typical discharge cycle looks like in amp-hours (normally 80% of cell for LFP) - float depth = 1000.0F * ((float)settings->nominalbatcap/100.0F * (float)settings->soh_discharge_depth); + float depth = 1000.0F * ((float)settings->nominalbatcap / 100.0F * (float)settings->soh_discharge_depth); float in = (float)settings->soh_total_milliamphour_in / depth; float out = (float)settings->soh_total_milliamphour_out / depth; - //Take worst case + // Take worst case float cycles = max(in, out); - settings->soh_percent =100-((cycles / (float)settings->soh_lifetime_battery_cycles) * 100.0F); + settings->soh_percent = 100 - ((cycles / (float)settings->soh_lifetime_battery_cycles) * 100.0F); - settings->soh_estimated_battery_cycles=(uint16_t)round(cycles); + settings->soh_estimated_battery_cycles = (uint16_t)round(cycles); ESP_LOGI(TAG, "State of health calc %f %, estimated cycles=%f", settings->soh_percent, cycles); } @@ -3710,14 +3717,14 @@ const std::array log_levels = {.tag = "diybms-tx", .level = ESP_LOG_INFO}, {.tag = "diybms-rules", .level = ESP_LOG_INFO}, {.tag = "diybms-softap", .level = ESP_LOG_INFO}, - {.tag = "diybms-tft", .level = ESP_LOG_INFO}, + {.tag = "diybms-tft", .level = ESP_LOG_WARN}, {.tag = "diybms-victron", .level = ESP_LOG_INFO}, {.tag = "diybms-webfuncs", .level = ESP_LOG_INFO}, {.tag = "diybms-webpost", .level = ESP_LOG_INFO}, {.tag = "diybms-webreq", .level = ESP_LOG_INFO}, {.tag = "diybms-web", .level = ESP_LOG_INFO}, {.tag = "diybms-set", .level = ESP_LOG_INFO}, - {.tag = "diybms-mqtt", .level = ESP_LOG_INFO}, + {.tag = "diybms-mqtt", .level = ESP_LOG_DEBUG}, {.tag = "diybms-pylon", .level = ESP_LOG_INFO}, {.tag = "diybms-pyforce", .level = ESP_LOG_INFO}, {.tag = "curmon", .level = ESP_LOG_INFO}}; @@ -4181,8 +4188,9 @@ void loop() // Attempt to connect to MQTT if enabled and not already connected connectToMqtt(); } - else + else if (wifi_ap_connect_retry_num >= 25) { + // Try to reconnect WIFI every 30 seconds, if we have exhausted the first 25 "quick" attempts ESP_LOGI(TAG, "Trying to connect WIFI"); ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect()); } diff --git a/ESPController/src/webserver.cpp b/ESPController/src/webserver.cpp index d31ec5d0..b398a892 100644 --- a/ESPController/src/webserver.cpp +++ b/ESPController/src/webserver.cpp @@ -668,9 +668,10 @@ httpd_handle_t start_webserver(void) /* Function for stopping the webserver */ void stop_webserver(httpd_handle_t server) { - if (server) + if (server!=nullptr) { /* Stop the httpd server */ + ESP_LOGI(TAG, "httpd_stop"); httpd_stop(server); } } From d6e9c48ca48867e2c9161553a55a59d8c44ac9b5 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:39:17 +0000 Subject: [PATCH 16/39] Update framework version and new check for INA229 --- ESP32BoardTest/platformio.ini | 11 ++--- ESP32BoardTest/src/main.cpp | 78 ++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/ESP32BoardTest/platformio.ini b/ESP32BoardTest/platformio.ini index d2959cc0..b92813be 100644 --- a/ESP32BoardTest/platformio.ini +++ b/ESP32BoardTest/platformio.ini @@ -36,25 +36,22 @@ build_flags = [env] framework = arduino ; 4MB FLASH DEVKITC -platform = espressif32 -;platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream +platform = espressif32@~6.4.0 board = esp32dev monitor_speed = 115200 -monitor_port=COM4 +monitor_port=COM3 monitor_filters = log2file, esp32_exception_decoder board_build.flash_mode = dout board_build.filesystem = littlefs extra_scripts = pre:buildscript_versioning.py - - upload_speed=921600 -upload_port=COM4 +upload_port=COM3 platform_packages = - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.6 + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.14 board_build.partitions = diybms_partitions.csv diff --git a/ESP32BoardTest/src/main.cpp b/ESP32BoardTest/src/main.cpp index 5391b167..cf932fa3 100644 --- a/ESP32BoardTest/src/main.cpp +++ b/ESP32BoardTest/src/main.cpp @@ -87,6 +87,9 @@ static constexpr const char *const TAG = "diybms"; #define RS485_TX GPIO_NUM_22 #define RS485_ENABLE GPIO_NUM_25 +#define INA229_CHIPSELECT GPIO_NUM_33 +#define INA229_INTERRUPT_PIN GPIO_NUM_35 + enum RGBLED : uint8_t { OFF = 0, @@ -605,7 +608,7 @@ void testSerial() delay(10); ESP_LOGI(TAG, "Test serial TX1/RX1"); - for (size_t i = 0; i < 50; i++) + for (size_t i = 0; i < 20; i++) { SERIAL_DATA.println("test!"); @@ -623,6 +626,65 @@ void testSerial() } } + enum INA_REGISTER : uint8_t + { + CONFIG = 0, + ADC_CONFIG = 1, + // Shunt Calibration + SHUNT_CAL = 2, + // Shunt Temperature Coefficient + SHUNT_TEMPCO = 3, + // Shunt Voltage Measurement 24bit + VSHUNT = 4, + // Bus Voltage Measurement 24bit + VBUS = 5, + DIETEMP = 6, + // Current Result 24bit + CURRENT = 7, + // Power Result 24bit + POWER = 8, + // Energy Result 40bit + ENERGY = 9, + // Charge Result 40bit + CHARGE = 0x0A, + // Alert triggers + DIAG_ALRT = 0x0b, + // Shunt Overvoltage Threshold + // overcurrent protection + SOVL = 0x0c, + // Shunt Undervoltage Threshold + // undercurrent protection + SUVL = 0x0d, + // Bus Overvoltage Threshold + BOVL = 0x0e, + // Bus Undervoltage Threshold + BUVL = 0x0f, + // Temperature Over-Limit Threshold + TEMP_LIMIT = 0x10, + // Power Over-Limit Threshold + PWR_LIMIT = 0x11, + // Manufacturer ID + MANUFACTURER_ID = 0x3E, + // Device ID + DEVICE_ID = 0x3F + + }; + +uint16_t read16bits(INA_REGISTER r) +{ + SPISettings _spisettings = SPISettings(10000000, MSBFIRST, SPI_MODE1); + vspi.beginTransaction(_spisettings); + digitalWrite(INA229_CHIPSELECT, LOW); + // The transfers are always a step behind, so the transfer reads the previous value/command + vspi.write((uint8_t)((r << 2U) | B00000001)); + uint16_t value = vspi.transfer16(0); + digitalWrite(INA229_CHIPSELECT, HIGH); + vspi.endTransaction(); + + ESP_LOGD(TAG, "Read register 0x%02x = 0x%04x", r, value); + return value; +} + void setup() { // We are not testing ESP32, so switch off WIFI + BT @@ -638,6 +700,18 @@ void setup() ConfigureI2C(); ConfigureVSPI(); + + uint16_t value = read16bits(INA_REGISTER::DEVICE_ID); + if ((value >> 4) == 0x229) + { + ESP_LOGI(TAG, "FOUND CURRENT SHUNT CHIP INA%02x, Revision=%u", value >> 4, value & B1111); + } + else + { + // Stop here - no chip found + ESP_LOGW(TAG, "** CURRENT SHUNT CHIP INA229 CHIP ABSENT **"); + } + mountSDCard(); // Create a test file @@ -681,7 +755,7 @@ void setup() Led(RGBLED::OFF); // Test SERIAL - SERIAL_DATA.begin(2400, SERIAL_8N1, 2, 32); // Serial for comms to modules + SERIAL_DATA.begin(5000, SERIAL_8N1, 2, 32); // Serial for comms to modules testSerial(); init_tft_display(); From 0d1e861a4122a336fe688628c1b6b7f9430ddf8c Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:03:25 +0000 Subject: [PATCH 17/39] Mandatory code changes for ArduinoJson 7 --- ESPController/include/settings.h | 4 +- ESPController/platformio.ini | 4 +- ESPController/src/main.cpp | 24 +++++----- ESPController/src/settings.cpp | 24 +++++----- ESPController/src/webserver_json_post.cpp | 11 +++-- ESPController/src/webserver_json_requests.cpp | 44 +++++++++---------- 6 files changed, 55 insertions(+), 56 deletions(-) diff --git a/ESPController/include/settings.h b/ESPController/include/settings.h index 429236bf..e0c68629 100644 --- a/ESPController/include/settings.h +++ b/ESPController/include/settings.h @@ -30,8 +30,8 @@ void DefaultConfiguration(diybms_eeprom_settings *settings); void SaveWIFI(const wifi_eeprom_settings *wifi); bool LoadWIFI(wifi_eeprom_settings *wifi); -void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settings *settings); -void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings); +void GenerateSettingsJSONDocument(JsonDocument &doc, diybms_eeprom_settings *settings); +void JSONToSettings(JsonDocument &doc, diybms_eeprom_settings *settings); void writeSetting(nvs_handle_t handle, const char *key, bool value); void writeSetting(nvs_handle_t handle, const char *key, uint8_t value); diff --git a/ESPController/platformio.ini b/ESPController/platformio.ini index 69431a4c..3c1f70df 100644 --- a/ESPController/platformio.ini +++ b/ESPController/platformio.ini @@ -35,7 +35,7 @@ build_flags = [env] framework = arduino ; 4MB FLASH DEVKITC -platform = espressif32@~6.4.0 +platform = espressif32@~6.5.0 board = esp32dev monitor_speed = 115200 @@ -60,7 +60,7 @@ platform_packages = board_build.partitions = diybms_partitions.csv lib_deps = - bblanchon/ArduinoJson@^6.21.4 + bblanchon/ArduinoJson@^7.0.3 bodmer/TFT_eSPI@^2.5.31 https://github.com/stuartpittaway/SerialEncoder.git diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index 9670c8f8..f655d877 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -1730,9 +1730,9 @@ bool SaveWIFIJson(const wifi_eeprom_settings *setting) return false; } - StaticJsonDocument<512> doc; + JsonDocument doc; - JsonObject wifi = doc.createNestedObject("wifi"); + JsonObject wifi = doc["wifi"].to(); wifi["ssid"] = setting->wifi_ssid; wifi["password"] = setting->wifi_passphrase; @@ -3597,7 +3597,7 @@ bool AreWifiConfigurationsTheSame(const wifi_eeprom_settings *a, const wifi_eepr /// @return TRUE if the WIFI config on the card is different, false if identical bool LoadWiFiConfigFromSDCard(const bool existingConfigValid) { - StaticJsonDocument<2048> json; + JsonDocument json; DeserializationError error; if (!_sd_card_installed) @@ -4025,7 +4025,7 @@ void ESPCoreDumpToJSON(JsonObject &doc) { if (esp_core_dump_image_check() == ESP_OK) { - JsonObject core = doc.createNestedObject("coredump"); + JsonObject core = doc["coredump"].to(); // A valid core dump is in FLASH storage esp_core_dump_summary_t *summary = (esp_core_dump_summary_t *)malloc(sizeof(esp_core_dump_summary_t)); @@ -4047,7 +4047,7 @@ void ESPCoreDumpToJSON(JsonObject &doc) core["bt_corrupted"] = summary->exc_bt_info.corrupted; core["bt_depth"] = summary->exc_bt_info.depth; - auto backtrace = core.createNestedArray("backtrace"); + auto backtrace = core["backtrace"].to(); for (auto value : summary->exc_bt_info.bt) { ultoa(value, outputString, 16); @@ -4061,13 +4061,13 @@ void ESPCoreDumpToJSON(JsonObject &doc) ultoa(summary->ex_info.exc_vaddr, outputString, 16); core["exc_vaddr"] = outputString; - auto exc_a = core.createNestedArray("exc_a"); + auto exc_a = core["exc_a"].to();; for (auto value : summary->ex_info.exc_a) { ultoa(value, outputString, 16); exc_a.add(outputString); } - auto epcx = core.createNestedArray("epcx"); + auto epcx = core["epcx"].to();; for (auto value : summary->ex_info.epcx) { ultoa(value, outputString, 16); @@ -4086,12 +4086,12 @@ void ESPCoreDumpToJSON(JsonObject &doc) /// @return esp_err_t diagnosticJSON(httpd_req_t *req, char buffer[], int bufferLenMax) { - DynamicJsonDocument doc(2048); + JsonDocument doc; JsonObject root = doc.to(); - JsonObject diag = root.createNestedObject("diagnostic"); + JsonObject diag = root["diagnostic"].to(); diag["numtasks"] = uxTaskGetNumberOfTasks(); - auto tasks = diag.createNestedArray("tasks"); + auto tasks = diag["tasks"].to(); // Array of pointers to the task handles we are going to examine const std::array task_handle_ptrs = @@ -4107,7 +4107,7 @@ esp_err_t diagnosticJSON(httpd_req_t *req, char buffer[], int bufferLenMax) { if (*h != nullptr) { - JsonObject nested = tasks.createNestedObject(); + JsonObject nested = tasks.add(); nested["name"] = pcTaskGetName(*h); nested["hwm"] = uxTaskGetStackHighWaterMark(*h); } @@ -4123,7 +4123,7 @@ esp_err_t diagnosticJSON(httpd_req_t *req, char buffer[], int bufferLenMax) { if (h != nullptr) { - JsonObject nested = tasks.createNestedObject(); + JsonObject nested = tasks.add(); nested["name"] = pcTaskGetName(h); nested["hwm"] = uxTaskGetStackHighWaterMark(h); } diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index 274956c9..b1258686 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -1010,9 +1010,9 @@ void ValidateConfiguration(diybms_eeprom_settings *settings) } // Builds up a JSON document which mirrors the parameters inside "diybms_eeprom_settings" -void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settings *settings) +void GenerateSettingsJSONDocument(JsonDocument &doc, diybms_eeprom_settings *settings) { - JsonObject root = doc->createNestedObject("diybms_settings"); + JsonObject root = doc["diybms_settings"].to(); root[totalNumberOfBanks_JSONKEY] = settings->totalNumberOfBanks; root[totalNumberOfSeriesModules_JSONKEY] = settings->totalNumberOfSeriesModules; @@ -1057,7 +1057,7 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin root[homeassist_apikey_JSONKEY] = settings->homeassist_apikey; - JsonObject mqtt = root.createNestedObject("mqtt"); + JsonObject mqtt = root["mqtt"].to(); mqtt[mqtt_enabled_JSONKEY] = settings->mqtt_enabled; mqtt[mqtt_basic_cell_reporting_JSONKEY] = settings->mqtt_basic_cell_reporting; mqtt[mqtt_uri_JSONKEY] = settings->mqtt_uri; @@ -1065,7 +1065,7 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin mqtt[mqtt_username_JSONKEY] = settings->mqtt_username; mqtt[mqtt_password_JSONKEY] = settings->mqtt_password; - JsonObject influxdb = root.createNestedObject("influxdb"); + JsonObject influxdb = root["influxdb"].to(); influxdb[influxdb_enabled_JSONKEY] = settings->influxdb_enabled; influxdb[influxdb_apitoken_JSONKEY] = settings->influxdb_apitoken; influxdb[influxdb_databasebucket_JSONKEY] = settings->influxdb_databasebucket; @@ -1073,16 +1073,16 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin influxdb[influxdb_serverurl_JSONKEY] = settings->influxdb_serverurl; influxdb[influxdb_loggingFreqSeconds_JSONKEY] = settings->influxdb_loggingFreqSeconds; - JsonObject outputs = root.createNestedObject("outputs"); - JsonArray d = outputs.createNestedArray("default"); - JsonArray t = outputs.createNestedArray("type"); + JsonObject outputs = root["outputs"].to(); + JsonArray d = outputs["default"].to();; + JsonArray t = outputs["type"].to();; for (uint8_t i = 0; i < RELAY_TOTAL; i++) { d.add(settings->rulerelaydefault[i]); t.add(settings->relaytype[i]); } - JsonObject rules = root.createNestedObject("rules"); + JsonObject rules = root["rules"].to(); for (uint8_t rr = 0; rr < RELAY_RULES; rr++) { // This is a default "catch all" @@ -1099,12 +1099,12 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin ESP_LOGE(TAG, "Loop outside bounds of MAXIMUM_RuleNumber"); } - JsonObject state = rules.createNestedObject(elementName); + JsonObject state = rules[elementName].to(); state["value"] = settings->rulevalue[rr]; state["hysteresis"] = settings->rulehysteresis[rr]; - JsonArray relaystate = state.createNestedArray("state"); + JsonArray relaystate = state["state"].to();; for (uint8_t rt = 0; rt < RELAY_TOTAL; rt++) { relaystate.add(settings->rulerelaystate[rr][rt]); @@ -1146,7 +1146,7 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin root[floatvoltagetimer_JSONKEY] = settings->floatvoltagetimer; root[stateofchargeresumevalue_JSONKEY] = settings->stateofchargeresumevalue; - JsonArray tv = root.createNestedArray("tilevisibility"); + JsonArray tv = root["tilevisibility"].to(); for (uint8_t i = 0; i < sizeof(settings->tileconfig) / sizeof(uint16_t); i++) { tv.add(settings->tileconfig[i]); @@ -1160,7 +1160,7 @@ void GenerateSettingsJSONDocument(DynamicJsonDocument *doc, diybms_eeprom_settin root[soh_discharge_depth_JSONKEY] = settings->soh_discharge_depth; } -void JSONToSettings(DynamicJsonDocument &doc, diybms_eeprom_settings *settings) +void JSONToSettings(JsonDocument &doc, diybms_eeprom_settings *settings) { // Use defaults to populate the settings, just in case we are missing values from the JSON DefaultConfiguration(settings); diff --git a/ESPController/src/webserver_json_post.cpp b/ESPController/src/webserver_json_post.cpp index 836221ad..f397827a 100644 --- a/ESPController/src/webserver_json_post.cpp +++ b/ESPController/src/webserver_json_post.cpp @@ -188,8 +188,8 @@ esp_err_t post_saveinfluxdbsetting_json_handler(httpd_req_t *req, bool urlEncode // Saves all the BMS controller settings to a JSON file in FLASH esp_err_t post_saveconfigurationtoflash_json_handler(httpd_req_t *req, bool urlEncoded) { - DynamicJsonDocument doc(5000); - GenerateSettingsJSONDocument(&doc, &mysettings); + JsonDocument doc; + GenerateSettingsJSONDocument(doc, &mysettings); struct tm timeinfo; @@ -876,7 +876,7 @@ esp_err_t post_avrprog_json_handler(httpd_req_t *req, bool urlEncoded) return SendFailure(req); } - DynamicJsonDocument doc(512); + JsonDocument doc; int bufferused = 0; @@ -895,7 +895,7 @@ esp_err_t post_avrprog_json_handler(httpd_req_t *req, bool urlEncoded) if (LittleFS.exists(manifestfilename)) { - DynamicJsonDocument jsonmanifest(3000); + JsonDocument jsonmanifest; File file = LittleFS.open(manifestfilename); DeserializationError error = deserializeJson(jsonmanifest, file); if (error != DeserializationError::Ok) @@ -1083,8 +1083,7 @@ esp_err_t post_saverules_json_handler(httpd_req_t *req, bool urlEncoded) esp_err_t post_restoreconfig_json_handler(httpd_req_t *req, bool urlEncoded) { - // Needs to be large enough to de-serialize the JSON file - DynamicJsonDocument doc(8000); + JsonDocument doc; bool success = false; diff --git a/ESPController/src/webserver_json_requests.cpp b/ESPController/src/webserver_json_requests.cpp index 5bf04c4f..2178a38b 100644 --- a/ESPController/src/webserver_json_requests.cpp +++ b/ESPController/src/webserver_json_requests.cpp @@ -25,7 +25,7 @@ esp_err_t content_handler_avrstorage(httpd_req_t *req) if (LittleFS.exists(manifest)) { // Use Dynamic to avoid head issues - DynamicJsonDocument doc(3000); + JsonDocument doc; File file = LittleFS.open(manifest); DeserializationError error = deserializeJson(doc, file); if (error) @@ -524,9 +524,9 @@ esp_err_t content_handler_modules(httpd_req_t *req) prg.sendGetSettingsRequest(c); } - DynamicJsonDocument doc(2048); + JsonDocument doc; JsonObject root = doc.to(); - JsonObject settings = root.createNestedObject("settings"); + JsonObject settings = root["settings"].to(); uint8_t b = c / mysettings.totalNumberOfSeriesModules; uint8_t m = c - (b * mysettings.totalNumberOfSeriesModules); @@ -563,7 +563,7 @@ esp_err_t content_handler_modules(httpd_req_t *req) esp_err_t content_handler_avrstatus(httpd_req_t *req) { int bufferused = 0; - StaticJsonDocument<200> doc; + JsonDocument doc; doc["inprogress"] = _avrsettings.inProgress ? 1 : 0; doc["result"] = _avrsettings.progresult; doc["duration"] = _avrsettings.duration; @@ -577,10 +577,10 @@ esp_err_t content_handler_avrstatus(httpd_req_t *req) esp_err_t content_handler_tileconfig(httpd_req_t *req) { - StaticJsonDocument<200> doc; + JsonDocument doc; JsonObject root = doc.to(); - JsonObject settings = root.createNestedObject("tileconfig"); - JsonArray v = settings.createNestedArray("values"); + JsonObject settings = root["tileconfig"].to(); + JsonArray v = settings["values"].to();; for (auto n : mysettings.tileconfig) { @@ -595,9 +595,9 @@ esp_err_t content_handler_chargeconfig(httpd_req_t *req) { int bufferused = 0; - DynamicJsonDocument doc(2048); + JsonDocument doc; JsonObject root = doc.to(); - JsonObject settings = root.createNestedObject("chargeconfig"); + JsonObject settings = root["chargeconfig"].to(); settings["protocol"] = mysettings.protocol; settings["canbusinverter"] = mysettings.canbusinverter; @@ -648,7 +648,7 @@ esp_err_t content_handler_rules(httpd_req_t *req) { int bufferused = 0; - DynamicJsonDocument doc(3000); + JsonDocument doc; JsonObject root = doc.to(); struct tm timeinfo; @@ -663,7 +663,7 @@ esp_err_t content_handler_rules(httpd_req_t *req) root["ControlState"] = _controller_state; - JsonArray defaultArray = root.createNestedArray("relaydefault"); + JsonArray defaultArray = root["relaydefault"].to(); for (auto v : mysettings.rulerelaydefault) { switch (v) @@ -681,7 +681,7 @@ esp_err_t content_handler_rules(httpd_req_t *req) } } - JsonArray typeArray = root.createNestedArray("relaytype"); + JsonArray typeArray = root["relaytype"].to(); for (auto v : mysettings.relaytype) { switch (v) @@ -698,15 +698,15 @@ esp_err_t content_handler_rules(httpd_req_t *req) } } - JsonArray bankArray = root.createNestedArray("rules"); + JsonArray bankArray = root["rules"].to(); for (uint8_t r = 0; r < RELAY_RULES; r++) { - JsonObject rule = bankArray.createNestedObject(); + JsonObject rule = bankArray.add(); rule["value"] = mysettings.rulevalue[r]; rule["hysteresis"] = mysettings.rulehysteresis[r]; rule["triggered"] = rules.ruleOutcome((Rule)r); - JsonArray data = rule.createNestedArray("relays"); + JsonArray data = rule["relays"].to(); for (auto v : mysettings.rulerelaystate[r]) { @@ -735,10 +735,10 @@ esp_err_t content_handler_settings(httpd_req_t *req) { int bufferused = 0; - DynamicJsonDocument doc(2048); + JsonDocument doc; JsonObject root = doc.to(); - JsonObject settings = root.createNestedObject("settings"); + JsonObject settings = root["settings"].to(); settings["totalnumberofbanks"] = mysettings.totalNumberOfBanks; settings["totalseriesmodules"] = mysettings.totalNumberOfSeriesModules; @@ -802,7 +802,7 @@ esp_err_t content_handler_settings(httpd_req_t *req) settings["man_dns2"] = ip4_to_string(_wificonfig.wifi_dns2); } - JsonObject wifi = root.createNestedObject("wifi"); + JsonObject wifi = root["wifi"].to(); if (wifi_isconnected) { @@ -839,13 +839,13 @@ esp_err_t content_handler_integration(httpd_req_t *req) { int bufferused = 0; - DynamicJsonDocument doc(1024); + JsonDocument doc; JsonObject root = doc.to(); - JsonObject ha = root.createNestedObject("ha"); + JsonObject ha = root["ha"].to(); ha["api"] = mysettings.homeassist_apikey; - JsonObject mqtt = root.createNestedObject("mqtt"); + JsonObject mqtt = root["mqtt"].to(); mqtt["enabled"] = mysettings.mqtt_enabled; mqtt["basiccellreporting"] = mysettings.mqtt_basic_cell_reporting; mqtt["topic"] = mysettings.mqtt_topic; @@ -861,7 +861,7 @@ esp_err_t content_handler_integration(httpd_req_t *req) // We don't output the password in the json file as this could breach security // mqtt["password"] =mysettings.mqtt_password; - JsonObject influxdb = root.createNestedObject("influxdb"); + JsonObject influxdb = root["influxdb"].to(); influxdb["enabled"] = mysettings.influxdb_enabled; influxdb["url"] = mysettings.influxdb_serverurl; influxdb["bucket"] = mysettings.influxdb_databasebucket; From 5ca8ddb2107ce7e7ede6ea2618505a3e22988204 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:13:39 +0000 Subject: [PATCH 18/39] Update main.cpp --- ESPController/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index f655d877..22037a21 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -3724,7 +3724,7 @@ const std::array log_levels = {.tag = "diybms-webreq", .level = ESP_LOG_INFO}, {.tag = "diybms-web", .level = ESP_LOG_INFO}, {.tag = "diybms-set", .level = ESP_LOG_INFO}, - {.tag = "diybms-mqtt", .level = ESP_LOG_DEBUG}, + {.tag = "diybms-mqtt", .level = ESP_LOG_INFO}, {.tag = "diybms-pylon", .level = ESP_LOG_INFO}, {.tag = "diybms-pyforce", .level = ESP_LOG_INFO}, {.tag = "curmon", .level = ESP_LOG_INFO}}; From 8cb752463ffcfb3609dd41f19dccafbdb4b85921 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:31:54 +0000 Subject: [PATCH 19/39] Store SoC into flash and restore on power up --- ESPController/include/CurrentMonitorINA229.h | 8 ++- ESPController/include/defines.h | 1 + ESPController/include/settings.h | 3 + ESPController/src/CurrentMonitorINA229.cpp | 60 ++++++---------- ESPController/src/main.cpp | 31 ++++++-- ESPController/src/settings.cpp | 75 ++++++++++++++++++-- 6 files changed, 126 insertions(+), 52 deletions(-) diff --git a/ESPController/include/CurrentMonitorINA229.h b/ESPController/include/CurrentMonitorINA229.h index 0e98b5aa..47edf6f6 100644 --- a/ESPController/include/CurrentMonitorINA229.h +++ b/ESPController/include/CurrentMonitorINA229.h @@ -217,9 +217,12 @@ class CurrentMonitorINA229 uint16_t shunttempcoefficient, bool TemperatureCompEnabled); - void GuessSOC(); + void DefaultSOC(); void TakeReadings(); + uint32_t raw_milliamphour_out() const{ return milliamphour_out; } + uint32_t raw_milliamphour_in() const{ return milliamphour_in; } + uint32_t calc_milliamphour_out() const{ return milliamphour_out - milliamphour_out_offset; } uint32_t calc_milliamphour_in() const{ return milliamphour_in - milliamphour_in_offset; } uint32_t calc_daily_milliamphour_out() const{ return daily_milliamphour_out; } @@ -264,12 +267,15 @@ class CurrentMonitorINA229 return registers.R_DIAG_ALRT & ALL_ALERT_BITS; } void SetSOC(uint16_t value); + void SetSOCByMilliAmpCounter(uint32_t in,uint32_t out); void ResetDailyAmpHourCounters() { daily_milliamphour_out=0; daily_milliamphour_in=0; } + uint16_t raw_stateofcharge() const { return SOC; } + private: uint16_t SOC = 0; float voltage = 0; diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index 76d63fc2..235e54c1 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -258,6 +258,7 @@ struct diybms_eeprom_settings char homeassist_apikey[24+1]; /// @brief State of health variables - total lifetime mAh output (discharge) + // Might need to watch overflow on the uint32 (max value 4,294,967,295mAh) = approx 15339 cycles of 280Ah battery uint32_t soh_total_milliamphour_out; /// @brief State of health variables - total lifetime mAh input (charge) uint32_t soh_total_milliamphour_in; diff --git a/ESPController/include/settings.h b/ESPController/include/settings.h index e0c68629..89179c07 100644 --- a/ESPController/include/settings.h +++ b/ESPController/include/settings.h @@ -43,4 +43,7 @@ void writeSetting(nvs_handle_t handle, const char *key, int8_t value); void writeSetting(nvs_handle_t handle, const char *key, const char *value); void writeSettingBlob(nvs_handle_t handle, const char *key, const void *value, size_t length); +bool GetStateOfCharge(uint32_t *in,uint32_t *out); +void SaveStateOfCharge(uint32_t,uint32_t); + #endif \ No newline at end of file diff --git a/ESPController/src/CurrentMonitorINA229.cpp b/ESPController/src/CurrentMonitorINA229.cpp index b4c271f3..c7cf1292 100644 --- a/ESPController/src/CurrentMonitorINA229.cpp +++ b/ESPController/src/CurrentMonitorINA229.cpp @@ -71,6 +71,20 @@ void CurrentMonitorINA229::CalculateLSB() // registers.R_SHUNT_CAL = ((uint32_t)registers.R_SHUNT_CAL * 985) / 1000; } + +void CurrentMonitorINA229::SetSOCByMilliAmpCounter(uint32_t in,uint32_t out) { + // Assume battery is fully charged + milliamphour_in = in; + // And we have consumed this much... + milliamphour_out = out; + + // Zero out readings using the offsets + milliamphour_out_offset = milliamphour_out; + milliamphour_in_offset = milliamphour_in; + + ESP_LOGI(TAG, "SetSOCByMilliAmpCounter mA in=%u, mA out=%u",milliamphour_in,milliamphour_out); +} + // Sets SOC by setting "fake" in/out amphour counts // value=8212 = 82.12% void CurrentMonitorINA229::SetSOC(uint16_t value) @@ -78,11 +92,13 @@ void CurrentMonitorINA229::SetSOC(uint16_t value) // Assume battery is fully charged milliamphour_in = 1000 * (uint32_t)registers.batterycapacity_amphour; // And we have consumed this much... - milliamphour_out = (uint32_t)((1.0F - ((float)value / 10000.0F)) * milliamphour_in); + milliamphour_out = (uint32_t)((1.0F - ((float)value / 10000.0F)) * (float)milliamphour_in); // Zero out readings using the offsets milliamphour_out_offset = milliamphour_out; milliamphour_in_offset = milliamphour_in; + + ESP_LOGI(TAG, "SetSOC mA in=%u, mA out=%u",milliamphour_in,milliamphour_out); } uint8_t CurrentMonitorINA229::readRegisterValue(INA_REGISTER r) const @@ -391,47 +407,11 @@ void CurrentMonitorINA229::CalculateAmpHourCounts() } } -// Guess the SoC % based on battery voltage - not accurate, just a guess! -void CurrentMonitorINA229::GuessSOC() +// Set SoC to default values +void CurrentMonitorINA229::DefaultSOC() { // Default SOC% at 60% - uint16_t soc = 6000; - - // We apply a "guestimate" to SoC based on voltage - not really accurate, but somewhere to start - // only applicable to 24V/48V (16S) LIFEPO4 setups. These voltages should be the unloaded (no current flowing) voltage. - // Assumption that its LIFEPO4 cells we are using... - float v = BusVoltage(); - - if (v > 20 && v < 30) - { - // Scale up 24V battery to use the 48V scale - v = v * 2; - } - - if (v > 40 && v < 60) - { - // 16S LIFEPO4... - if (v >= 40.0) - soc = 500; - if (v >= 48.0) - soc = 900; - if (v >= 50.0) - soc = 1400; - if (v >= 51.2) - soc = 1700; - if (v >= 51.6) - soc = 2000; - if (v >= 52.0) - soc = 3000; - if (v >= 52.4) - soc = 4000; - if (v >= 52.8) - soc = 7000; - if (v >= 53.2) - soc = 9000; - } - - SetSOC(soc); + SetSOC(6000); // Reset the daily counters daily_milliamphour_in = 0; diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index 22037a21..b9d0423f 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -3704,7 +3704,7 @@ struct log_level_t }; // Default log levels to use for various components. -const std::array log_levels = +const std::array log_levels = { log_level_t{.tag = "*", .level = ESP_LOG_DEBUG}, {.tag = "wifi", .level = ESP_LOG_WARN}, @@ -3725,6 +3725,7 @@ const std::array log_levels = {.tag = "diybms-web", .level = ESP_LOG_INFO}, {.tag = "diybms-set", .level = ESP_LOG_INFO}, {.tag = "diybms-mqtt", .level = ESP_LOG_INFO}, + {.tag = "diybms-ctrl", .level = ESP_LOG_INFO}, {.tag = "diybms-pylon", .level = ESP_LOG_INFO}, {.tag = "diybms-pyforce", .level = ESP_LOG_INFO}, {.tag = "curmon", .level = ESP_LOG_INFO}}; @@ -3916,10 +3917,19 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)", mysettings.currentMonitoring_shunttempcoefficient, mysettings.currentMonitoring_tempcompenabled); - currentmon_internal.GuessSOC(); + currentmon_internal.DefaultSOC(); + + uint32_t in; + uint32_t out; + if (GetStateOfCharge(&in,&out)) + { + currentmon_internal.SetSOCByMilliAmpCounter(in,out); + } currentmon_internal.TakeReadings(); + + CalculateStateOfHealth(&mysettings); } else @@ -4061,13 +4071,15 @@ void ESPCoreDumpToJSON(JsonObject &doc) ultoa(summary->ex_info.exc_vaddr, outputString, 16); core["exc_vaddr"] = outputString; - auto exc_a = core["exc_a"].to();; + auto exc_a = core["exc_a"].to(); + ; for (auto value : summary->ex_info.exc_a) { ultoa(value, outputString, 16); exc_a.add(outputString); } - auto epcx = core["epcx"].to();; + auto epcx = core["epcx"].to(); + ; for (auto value : summary->ex_info.epcx) { ultoa(value, outputString, 16); @@ -4227,7 +4239,14 @@ void loop() heap.free_blocks, heap.total_blocks); - // Report again in 30 seconds - heaptimer = currentMillis + 30000; + // Report again in 60 seconds + heaptimer = currentMillis + 60000; + + //Once per minute, store the state of charge into flash, just in case the controller is rebooted and we can restore this value + //on power up. + if (mysettings.currentMonitoringEnabled && mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_INTERNAL) + { + SaveStateOfCharge(currentmon_internal.raw_milliamphour_in(),currentmon_internal.raw_milliamphour_out()); + } } } diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index b1258686..da6e6b84 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -194,6 +194,9 @@ static const char soh_total_milliamphour_in_NVSKEY[] = "soh_mah_in"; static const char soh_lifetime_battery_cycles_NVSKEY[] = "soh_batcycle"; static const char soh_discharge_depth_NVSKEY[] = "soh_disdepth"; +static const char soc_milliamphour_out_NVSKEY[] = "soc_mah_out"; +static const char soc_milliamphour_in_NVSKEY[] = "soc_mah_in"; + #define MACRO_NVSWRITE(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, settings->VARNAME); #define MACRO_NVSWRITE_UINT8(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, (uint8_t)settings->VARNAME); #define MACRO_NVSWRITESTRING(VARNAME) writeSetting(nvs_handle, VARNAME##_NVSKEY, &settings->VARNAME[0]); @@ -346,6 +349,64 @@ void writeSettingBlob(nvs_handle_t handle, const char *key, const void *value, s ESP_ERROR_CHECK(nvs_set_blob(handle, key, value, length)); } +/// @brief Reads state of charge (milliamp hour counts) from flash +/// @param in pointer to milliamp in count +/// @param out pointer to milliamp out count +/// @return true if values are valid +bool GetStateOfCharge(uint32_t *in, uint32_t *out) +{ + const char *partname = "diybms-ctrl"; + ESP_LOGI(TAG, "Read state of charge from flash"); + + nvs_handle_t nvs_handle; + auto err = nvs_open(partname, NVS_READONLY, &nvs_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error (%s) opening NVS handle", esp_err_to_name(err)); + } + else + { + // Open + auto ret1 = getSetting(nvs_handle, soc_milliamphour_in_NVSKEY, in); + auto ret2 = getSetting(nvs_handle, soc_milliamphour_out_NVSKEY, out); + + nvs_close(nvs_handle); + + if (ret1 && ret2) + { + return true; + } + + ESP_LOGI(TAG, "SoC value doesn't exist in flash"); + } + return false; +} + +/// @brief Stores the milliamp current values to allow restore of state of charge on power up +/// @param in milliamp hours in +/// @param out milliamp hours out +void SaveStateOfCharge(uint32_t in, uint32_t out) +{ + const char *partname = "diybms-ctrl"; + ESP_LOGI(TAG, "Write SoC to flash in=%u out=%u", in, out); + + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(partname, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error %s opening NVS handle!", esp_err_to_name(err)); + } + else + { + // Save settings + writeSetting(nvs_handle, soc_milliamphour_in_NVSKEY, in); + writeSetting(nvs_handle, soc_milliamphour_out_NVSKEY, out); + + ESP_ERROR_CHECK(nvs_commit(nvs_handle)); + nvs_close(nvs_handle); + } +} + void SaveConfiguration(const diybms_eeprom_settings *settings) { const char *partname = "diybms-ctrl"; @@ -1004,8 +1065,9 @@ void ValidateConfiguration(diybms_eeprom_settings *settings) settings->stateofchargeresumevalue = settings->stateofchargeresumevalue; } - if (settings->soh_discharge_depth==0 || settings->soh_discharge_depth>100) { - settings->soh_discharge_depth=80; + if (settings->soh_discharge_depth == 0 || settings->soh_discharge_depth > 100) + { + settings->soh_discharge_depth = 80; } } @@ -1074,8 +1136,10 @@ void GenerateSettingsJSONDocument(JsonDocument &doc, diybms_eeprom_settings *set influxdb[influxdb_loggingFreqSeconds_JSONKEY] = settings->influxdb_loggingFreqSeconds; JsonObject outputs = root["outputs"].to(); - JsonArray d = outputs["default"].to();; - JsonArray t = outputs["type"].to();; + JsonArray d = outputs["default"].to(); + ; + JsonArray t = outputs["type"].to(); + ; for (uint8_t i = 0; i < RELAY_TOTAL; i++) { d.add(settings->rulerelaydefault[i]); @@ -1104,7 +1168,8 @@ void GenerateSettingsJSONDocument(JsonDocument &doc, diybms_eeprom_settings *set state["value"] = settings->rulevalue[rr]; state["hysteresis"] = settings->rulehysteresis[rr]; - JsonArray relaystate = state["state"].to();; + JsonArray relaystate = state["state"].to(); + ; for (uint8_t rt = 0; rt < RELAY_TOTAL; rt++) { relaystate.add(settings->rulerelaystate[rr][rt]); From 3fc920e27a0c75ce837ca114cd9008de2d163eb4 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:48:35 +0000 Subject: [PATCH 20/39] Change SoC calculation to actually reach 100% state of charge --- ESPController/src/CurrentMonitorINA229.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ESPController/src/CurrentMonitorINA229.cpp b/ESPController/src/CurrentMonitorINA229.cpp index c7cf1292..30424a80 100644 --- a/ESPController/src/CurrentMonitorINA229.cpp +++ b/ESPController/src/CurrentMonitorINA229.cpp @@ -90,9 +90,15 @@ void CurrentMonitorINA229::SetSOCByMilliAmpCounter(uint32_t in,uint32_t out) { void CurrentMonitorINA229::SetSOC(uint16_t value) { // Assume battery is fully charged - milliamphour_in = 1000 * (uint32_t)registers.batterycapacity_amphour; + //milliamphour_in = 1000 * (uint32_t)registers.batterycapacity_amphour; // And we have consumed this much... - milliamphour_out = (uint32_t)((1.0F - ((float)value / 10000.0F)) * (float)milliamphour_in); + //milliamphour_out = (uint32_t)((1.0F - ((float)value / 10000.0F)) * (float)milliamphour_in); + + // Updated SoC logic by delboy711 https://github.com/stuartpittaway/diyBMSv4ESP32/issues/232 + // Assume battery is fully charged + milliamphour_in = lround(100000.0*(float)registers.batterycapacity_amphour/(registers.charge_efficiency_factor)); + // And we have consumed this much... + milliamphour_out = (uint32_t)((1.0 - ((float)value / 10000.0)) * (1000.0*(float)registers.batterycapacity_amphour)); // Zero out readings using the offsets milliamphour_out_offset = milliamphour_out; @@ -101,6 +107,8 @@ void CurrentMonitorINA229::SetSOC(uint16_t value) ESP_LOGI(TAG, "SetSOC mA in=%u, mA out=%u",milliamphour_in,milliamphour_out); } + + uint8_t CurrentMonitorINA229::readRegisterValue(INA_REGISTER r) const { // These are not really registers, but shape the SPI frame to indicate read/write From 05839be857a37b24ca0762e895acdf9760fae4a0 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:53:12 +0000 Subject: [PATCH 21/39] Update pylonforce_canbus.cpp --- ESPController/src/pylonforce_canbus.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ESPController/src/pylonforce_canbus.cpp b/ESPController/src/pylonforce_canbus.cpp index 105ba001..b17e20a7 100644 --- a/ESPController/src/pylonforce_canbus.cpp +++ b/ESPController/src/pylonforce_canbus.cpp @@ -132,17 +132,18 @@ void pylonforce_message_4210() data.temperature = 121+1000; // 12.1 °C } - // TODO: Need to determine this based on age of battery/cycles etc. - data.stateofhealthvalue = 100; // Only send CANBUS message if we have a current monitor enabled & valid if (mysettings.currentMonitoringEnabled && currentMonitor.validReadings && (mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_MODBUS || mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_INTERNAL)) { - data.stateofchargevalue = rules.StateOfChargeWithRulesApplied(&mysettings, currentMonitor.stateofcharge); + data.stateofchargevalue = (uint8_t)rules.StateOfChargeWithRulesApplied(&mysettings, currentMonitor.stateofcharge); + // Based on age of battery/cycles + data.stateofhealthvalue = (uint8_t)(trunc(mysettings.soh_percent)); } else { data.stateofchargevalue = 50; + data.stateofhealthvalue = 100; } send_ext_canbus_message(0x4210+mysettings.canbus_equipment_addr, (uint8_t *)&data, sizeof(data4210)); From a3b4131237e0d81f310cb986d0515166b89b6a59 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:03:41 +0000 Subject: [PATCH 22/39] Update settings.cpp --- ESPController/src/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index da6e6b84..4cc9d467 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -325,7 +325,7 @@ void writeSetting(nvs_handle_t handle, const char *key, int16_t value) } void writeSetting(nvs_handle_t handle, const char *key, uint32_t value) { - ESP_LOGD(TAG, "Writing (%s)=%i", key, value); + ESP_LOGD(TAG, "Writing (%s)=%u", key, value); ESP_ERROR_CHECK(nvs_set_u32(handle, key, value)); } void writeSetting(nvs_handle_t handle, const char *key, int32_t value) From 40ba5501ebbb0b25126296d22a3a0daceabf9c1d Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:06:44 +0000 Subject: [PATCH 23/39] Change logic of state of health calculation --- ESPController/include/defines.h | 6 ++-- ESPController/src/main.cpp | 29 ++++++++++--------- ESPController/src/settings.cpp | 25 ++++++++-------- ESPController/src/webserver_json_post.cpp | 2 +- ESPController/src/webserver_json_requests.cpp | 2 +- ESPController/web_src/default.htm | 8 +++-- ESPController/web_src/pagecode.js | 2 +- 7 files changed, 38 insertions(+), 36 deletions(-) diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index 235e54c1..2488da3d 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -264,10 +264,10 @@ struct diybms_eeprom_settings uint32_t soh_total_milliamphour_in; /// @brief State of health variables - total expected lifetime cycles of battery (6000) uint16_t soh_lifetime_battery_cycles; + /// @brief State of health variables - expected remaining capacity (%) at end of life/max cycles + uint8_t soh_eol_capacity; /// @brief State of health variables - estimated number of cycles - uint16_t soh_estimated_battery_cycles; - /// @brief State of health variables - discharge depth (80%) - uint8_t soh_discharge_depth; + uint16_t soh_estimated_battery_cycles; /// @brief Calculated percentage calculation of health float soh_percent; }; diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index b9d0423f..e8e027a9 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -3220,17 +3220,16 @@ void send_ext_canbus_message(const uint32_t identifier, const uint8_t *buffer, c void CalculateStateOfHealth(diybms_eeprom_settings *settings) { - // Value indicating what a typical discharge cycle looks like in amp-hours (normally 80% of cell for LFP) - float depth = 1000.0F * ((float)settings->nominalbatcap / 100.0F * (float)settings->soh_discharge_depth); - float in = (float)settings->soh_total_milliamphour_in / depth; - float out = (float)settings->soh_total_milliamphour_out / depth; - // Take worst case + float batcap_mah = 1000.0F * settings->nominalbatcap; + float in = (float)settings->soh_total_milliamphour_in / batcap_mah; + float out = (float)settings->soh_total_milliamphour_out / batcap_mah; + // Take worst case number of cycles float cycles = max(in, out); - settings->soh_percent = 100 - ((cycles / (float)settings->soh_lifetime_battery_cycles) * 100.0F); - settings->soh_estimated_battery_cycles = (uint16_t)round(cycles); + settings->soh_percent = 100.0F - ((100.0F - settings->soh_eol_capacity) * (cycles / (float)settings->soh_lifetime_battery_cycles)); + ESP_LOGI(TAG, "State of health calc %f %, estimated cycles=%f", settings->soh_percent, cycles); } @@ -3921,15 +3920,13 @@ ESP32 Chip model = %u, Rev %u, Cores=%u, Features=%u)", uint32_t in; uint32_t out; - if (GetStateOfCharge(&in,&out)) + if (GetStateOfCharge(&in, &out)) { - currentmon_internal.SetSOCByMilliAmpCounter(in,out); + currentmon_internal.SetSOCByMilliAmpCounter(in, out); } currentmon_internal.TakeReadings(); - - CalculateStateOfHealth(&mysettings); } else @@ -4242,11 +4239,15 @@ void loop() // Report again in 60 seconds heaptimer = currentMillis + 60000; - //Once per minute, store the state of charge into flash, just in case the controller is rebooted and we can restore this value - //on power up. + // Once per minute, store the state of charge into flash, just in case the controller is rebooted and we can restore this value + // on power up. if (mysettings.currentMonitoringEnabled && mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_INTERNAL) { - SaveStateOfCharge(currentmon_internal.raw_milliamphour_in(),currentmon_internal.raw_milliamphour_out()); + // Avoid writing zero SoC into flash + if (currentmon_internal.calc_state_of_charge() > 1.0F) + { + SaveStateOfCharge(currentmon_internal.raw_milliamphour_in(), currentmon_internal.raw_milliamphour_out()); + } } } } diff --git a/ESPController/src/settings.cpp b/ESPController/src/settings.cpp index 4cc9d467..73434d5e 100644 --- a/ESPController/src/settings.cpp +++ b/ESPController/src/settings.cpp @@ -95,7 +95,10 @@ static const char soh_total_milliamphour_out_JSONKEY[] = "soh_mah_out"; static const char soh_total_milliamphour_in_JSONKEY[] = "soh_mah_in"; static const char soh_lifetime_battery_cycles_JSONKEY[] = "soh_batcycle"; -static const char soh_discharge_depth_JSONKEY[] = "soh_disdepth"; + +static const char soh_eol_capacity_JSONKEY[] = "soh_eol_capacity"; + + /* NVS KEYS THESE STRINGS ARE USED TO HOLD THE PARAMETER IN NVS FLASH, MAXIMUM LENGTH OF 16 CHARACTERS @@ -192,7 +195,7 @@ static const char homeassist_apikey_NVSKEY[] = "haapikey"; static const char soh_total_milliamphour_out_NVSKEY[] = "soh_mah_out"; static const char soh_total_milliamphour_in_NVSKEY[] = "soh_mah_in"; static const char soh_lifetime_battery_cycles_NVSKEY[] = "soh_batcycle"; -static const char soh_discharge_depth_NVSKEY[] = "soh_disdepth"; +static const char soh_eol_capacity_NVSKEY[] = "soh_eol_cap"; static const char soc_milliamphour_out_NVSKEY[] = "soc_mah_out"; static const char soc_milliamphour_in_NVSKEY[] = "soc_mah_in"; @@ -523,7 +526,7 @@ void SaveConfiguration(const diybms_eeprom_settings *settings) MACRO_NVSWRITE(soh_total_milliamphour_out) MACRO_NVSWRITE(soh_total_milliamphour_in) MACRO_NVSWRITE(soh_lifetime_battery_cycles) - MACRO_NVSWRITE_UINT8(soh_discharge_depth) + MACRO_NVSWRITE_UINT8(soh_eol_capacity) ESP_ERROR_CHECK(nvs_commit(nvs_handle)); nvs_close(nvs_handle); @@ -659,8 +662,7 @@ void LoadConfiguration(diybms_eeprom_settings *settings) MACRO_NVSREAD(soh_total_milliamphour_out) MACRO_NVSREAD(soh_total_milliamphour_in) MACRO_NVSREAD(soh_lifetime_battery_cycles) - MACRO_NVSREAD_UINT8(soh_discharge_depth) - + MACRO_NVSREAD_UINT8(soh_eol_capacity) nvs_close(nvs_handle); } @@ -848,7 +850,7 @@ void DefaultConfiguration(diybms_eeprom_settings *_myset) _myset->soh_total_milliamphour_out = 0; _myset->soh_total_milliamphour_in = 0; _myset->soh_lifetime_battery_cycles = 6000; - _myset->soh_discharge_depth = 80; + _myset->soh_eol_capacity = 80; _myset->soh_percent = 100.0F; } @@ -1064,11 +1066,6 @@ void ValidateConfiguration(diybms_eeprom_settings *settings) { settings->stateofchargeresumevalue = settings->stateofchargeresumevalue; } - - if (settings->soh_discharge_depth == 0 || settings->soh_discharge_depth > 100) - { - settings->soh_discharge_depth = 80; - } } // Builds up a JSON document which mirrors the parameters inside "diybms_eeprom_settings" @@ -1222,7 +1219,8 @@ void GenerateSettingsJSONDocument(JsonDocument &doc, diybms_eeprom_settings *set root[soh_total_milliamphour_out_JSONKEY] = settings->soh_total_milliamphour_out; root[soh_total_milliamphour_in_JSONKEY] = settings->soh_total_milliamphour_in; root[soh_lifetime_battery_cycles_JSONKEY] = settings->soh_lifetime_battery_cycles; - root[soh_discharge_depth_JSONKEY] = settings->soh_discharge_depth; + root[soh_eol_capacity_JSONKEY] = settings->soh_eol_capacity; + } void JSONToSettings(JsonDocument &doc, diybms_eeprom_settings *settings) @@ -1319,7 +1317,8 @@ void JSONToSettings(JsonDocument &doc, diybms_eeprom_settings *settings) settings->soh_total_milliamphour_out = root[soh_total_milliamphour_out_JSONKEY]; settings->soh_total_milliamphour_in = root[soh_total_milliamphour_in_JSONKEY]; settings->soh_lifetime_battery_cycles = root[soh_lifetime_battery_cycles_JSONKEY]; - settings->soh_discharge_depth = root[soh_discharge_depth_JSONKEY]; + settings->soh_eol_capacity=root[soh_eol_capacity_JSONKEY]; + strncpy(settings->homeassist_apikey, root[homeassist_apikey_JSONKEY].as().c_str(), sizeof(settings->homeassist_apikey)); diff --git a/ESPController/src/webserver_json_post.cpp b/ESPController/src/webserver_json_post.cpp index f397827a..98819705 100644 --- a/ESPController/src/webserver_json_post.cpp +++ b/ESPController/src/webserver_json_post.cpp @@ -579,7 +579,7 @@ esp_err_t post_savechargeconfig_json_handler(httpd_req_t *req, bool urlEncoded) } GetKeyValue(httpbuf, "expected_cycles", &mysettings.soh_lifetime_battery_cycles, urlEncoded); - GetKeyValue(httpbuf, "dischargedepth", &mysettings.soh_discharge_depth, urlEncoded); + GetKeyValue(httpbuf, "eol_capacity", &mysettings.soh_eol_capacity, urlEncoded); if (GetKeyValue(httpbuf, "total_ah_discharge", &mysettings.soh_total_milliamphour_out, urlEncoded)) { diff --git a/ESPController/src/webserver_json_requests.cpp b/ESPController/src/webserver_json_requests.cpp index 2178a38b..ce82aaed 100644 --- a/ESPController/src/webserver_json_requests.cpp +++ b/ESPController/src/webserver_json_requests.cpp @@ -605,8 +605,8 @@ esp_err_t content_handler_chargeconfig(httpd_req_t *req) settings["equip_addr"] = mysettings.canbus_equipment_addr; settings["nominalbatcap"] = mysettings.nominalbatcap; - settings["dischargedepth"] = mysettings.soh_discharge_depth; settings["expectedlifetime_cycles"] = mysettings.soh_lifetime_battery_cycles; + settings["eol_capacity"] = mysettings.soh_eol_capacity; settings["total_mah_charge"] = mysettings.soh_total_milliamphour_in; settings["total_mah_discharge"] = mysettings.soh_total_milliamphour_out; settings["estimatebatterycycle"] = mysettings.soh_estimated_battery_cycles; diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index e96fec72..fd11adab 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -1035,13 +1035,15 @@

Charge/Discharge configuration

+

State of Health

+
- +
- - + +
diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index 894d8ef8..2eda4249 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -2004,7 +2004,7 @@ $(function () { $("#nominalbatcap").val(data.chargeconfig.nominalbatcap); $("#expected_cycles").val(data.chargeconfig.expectedlifetime_cycles); - $("#dischargedepth").val(data.chargeconfig.dischargedepth); + $("#eol_capacity").val(data.chargeconfig.eol_capacity); $("#total_ah_charge").val(Math.trunc(data.chargeconfig.total_mah_charge / 1000)); $("#total_ah_discharge").val(Math.trunc(data.chargeconfig.total_mah_discharge / 1000)); $("#estimate_bat_cycle").val(data.chargeconfig.estimatebatterycycle); From f6202a51312400a39c72ddd2912dd3e1a8dbb67a Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:52:04 +0000 Subject: [PATCH 24/39] Update pagecode.js --- ESPController/web_src/pagecode.js | 72 +++++++++++++------------------ 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index 2eda4249..5d9188dc 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -70,7 +70,7 @@ function upload_file() { xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { - var status = xhr.status; + let status = xhr.status; if (status >= 200 && status < 400) { //Refresh the storage page $("#storage").trigger("click"); @@ -100,7 +100,7 @@ function upload_firmware() { }); xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { - var status = xhr.status; + let status = xhr.status; if (status >= 200 && status < 400) { $("#status_div").text("Upload accepted. BMS will reboot."); } else { @@ -120,16 +120,16 @@ function CalculateChargeCurrent(value1, value2, highestCellVoltage, maximumcharg return maximumchargecurrent; } - var knee_voltage = 0 / 100.0; - var at_knee = Math.pow(value1, knee_voltage * Math.pow(knee_voltage, value2)); + let knee_voltage = 0 / 100.0; + let at_knee = Math.pow(value1, knee_voltage * Math.pow(knee_voltage, value2)); - var target_cell_voltage = (cellmaxmv - kneemv) / 100.0; - var at_target_cell_voltage = Math.pow(value1, target_cell_voltage * Math.pow(target_cell_voltage, value2)); + let target_cell_voltage = (cellmaxmv - kneemv) / 100.0; + let at_target_cell_voltage = Math.pow(value1, target_cell_voltage * Math.pow(target_cell_voltage, value2)); - var actual_cell_voltage = (highestCellVoltage - kneemv) / 100.0; - var at_actual_cell_voltage = Math.pow(value1, actual_cell_voltage * Math.pow(actual_cell_voltage, value2)); + let actual_cell_voltage = (highestCellVoltage - kneemv) / 100.0; + let at_actual_cell_voltage = Math.pow(value1, actual_cell_voltage * Math.pow(actual_cell_voltage, value2)); - var percent = 1 - (at_actual_cell_voltage / at_knee) / at_target_cell_voltage; + let percent = 1 - (at_actual_cell_voltage / at_knee) / at_target_cell_voltage; if (percent < 0.01) { percent = 0.01; @@ -139,8 +139,6 @@ function CalculateChargeCurrent(value1, value2, highestCellVoltage, maximumcharg } function DrawChargingGraph() { - - var xaxisvalues = []; var yaxisvalues = []; @@ -158,18 +156,10 @@ function DrawChargingGraph() { yaxisvalues.push(CalculateChargeCurrent(value1, value2, voltage, chargecurrent, kneemv, cellmaxmv)); } - /* - if (window.g3 != null) { - window.g3.dispose(); - window.g3 = null; - } - */ - if (window.g3 == null) { + if (window.g3 == null) { window.g3 = echarts.init(document.getElementById('graph3')) - var option; - - option = { + let option = { tooltip: { trigger: 'axis', @@ -304,7 +294,7 @@ function refreshCurrentMonitorValues() { $("#cmmodel").val(data.model.toString(16)); $("#cmfirmwarev").val(data.firmwarev.toString(16)); - var d = new Date(data.firmwaredate * 1000); + let d = new Date(data.firmwaredate * 1000); $("#cmfirmwaredate").val(d.toString()); @@ -357,13 +347,13 @@ function refreshCurrentMonitorValues() { // Show and hide tiles based on bit pattern in tileconfig array function refreshVisibleTiles() { for (i = 0; i < TILE_IDS.length; i++) { - var tc = TILE_IDS[i]; - var value = tileconfig[i]; - for (var a = tc.length - 1; a >= 0; a--) { - var visible = (value & 1) == 1 ? true : false; + let tc = TILE_IDS[i]; + let value = tileconfig[i]; + for (let a = tc.length - 1; a >= 0; a--) { + let visible = (value & 1) == 1 ? true : false; value = value >>> 1; if (tc[a] != null && tc[a] != undefined && tc[a] != "") { - var obj = $("#" + tc[a]); + let obj = $("#" + tc[a]); if (visible) { //Only show if we have not force hidden it if (obj.hasClass(".hide") == false) { @@ -383,16 +373,16 @@ function refreshVisibleTiles() { function postTileVisibiltity() { $(".stat.vistile.hide").removeClass("vistile"); - var newconfig = []; - for (var index = 0; index < tileconfig.length; index++) { + let newconfig = []; + for (let index = 0; index < tileconfig.length; index++) { newconfig.push(0); } - for (var i = 0; i < TILE_IDS.length; i++) { - var tc = TILE_IDS[i]; - var value = 0; - var v = 0x8000; - for (var a = 0; a < tc.length; a++) { + for (let i = 0; i < TILE_IDS.length; i++) { + let tc = TILE_IDS[i]; + let value = 0; + let v = 0x8000; + for (let a = 0; a < tc.length; a++) { if (tc[a] != null && tc[a] != undefined && tc[a] != "") { if ($("#" + tc[a]).hasClass("vistile")) { value = value | v; @@ -404,8 +394,8 @@ function postTileVisibiltity() { newconfig[i] = value; } - var diff = false; - for (var index = 0; index < tileconfig.length; index++) { + let diff = false; + for (let index = 0; index < tileconfig.length; index++) { if (tileconfig[index] != newconfig[index]) { tileconfig[index] = newconfig[index]; diff = true; @@ -690,9 +680,7 @@ function queryBMS() { var markLineData = []; - markLineData.push({ name: 'avg', type: 'average', lineStyle: { color: '#ddd', width: 2, type: 'dotted', opacity: 0.3 }, label: { distance: [10, 0], position: 'start', color: "#eeeeee", textBorderColor: '#313131', textBorderWidth: 2 } }); - //markLineData.push({ name: 'min', type: 'min', lineStyle: { color: '#ddd', width: 2, type: 'dotted', opacity: 0.3 }, label: { distance: [10, 0], position: 'start', color: "#eeeeee", textBorderColor: '#313131', textBorderWidth: 2 } }); - //markLineData.push({ name: 'max', type: 'max', lineStyle: { color: '#ddd', width: 2, type: 'dotted', opacity: 0.3 }, label: { distance: [10, 0], position: 'start', color: "#eeeeee", textBorderColor: '#313131', textBorderWidth: 2 } }); + markLineData.push({ name: 'avg', type: 'average', lineStyle: { color: '#eee', width: 3, type: 'dotted', opacity: 0.4 }, label: { distance: [10, 0], position: 'start', color: "#eeeeee", textBorderColor: '#313131', textBorderWidth: 2 } }); var xAxis = 0; for (let index = 0; index < jsondata.banks; index++) { @@ -980,7 +968,7 @@ function queryBMS() { window.g1 = echarts.init(document.getElementById('graph1')) // specify chart configuration item and data - var option = { + let option = { tooltip: { show: true, axisPointer: { type: 'cross', label: { @@ -2343,13 +2331,11 @@ $(function () { DrawChargingGraph(); }); - //On page ready queryBMS(); - //Automatically open correct sub-page based on hash - var hash = $(location).attr('hash'); + let hash = $(location).attr('hash'); switch (hash) { case "#tiles": case "#modules": From d67f47683581b05d481d473741630fba35ffaba4 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:28:48 +0000 Subject: [PATCH 25/39] #270 remove voltage and temp labels if more than 24 cells in use (visibility) --- ESPController/web_src/default.htm | 2 +- ESPController/web_src/pagecode.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index fd11adab..eed3ef60 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -231,7 +231,7 @@
-
+
Graph: 2D diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index 5d9188dc..15dc2624 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -156,7 +156,7 @@ function DrawChargingGraph() { yaxisvalues.push(CalculateChargeCurrent(value1, value2, voltage, chargecurrent, kneemv, cellmaxmv)); } - if (window.g3 == null) { + if (window.g3 == null) { window.g3 = echarts.init(document.getElementById('graph3')) let option = { @@ -1141,6 +1141,14 @@ function queryBMS() { }] }; + + if (jsondata.voltages.length > 24) { + // When lots of cell data is on screen, hide the labels to improve visability + for (const element of option.series) { + element.label.normal.show = false; + } + } + // use configuration item and data specified to show chart g1.setOption(option); From 2a4fb276d8963bac662a3b31db727332f04f6079 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:37:48 +0100 Subject: [PATCH 26/39] Update STM32 framework version --- STM32All-In-One/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STM32All-In-One/platformio.ini b/STM32All-In-One/platformio.ini index a369aec8..5aaa08f8 100644 --- a/STM32All-In-One/platformio.ini +++ b/STM32All-In-One/platformio.ini @@ -12,7 +12,7 @@ default_envs = V490_AUTOBAUD_VREF4096, V490_AUTOBAUD_VREF4500 [env] -platform = platformio/ststm32@^17.0.0 +platform = platformio/ststm32@^17.3.0 board = genericSTM32F030K6T6 board_build.core = stm32 framework = arduino From 84531f71000ced5da2f00200eecb8fa151afadb8 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 3 May 2024 11:24:47 +0100 Subject: [PATCH 27/39] Write SOC into flash on 10min interval --- ESPController/src/main.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index e8e027a9..3e4d8d97 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -4152,7 +4152,7 @@ esp_err_t diagnosticJSON(httpd_req_t *req, char buffer[], int bufferLenMax) unsigned long wifitimer = 0; unsigned long heaptimer = 0; -// unsigned long taskinfotimer = 0; +uint8_t flash_write_soc_timer = 0; void logActualTime() { @@ -4166,9 +4166,10 @@ void logActualTime() void loop() { - delay(100); + //vTaskDelete(NULL); + delay(250); - unsigned long currentMillis = millis(); + auto currentMillis = millis(); if (card_action == CardAction::Mount) { @@ -4213,6 +4214,8 @@ void loop() if (currentMillis > heaptimer) { + // This gets called once per minute... + logActualTime(); /* total_free_bytes; Total free bytes in the heap. Equivalent to multi_free_heap_size(). @@ -4239,15 +4242,13 @@ void loop() // Report again in 60 seconds heaptimer = currentMillis + 60000; - // Once per minute, store the state of charge into flash, just in case the controller is rebooted and we can restore this value - // on power up. - if (mysettings.currentMonitoringEnabled && mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_INTERNAL) + // Once every 10 minutes, store the state of charge into flash, just in case the controller is rebooted and we can restore this value + // on power up. 10 minutes was chosen so we don't rapidly wear out the internal flash memory with writes. + flash_write_soc_timer++; + if (flash_write_soc_timer > 10 && currentmon_internal.calc_state_of_charge() > 1.0F && mysettings.currentMonitoringEnabled && mysettings.currentMonitoringDevice == CurrentMonitorDevice::DIYBMS_CURRENT_MON_INTERNAL) { - // Avoid writing zero SoC into flash - if (currentmon_internal.calc_state_of_charge() > 1.0F) - { - SaveStateOfCharge(currentmon_internal.raw_milliamphour_in(), currentmon_internal.raw_milliamphour_out()); - } + flash_write_soc_timer = 0; + SaveStateOfCharge(currentmon_internal.raw_milliamphour_in(), currentmon_internal.raw_milliamphour_out()); } } } From 59b9a90bd2f8b08da6f2e81105e5441d904b1e2b Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 3 May 2024 11:25:00 +0100 Subject: [PATCH 28/39] arduino-esp32 version 2.0.16 --- ESPController/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ESPController/platformio.ini b/ESPController/platformio.ini index 3c1f70df..7c4cba27 100644 --- a/ESPController/platformio.ini +++ b/ESPController/platformio.ini @@ -55,7 +55,7 @@ upload_speed=921600 upload_port=COM3 board_build.arduino.upstream_packages = no platform_packages = - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.14 + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.16 board_build.partitions = diybms_partitions.csv From f210e5be8b0781e24d69c028c44d4f0cadb641d9 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 3 May 2024 14:08:40 +0100 Subject: [PATCH 29/39] ArduinoJson 7.0.4 and TFT_eSPI 2.5.43 --- ESPController/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ESPController/platformio.ini b/ESPController/platformio.ini index 7c4cba27..3c4ed6b1 100644 --- a/ESPController/platformio.ini +++ b/ESPController/platformio.ini @@ -60,8 +60,8 @@ platform_packages = board_build.partitions = diybms_partitions.csv lib_deps = - bblanchon/ArduinoJson@^7.0.3 - bodmer/TFT_eSPI@^2.5.31 + bblanchon/ArduinoJson@^7.0.4 + bodmer/TFT_eSPI@^2.5.43 https://github.com/stuartpittaway/SerialEncoder.git [env:esp32-devkitc] From d28af051753aa6cebad14c1c86335eb3367ad19b Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:40:45 +0100 Subject: [PATCH 30/39] Update to latest 3rd party library versions --- ESPController/platformio.ini | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ESPController/platformio.ini b/ESPController/platformio.ini index 3c4ed6b1..25873c96 100644 --- a/ESPController/platformio.ini +++ b/ESPController/platformio.ini @@ -35,11 +35,12 @@ build_flags = [env] framework = arduino ; 4MB FLASH DEVKITC -platform = espressif32@~6.5.0 +; recommended to pin to a version, see https://github.com/platformio/platform-espressif32/releases +platform = espressif32@^6.8.1 board = esp32dev monitor_speed = 115200 -monitor_port=COM3 +monitor_port=COM4 monitor_filters = esp32_exception_decoder board_build.flash_mode = dout board_build.filesystem = littlefs @@ -52,15 +53,15 @@ extra_scripts = post:extract_bootloader.py upload_speed=921600 -upload_port=COM3 +upload_port=COM4 board_build.arduino.upstream_packages = no platform_packages = - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.16 + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.17 board_build.partitions = diybms_partitions.csv lib_deps = - bblanchon/ArduinoJson@^7.0.4 + bblanchon/ArduinoJson@^7.1.0 bodmer/TFT_eSPI@^2.5.43 https://github.com/stuartpittaway/SerialEncoder.git From 6e62d616aa642884cb6458c0baf8057ccf2c11d6 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:10:45 +0100 Subject: [PATCH 31/39] platformio/ststm32@^17.5.0 --- STM32All-In-One/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/STM32All-In-One/platformio.ini b/STM32All-In-One/platformio.ini index 5aaa08f8..3a3efa28 100644 --- a/STM32All-In-One/platformio.ini +++ b/STM32All-In-One/platformio.ini @@ -12,7 +12,7 @@ default_envs = V490_AUTOBAUD_VREF4096, V490_AUTOBAUD_VREF4500 [env] -platform = platformio/ststm32@^17.3.0 +platform = platformio/ststm32@^17.5.0 board = genericSTM32F030K6T6 board_build.core = stm32 framework = arduino @@ -29,7 +29,7 @@ lib_deps = monitor_port=COM3 monitor_speed=115200 -upload_port = COM4 +upload_port = COM5 debug_tool = stlink upload_protocol = stlink ;upload_protocol = serial From 5c7cd6e9c93d075bfdd28a7694490c84b566d9e8 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:35:00 +0100 Subject: [PATCH 32/39] Update main.yml --- .github/workflows/main.yml | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8d07fe39..fd485029 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,29 +10,13 @@ jobs: job_build_modulecode: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Cache pip - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: ${{ runner.os }}-pip- - - - name: Cache PlatformIO - uses: actions/cache@v3 - with: - path: ~/.platformio - key: ${{ runner.os }}-platformio-2022-${{ hashFiles('**/lockfiles') }} + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 - - - uses: actions/cache@v3 + uses: actions/setup-python@v5 with: - path: ~/.local/share/virtualenvs - key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }} - restore-keys: ${{ runner.os }}-pipenv- + python-version: '3.9' + cache: 'pip' # caching pip dependencies - name: Install PlatformIO run: | From f4cbaa99474ab32153dc68458d78fc0998c29c45 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:36:05 +0100 Subject: [PATCH 33/39] Update main.yml --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd485029..9fe4602c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,6 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.9' - cache: 'pip' # caching pip dependencies - name: Install PlatformIO run: | From e5556b8b37a95c6c197bbcb376fd7ff3a772a722 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:46:11 +0100 Subject: [PATCH 34/39] Update main.yml --- .github/workflows/main.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9fe4602c..0fa89989 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,16 +12,22 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Cache pio + uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + key: ${{ runner.os }}-pio + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.11' - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - + run: pip install --upgrade platformio + - name: Create folders run: | mkdir -p ./ESPController/data/avr From 589eccaece7a82ae51f20c7cf740d03141d57db7 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:01:00 +0100 Subject: [PATCH 35/39] Update main.yml --- .github/workflows/main.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0fa89989..010a322b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: - name: Install PlatformIO run: pip install --upgrade platformio - + - name: Create folders run: | mkdir -p ./ESPController/data/avr @@ -54,13 +54,6 @@ jobs: run: | cp ./STM32All-In-One/.pio/build/V*/*.bin ~/OUTPUT/Modules/BIN/ - - name: Get latest esptool - run: | - git clone https://github.com/espressif/esptool.git - cd esptool - pip install --user -e . - cd ~ - python -m esptool version - name: Build code for ESP32 controller run: pio run --project-dir=/home/runner/work/diyBMSv4ESP32/diyBMSv4ESP32/ESPController --environment esp32-devkitc --project-conf=/home/runner/work/diyBMSv4ESP32/diyBMSv4ESP32/ESPController/platformio.ini @@ -81,8 +74,12 @@ jobs: run: | cp ./ESPController/.pio/build/esp32-devkitc/diybms_controller_filesystemimage_espressif32_esp32-devkitc.bin ~/OUTPUT/Controller/ - - name: Build single ESP32 image + - name: Get latest esptool / Build single ESP32 image run: | + git clone https://github.com/espressif/esptool.git + cd esptool + pip install --user -e . + cd ~ python -m esptool --chip esp32 merge_bin -o ~/OUTPUT/Controller/esp32-controller-firmware-complete.bin --flash_mode=keep --flash_size 4MB 0x1000 ~/OUTPUT/Controller/bootloader.bin 0x8000 ~/OUTPUT/Controller/partitions.bin 0xe000 ~/OUTPUT/Controller/boot_app0.bin 0x10000 ~/OUTPUT/Controller/diybms_controller_firmware_espressif32_esp32-devkitc.bin 0x1C0000 ~/OUTPUT/Controller/diybms_controller_firmware_espressif32_esp32-devkitc.bin 0x370000 ~/OUTPUT/Controller/diybms_controller_filesystemimage_espressif32_esp32-devkitc.bin - name: Board test code for ESP32 controller From bc17dd839ebbddfd670e2dac576721cc08accc2a Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:13:48 +0100 Subject: [PATCH 36/39] Update pagecode.js --- ESPController/web_src/pagecode.js | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index 15dc2624..700b8bb1 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -540,7 +540,7 @@ function configureModule(button, cellid, attempts) { $('#m').val(data.settings.id); if (data.settings.Cached == true) { - var currentReading = parseFloat($("#modulesRows > tr.selected > td:nth-child(3)").text()); + const currentReading = parseFloat($("#modulesRows > tr.selected > td:nth-child(3)").text()); $("#ActualVoltage").val(currentReading.toFixed(3)); $("#settingConfig h2").html("Settings for module bank:" + data.settings.bank + " module:" + data.settings.module); @@ -632,15 +632,15 @@ function secondsToHms(seconds) { return ""; } - var d = Math.floor(seconds / (3600 * 24)); - var h = Math.floor(seconds % (3600 * 24) / 3600); - var m = Math.floor(seconds % 3600 / 60); - var s = Math.floor(seconds % 60); + let d = Math.floor(seconds / (3600 * 24)); + let h = Math.floor(seconds % (3600 * 24) / 3600); + let m = Math.floor(seconds % 3600 / 60); + let s = Math.floor(seconds % 60); - var dDisplay = d > 0 ? d + "d" : ""; - var hDisplay = h > 0 ? h + "h" : ""; - var mDisplay = m > 0 ? m + "m" : ""; - var sDisplay = h > 0 ? "" : (s > 0 ? s + "s" : ""); + let dDisplay = d > 0 ? d + "d" : ""; + let hDisplay = h > 0 ? h + "h" : ""; + let mDisplay = m > 0 ? m + "m" : ""; + let sDisplay = h > 0 ? "" : (s > 0 ? s + "s" : ""); return dDisplay + hDisplay + mDisplay + sDisplay; } @@ -1222,16 +1222,16 @@ function queryBMS() { var cells3d = []; var banks3d = []; - for (var seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { + for (let seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { cells3d.push({ value: 'Cell ' + seriesmodules, textStyle: { color: '#ffffff' } }); } var data3d = []; var cell = 0; - for (var bankNumber = 0; bankNumber < jsondata.banks; bankNumber++) { + for (let bankNumber = 0; bankNumber < jsondata.banks; bankNumber++) { banks3d.push({ value: 'Bank ' + bankNumber, textStyle: { color: '#ffffff' } }); //Build up 3d array for cell data - for (var seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { + for (let seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { data3d.push({ value: [seriesmodules, bankNumber, voltages[cell].value], itemStyle: voltages[cell].itemStyle }); cell++; } @@ -1316,7 +1316,7 @@ $(function () { ); $(".rule").on("change", function () { - var origv = $(this).attr("data-origv") + let origv = $(this).attr("data-origv") if (origv !== this.value) { $(this).addClass("modified"); } else { @@ -1325,10 +1325,10 @@ $(function () { }); $("#labelMaxModules").text(MAXIMUM_NUMBER_OF_SERIES_MODULES); - for (var n = 1; n <= MAXIMUM_NUMBER_OF_SERIES_MODULES; n++) { + for (let n = 1; n <= MAXIMUM_NUMBER_OF_SERIES_MODULES; n++) { $("#totalSeriesModules").append('') } - for (var n = MAXIMUM_NUMBER_OF_BANKS - 1; n >= 0; n--) { + for (let n = MAXIMUM_NUMBER_OF_BANKS - 1; n >= 0; n--) { $("#totalBanks").prepend('') $("#info").prepend('
Range ' + n + ':
'); $("#info").prepend('
Voltage ' + n + ':
'); @@ -1352,10 +1352,10 @@ $(function () { }); $('#CalculateCalibration').click(function () { - var currentReading = parseFloat($("#modulesRows > tr.selected > td:nth-child(3)").text()); - var currentCalib = parseFloat($("#Calib").val()); - var actualV = parseFloat($("#ActualVoltage").val()); - var result = (currentCalib / currentReading) * actualV; + const currentReading = parseFloat($("#modulesRows > tr.selected > td:nth-child(3)").text()); + const currentCalib = parseFloat($("#Calib").val()); + const actualV = parseFloat($("#ActualVoltage").val()); + let result = (currentCalib / currentReading) * actualV; $("#Calib").val(result.toFixed(4)); return true; }); From 0754232a677ee946cc63e0a6711b9e3d34d5b2e6 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:13:52 +0100 Subject: [PATCH 37/39] Update main.yml --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 010a322b..1d91653e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -90,7 +90,7 @@ jobs: cp ./ESP32BoardTest/.pio/build/esp32-devkitc/diybms_boardtest_espressif32_esp32-devkitc.bin ~/OUTPUT/ControllerBoardTest/ - name: Publish Artifacts 1 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: DIYBMS-Compiled path: ~/OUTPUT @@ -101,7 +101,7 @@ jobs: needs: [job_build_modulecode] steps: - name: Download artifact DIYBMS-Compiled - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: DIYBMS-Compiled @@ -128,7 +128,7 @@ jobs: run: mv release.zip release_${{ env.dt }}.zip - name: Publish Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: DIYBMS-Release-Artifact-${{ env.dt }} path: | From 2240a64ee88d92d5bbc51fa225d7cf1f3aea8fdf Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:54:07 +0100 Subject: [PATCH 38/39] Update pagecode.js --- ESPController/web_src/pagecode.js | 1034 +++++++++++++++-------------- 1 file changed, 518 insertions(+), 516 deletions(-) diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index 700b8bb1..79dc038b 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -644,624 +644,626 @@ function secondsToHms(seconds) { return dDisplay + hDisplay + mDisplay + sDisplay; } -function queryBMS() { - $.getJSON("/api/monitor2", function (jsondata) { - var labels = []; - var cells = []; - var bank = []; - var voltages = []; - var voltagesmin = []; - var voltagesmax = []; - var tempint = []; - var tempext = []; - var pwm = []; - - var minVoltage = DEFAULT_GRAPH_MIN_VOLTAGE / 1000.0; - var maxVoltage = DEFAULT_GRAPH_MAX_VOLTAGE / 1000.0; - - var minExtTemp = 999; - var maxExtTemp = -999; - - var bankNumber = 0; - var cellsInBank = 0; - - // Need one color for each bank, could make it colourful I suppose :-) - const colours = [ - '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', - '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', - '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', - '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', - ] - - const red = '#B44247' - - const highestCell = '#8c265d' - const lowestCell = '#b6a016' - - var markLineData = []; - - markLineData.push({ name: 'avg', type: 'average', lineStyle: { color: '#eee', width: 3, type: 'dotted', opacity: 0.4 }, label: { distance: [10, 0], position: 'start', color: "#eeeeee", textBorderColor: '#313131', textBorderWidth: 2 } }); - - var xAxis = 0; - for (let index = 0; index < jsondata.banks; index++) { - markLineData.push({ name: "Bank " + index, xAxis: xAxis, lineStyle: { color: colours[index], width: 4, type: 'dashed', opacity: 0.5 }, label: { show: true, distance: [0, 0], formatter: '{b}', color: '#eeeeee', textBorderColor: colours[index], textBorderWidth: 2 } }); - xAxis += jsondata.seriesmodules; - } +function updateChart(jsondata) { + + let labels = []; + let cells = []; + let bank = []; + let voltages = []; + let voltagesmin = []; + let voltagesmax = []; + let tempint = []; + let tempext = []; + let pwm = []; + + let minVoltage = DEFAULT_GRAPH_MIN_VOLTAGE / 1000.0; + let maxVoltage = DEFAULT_GRAPH_MAX_VOLTAGE / 1000.0; + + let minExtTemp = 999; + let maxExtTemp = -999; + + var bankNumber = 0; + let cellsInBank = 0; + + // Need one color for each bank, could make it colourful I suppose :-) + const colours = [ + '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', + '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', + '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', + '#55a1ea', '#33628f', '#498FD0', '#6D8EA0', + ] + + const red = '#B44247' + + const highestCell = '#8c265d' + const lowestCell = '#b6a016' + + let markLineData = []; + + markLineData.push({ name: 'avg', type: 'average', lineStyle: { color: '#eee', width: 3, type: 'dotted', opacity: 0.4 }, label: { distance: [10, 0], position: 'start', color: "#eeeeee", textBorderColor: '#313131', textBorderWidth: 2 } }); + + let xAxis = 0; + for (let index = 0; index < jsondata.banks; index++) { + markLineData.push({ name: "Bank " + index, xAxis: xAxis, lineStyle: { color: colours[index], width: 4, type: 'dashed', opacity: 0.5 }, label: { show: true, distance: [0, 0], formatter: '{b}', color: '#eeeeee', textBorderColor: colours[index], textBorderWidth: 2 } }); + xAxis += jsondata.seriesmodules; + } - if (jsondata.voltages) { - //Clone array of voltages - tempArray = []; - for (i = 0; i < jsondata.voltages.length; i++) { - tempArray[i] = jsondata.voltages[i]; - } + if (jsondata.voltages) { + //Clone array of voltages + let tempArray = []; + for (i = 0; i < jsondata.voltages.length; i++) { + tempArray[i] = jsondata.voltages[i]; + } - //Split voltages into banks - sorted_voltages = []; - for (i = 0; i < jsondata.banks; i++) { - unsorted = tempArray.splice(0, jsondata.seriesmodules); - sorted_voltages.push(unsorted.sort()); - } + //Split voltages into banks + let sorted_voltages = []; + for (i = 0; i < jsondata.banks; i++) { + let unsorted = tempArray.splice(0, jsondata.seriesmodules); + sorted_voltages.push(unsorted.sort()); + } - for (let i = 0; i < jsondata.voltages.length; i++) { - labels.push(bankNumber + "/" + i); + for (let i = 0; i < jsondata.voltages.length; i++) { + labels.push(bankNumber + "/" + i); - // Make different banks different colours (stripes) - var stdcolor = colours[bankNumber]; + // Make different banks different colours (stripes) + let stdcolor = colours[bankNumber]; - var color = stdcolor; + let color = stdcolor; - //Highlight lowest cell voltage in this bank - if (jsondata.voltages[i] === sorted_voltages[bankNumber][0]) { - color = lowestCell; - } - //Highlight highest cell voltage in this bank - if (jsondata.voltages[i] === sorted_voltages[bankNumber][jsondata.seriesmodules - 1]) { - color = highestCell; - } - // Red - if (jsondata.bypass[i] === 1) { - color = red; - } + //Highlight lowest cell voltage in this bank + if (jsondata.voltages[i] === sorted_voltages[bankNumber][0]) { + color = lowestCell; + } + //Highlight highest cell voltage in this bank + if (jsondata.voltages[i] === sorted_voltages[bankNumber][jsondata.seriesmodules - 1]) { + color = highestCell; + } + // Red + if (jsondata.bypass[i] === 1) { + color = red; + } - var v = (parseFloat(jsondata.voltages[i]) / 1000.0); - voltages.push({ value: v, itemStyle: { color: color } }); + const v = (parseFloat(jsondata.voltages[i]) / 1000.0); + voltages.push({ value: v, itemStyle: { color: color } }); - //Auto scale graph is outside of normal bounds - if (v > maxVoltage) { maxVoltage = v; } - if (v < minVoltage) { minVoltage = v; } + //Auto scale graph is outside of normal bounds + if (v > maxVoltage) { maxVoltage = v; } + if (v < minVoltage) { minVoltage = v; } - if (jsondata.minvoltages) { - voltagesmin.push((parseFloat(jsondata.minvoltages[i]) / 1000.0)); - } - if (jsondata.maxvoltages) { - voltagesmax.push((parseFloat(jsondata.maxvoltages[i]) / 1000.0)); - } + if (jsondata.minvoltages) { + voltagesmin.push((parseFloat(jsondata.minvoltages[i]) / 1000.0)); + } + if (jsondata.maxvoltages) { + voltagesmax.push((parseFloat(jsondata.maxvoltages[i]) / 1000.0)); + } - bank.push(bankNumber); - cells.push(i); + bank.push(bankNumber); + cells.push(i); - cellsInBank++; - if (cellsInBank == jsondata.seriesmodules) { - cellsInBank = 0; - bankNumber++; - } + cellsInBank++; + if (cellsInBank == jsondata.seriesmodules) { + cellsInBank = 0; + bankNumber++; + } - color = jsondata.bypasshot[i] == 1 ? red : stdcolor; - tempint.push({ value: jsondata.inttemp[i], itemStyle: { color: color } }); - var exttemp = (jsondata.exttemp[i] == -40 ? 0 : jsondata.exttemp[i]); - tempext.push({ value: exttemp, itemStyle: { color: stdcolor } }); + color = jsondata.bypasshot[i] == 1 ? red : stdcolor; + tempint.push({ value: jsondata.inttemp[i], itemStyle: { color: color } }); + let exttemp = (jsondata.exttemp[i] == -40 ? 0 : jsondata.exttemp[i]); + tempext.push({ value: exttemp, itemStyle: { color: stdcolor } }); - if (jsondata.exttemp[i] != null) { - if (exttemp > maxExtTemp) { - maxExtTemp = exttemp; - } - if (exttemp < minExtTemp) { - minExtTemp = exttemp; - } + if (jsondata.exttemp[i] != null) { + if (exttemp > maxExtTemp) { + maxExtTemp = exttemp; + } + if (exttemp < minExtTemp) { + minExtTemp = exttemp; } + } - pwm.push({ value: jsondata.bypasspwm[i] == 0 ? null : Math.trunc(jsondata.bypasspwm[i] / 255 * 100) }); - } + pwm.push({ value: jsondata.bypasspwm[i] == 0 ? null : Math.trunc(jsondata.bypasspwm[i] / 255 * 100) }); } + } - //Scale down for low voltages - if (minVoltage < 0) { minVoltage = 0; } - - if (jsondata) { - $("#badcrc .v").html(jsondata.badcrc); - $("#ignored .v").html(jsondata.ignored); - $("#sent .v").html(jsondata.sent); - $("#received .v").html(jsondata.received); - $("#roundtrip .v").html(jsondata.roundtrip); - $("#oos .v").html(jsondata.oos); - $("#canfail .v").html(jsondata.can_fail); - $("#canrecerr .v").html(jsondata.can_r_err); - $("#cansent .v").html(jsondata.can_sent); - $("#canrecd .v").html(jsondata.can_rec); - $("#qlen .v").html(jsondata.qlen); - $("#uptime .v").html(secondsToHms(jsondata.uptime)); - if (minExtTemp == 999 || maxExtTemp == -999) { - $("#celltemp .v").html(""); - } else { - $("#celltemp .v").html(minExtTemp + "/" + maxExtTemp + "°C"); - } + //Scale down for low voltages + if (minVoltage < 0) { minVoltage = 0; } + + if (jsondata) { + $("#badcrc .v").html(jsondata.badcrc); + $("#ignored .v").html(jsondata.ignored); + $("#sent .v").html(jsondata.sent); + $("#received .v").html(jsondata.received); + $("#roundtrip .v").html(jsondata.roundtrip); + $("#oos .v").html(jsondata.oos); + $("#canfail .v").html(jsondata.can_fail); + $("#canrecerr .v").html(jsondata.can_r_err); + $("#cansent .v").html(jsondata.can_sent); + $("#canrecd .v").html(jsondata.can_rec); + $("#qlen .v").html(jsondata.qlen); + $("#uptime .v").html(secondsToHms(jsondata.uptime)); + if (minExtTemp == 999 || maxExtTemp == -999) { + $("#celltemp .v").html(""); + } else { + $("#celltemp .v").html(minExtTemp + "/" + maxExtTemp + "°C"); + } - if (jsondata.activerules == 0) { - $("#activerules").hide(); - } else { - $("#activerules").html(jsondata.activerules); - $("#activerules").show(400); - } + if (jsondata.activerules == 0) { + $("#activerules").hide(); + } else { + $("#activerules").html(jsondata.activerules); + $("#activerules").show(400); + } - if (jsondata.dyncv) { - $("#dyncvolt .v").html(parseFloat(jsondata.dyncv / 10).toFixed(2) + "V"); - } else { $("#dyncvolt .v").html(""); } + if (jsondata.dyncv) { + $("#dyncvolt .v").html(parseFloat(jsondata.dyncv / 10).toFixed(2) + "V"); + } else { $("#dyncvolt .v").html(""); } - if (jsondata.dyncc) { - $("#dynccurr .v").html(parseFloat(jsondata.dyncc / 10).toFixed(2) + "A"); - } else { $("#dynccurr .v").html(""); } + if (jsondata.dyncc) { + $("#dynccurr .v").html(parseFloat(jsondata.dyncc / 10).toFixed(2) + "A"); + } else { $("#dynccurr .v").html(""); } - switch (jsondata.cmode) { - case 0: $("#chgmode .v").html("Standard"); break; - case 1: $("#chgmode .v").html("Absorb " + secondsToHms(jsondata.ctime)); break; - case 2: $("#chgmode .v").html("Float " + secondsToHms(jsondata.ctime)); break; - case 3: $("#chgmode .v").html("Dynamic"); break; - case 4: $("#chgmode .v").html("Stopped"); break; - default: $("#chgmode .v").html("Unknown"); - } + switch (jsondata.cmode) { + case 0: $("#chgmode .v").html("Standard"); break; + case 1: $("#chgmode .v").html("Absorb " + secondsToHms(jsondata.ctime)); break; + case 2: $("#chgmode .v").html("Float " + secondsToHms(jsondata.ctime)); break; + case 3: $("#chgmode .v").html("Dynamic"); break; + case 4: $("#chgmode .v").html("Stopped"); break; + default: $("#chgmode .v").html("Unknown"); } + } - if (jsondata.bankv) { - for (var bankNumber = 0; bankNumber < jsondata.bankv.length; bankNumber++) { - $("#voltage" + bankNumber + " .v").html((parseFloat(jsondata.bankv[bankNumber]) / 1000.0).toFixed(2) + "V"); - $("#range" + bankNumber + " .v").html(jsondata.voltrange[bankNumber] + "mV"); - $("#voltage" + bankNumber).removeClass("hide"); - $("#range" + bankNumber).removeClass("hide"); - } + if (jsondata.bankv) { + for (let bankNumber = 0; bankNumber < jsondata.bankv.length; bankNumber++) { + $("#voltage" + bankNumber + " .v").html((parseFloat(jsondata.bankv[bankNumber]) / 1000.0).toFixed(2) + "V"); + $("#range" + bankNumber + " .v").html(jsondata.voltrange[bankNumber] + "mV"); + $("#voltage" + bankNumber).removeClass("hide"); + $("#range" + bankNumber).removeClass("hide"); + } - for (var bankNumber = jsondata.bankv.length; bankNumber < MAXIMUM_NUMBER_OF_BANKS; bankNumber++) { - $("#voltage" + bankNumber).hide().addClass("hide"); - $("#range" + bankNumber).hide().addClass("hide"); - } + for (let bankNumber = jsondata.bankv.length; bankNumber < MAXIMUM_NUMBER_OF_BANKS; bankNumber++) { + $("#voltage" + bankNumber).hide().addClass("hide"); + $("#range" + bankNumber).hide().addClass("hide"); } + } - if (jsondata.current) { - if (jsondata.current[0] == null) { - $("#current .v").html(""); - $("#shuntv .v").html(""); - $("#soc .v").html(""); - $("#power .v").html(""); - $("#amphout .v").html(""); - $("#amphin .v").html(""); - $("#damphout .v").html(""); - $("#damphin .v").html(""); - $("#time100 .v").html(""); - $("#time10 .v").html(""); - $("#time20 .v").html(""); - } else { - var data = jsondata.current[0]; - $("#current .v").html(parseFloat(data.c).toFixed(2) + "A"); - $("#shuntv .v").html(parseFloat(data.v).toFixed(2) + "V"); - $("#soc .v").html(parseFloat(data.soc).toFixed(2) + "%"); - $("#power .v").html(parseFloat(data.p) + "W"); - $("#amphout .v").html((parseFloat(data.mahout) / 1000).toFixed(3)); - $("#amphin .v").html((parseFloat(data.mahin) / 1000).toFixed(3)); - $("#damphout .v").html((parseFloat(data.dmahout) / 1000).toFixed(3)); - $("#damphin .v").html((parseFloat(data.dmahin) / 1000).toFixed(3)); - - if (data.time100 > 0) { - $("#time100 .v").html(secondsToHms(data.time100)); - } else { $("#time100 .v").html("∞"); } - if (data.time20 > 0) { - $("#time20 .v").html(secondsToHms(data.time20)); - } else { $("#time20 .v").html("∞"); } - if (data.time10 > 0) { - $("#time10 .v").html(secondsToHms(data.time10)); - } else { $("#time10 .v").html("∞"); } - } + if (jsondata.current) { + if (jsondata.current[0] == null) { + $("#current .v").html(""); + $("#shuntv .v").html(""); + $("#soc .v").html(""); + $("#power .v").html(""); + $("#amphout .v").html(""); + $("#amphin .v").html(""); + $("#damphout .v").html(""); + $("#damphin .v").html(""); + $("#time100 .v").html(""); + $("#time10 .v").html(""); + $("#time20 .v").html(""); + } else { + var data = jsondata.current[0]; + $("#current .v").html(parseFloat(data.c).toFixed(2) + "A"); + $("#shuntv .v").html(parseFloat(data.v).toFixed(2) + "V"); + $("#soc .v").html(parseFloat(data.soc).toFixed(2) + "%"); + $("#power .v").html(parseFloat(data.p) + "W"); + $("#amphout .v").html((parseFloat(data.mahout) / 1000).toFixed(3)); + $("#amphin .v").html((parseFloat(data.mahin) / 1000).toFixed(3)); + $("#damphout .v").html((parseFloat(data.dmahout) / 1000).toFixed(3)); + $("#damphin .v").html((parseFloat(data.dmahin) / 1000).toFixed(3)); + + if (data.time100 > 0) { + $("#time100 .v").html(secondsToHms(data.time100)); + } else { $("#time100 .v").html("∞"); } + if (data.time20 > 0) { + $("#time20 .v").html(secondsToHms(data.time20)); + } else { $("#time20 .v").html("∞"); } + if (data.time10 > 0) { + $("#time10 .v").html(secondsToHms(data.time10)); + } else { $("#time10 .v").html("∞"); } } + } - //Loop size needs increasing when more warnings are added - if (jsondata.warnings) { - for (let warning = 1; warning <= 9; warning++) { - if (jsondata.warnings.includes(warning)) { - //Once a warning has triggered, hide it from showing in the future - if ($("#warning" + warning).data("notify") == undefined) { - $("#warning" + warning).data("notify", 1); - $.notify($("#warning" + warning).text(), { autoHideDelay: 15000, globalPosition: 'top left', className: 'warn' }); - } + //Loop size needs increasing when more warnings are added + if (jsondata.warnings) { + for (let warning = 1; warning <= 9; warning++) { + if (jsondata.warnings.includes(warning)) { + //Once a warning has triggered, hide it from showing in the future + if ($("#warning" + warning).data("notify") == undefined) { + $("#warning" + warning).data("notify", 1); + $.notify($("#warning" + warning).text(), { autoHideDelay: 15000, globalPosition: 'top left', className: 'warn' }); } } + } - //Allow charge/discharge warnings to reappear - if (jsondata.warnings.includes(7) == false) { - $("#warning7").removeData("notify"); - } - if (jsondata.warnings.includes(8) == false) { - $("#warning8").removeData("notify"); - } + //Allow charge/discharge warnings to reappear + if (jsondata.warnings.includes(7) == false) { + $("#warning7").removeData("notify"); } + if (jsondata.warnings.includes(8) == false) { + $("#warning8").removeData("notify"); + } + } - //Needs increasing when more errors are added - if (jsondata.errors) { - for (let error = 1; error <= 7; error++) { - if (jsondata.errors.includes(error)) { - $("#error" + error).show(); + //Needs increasing when more errors are added + if (jsondata.errors) { + for (let error = 1; error <= 7; error++) { + if (jsondata.errors.includes(error)) { + $("#error" + error).show(); - if (error == INTERNALERRORCODE.ModuleCountMismatch) { - $("#missingmodule1").html(jsondata.modulesfnd); - $("#missingmodule2").html(jsondata.banks * jsondata.seriesmodules); - } - } else { - $("#error" + error).hide(); + if (error == INTERNALERRORCODE.ModuleCountMismatch) { + $("#missingmodule1").html(jsondata.modulesfnd); + $("#missingmodule2").html(jsondata.banks * jsondata.seriesmodules); } + } else { + $("#error" + error).hide(); } } + } - $("#info").show(); - $("#iperror").hide(); + $("#info").show(); + $("#iperror").hide(); - if ($('#modulesPage').is(':visible')) { - //The modules page is visible - var tbody = $("#modulesRows"); + if ($('#modulesPage').is(':visible')) { + //The modules page is visible + var tbody = $("#modulesRows"); - if ($('#modulesRows tr').length != cells.length) { - $("#settingConfig").hide(); + if ($('#modulesRows tr').length != cells.length) { + $("#settingConfig").hide(); - //Add rows if they dont exist (or incorrect amount) - $(tbody).find("tr").remove(); + //Add rows if they dont exist (or incorrect amount) + $(tbody).find("tr").remove(); - $.each(cells, function (index, value) { - $(tbody).append("" - + bank[index] - + "" + value + "" - + "" - + "" - + "") - }); + $.each(cells, function (index, value) { + $(tbody).append("" + + bank[index] + + "" + value + "" + + "" + + "" + + "") + }); + } + + var rows = $(tbody).find("tr"); + + $.each(cells, function (index, value) { + var columns = $(rows[index]).find("td"); + $(columns[2]).html(voltages[index].value.toFixed(3)); + if (voltagesmin.length > 0) { + $(columns[3]).html(voltagesmin[index].toFixed(3)); + } else { + $(columns[3]).html("n/a"); + } + if (voltagesmax.length > 0) { + $(columns[4]).html(voltagesmax[index].toFixed(3)); + } else { + $(columns[4]).html("n/a"); } + $(columns[5]).html(tempint[index].value); + $(columns[6]).html(tempext[index].value); + $(columns[7]).html(pwm[index].value); + }); - var rows = $(tbody).find("tr"); + //As the module page is open, we refresh the last 3 columns using seperate JSON web service to keep the monitor2 + //packets as small as possible + $.getJSON("/api/monitor3", function (jsondata) { + var tbody = $("#modulesRows"); + var rows = $(tbody).find("tr"); $.each(cells, function (index, value) { var columns = $(rows[index]).find("td"); - $(columns[2]).html(voltages[index].value.toFixed(3)); - if (voltagesmin.length > 0) { - $(columns[3]).html(voltagesmin[index].toFixed(3)); - } else { - $(columns[3]).html("n/a"); - } - if (voltagesmax.length > 0) { - $(columns[4]).html(voltagesmax[index].toFixed(3)); - } else { - $(columns[4]).html("n/a"); - } - $(columns[5]).html(tempint[index].value); - $(columns[6]).html(tempext[index].value); - $(columns[7]).html(pwm[index].value); + $(columns[8]).html(jsondata.badpacket[index]); + $(columns[9]).html(jsondata.pktrecvd[index]); + $(columns[10]).html(jsondata.balcurrent[index]); }); - - //As the module page is open, we refresh the last 3 columns using seperate JSON web service to keep the monitor2 - //packets as small as possible - - $.getJSON("/api/monitor3", function (jsondata) { - var tbody = $("#modulesRows"); - var rows = $(tbody).find("tr"); - $.each(cells, function (index, value) { - var columns = $(rows[index]).find("td"); - $(columns[8]).html(jsondata.badpacket[index]); - $(columns[9]).html(jsondata.pktrecvd[index]); - $(columns[10]).html(jsondata.balcurrent[index]); - }); - }); - } + }); + } - if ($('#homePage').is(':visible')) { - if (window.g1 == null && $('#graph1').css('display') != 'none') { - // based on prepared DOM, initialize echarts instance - window.g1 = echarts.init(document.getElementById('graph1')) + if ($('#homePage').is(':visible')) { + if (window.g1 == null && $('#graph1').css('display') != 'none') { + // based on prepared DOM, initialize echarts instance + window.g1 = echarts.init(document.getElementById('graph1')) - // specify chart configuration item and data - let option = { - tooltip: { - show: true, axisPointer: { - type: 'cross', label: { - backgroundColor: '#6a7985' - } + // specify chart configuration item and data + let option = { + tooltip: { + show: true, axisPointer: { + type: 'cross', label: { + backgroundColor: '#6a7985' + } + } + }, + legend: { + show: false + }, + xAxis: [{ + gridIndex: 0, type: 'category', axisLine: { + lineStyle: { + color: '#c1bdbd' + } + } + }, { + gridIndex: 1, type: 'category', axisLine: { + lineStyle: { color: '#c1bdbd' } + } + }], + yAxis: [{ + id: 0, gridIndex: 0, name: 'Volts', type: 'value', min: 2.5, max: 4.5, interval: 0.25, position: 'left', + axisLine: { + lineStyle: { + color: '#c1bdbd' } }, - legend: { - show: false + axisLabel: { + formatter: function (value, index) { + return value.toFixed(2); + } + } + }, + { + id: 1, + gridIndex: 0, name: 'Bypass', type: 'value', min: 0, + max: 100, interval: 10, position: 'right', + axisLabel: { formatter: '{value}%' }, + splitLine: { show: false }, + axisLine: { lineStyle: { type: 'dotted', color: '#c1bdbd' } }, + axisTick: { show: false } + }, + { + id: 2, + gridIndex: 1, + name: 'Temperature', + type: 'value', + interval: 10, + position: 'left', + axisLine: { + lineStyle: { color: '#c1bdbd' } }, - xAxis: [{ - gridIndex: 0, type: 'category', axisLine: { - lineStyle: { - color: '#c1bdbd' + axisLabel: { formatter: '{value}°C' } + }], + series: [ + { + xAxisIndex: 0, + name: 'Voltage', + yAxisIndex: 0, + type: 'bar', + data: [], + markLine: { + silent: true, symbol: 'none', data: markLineData + }, + itemStyle: { color: '#55a1ea', barBorderRadius: [8, 8, 0, 0] }, + label: { + normal: { + show: true, position: 'insideBottom', distance: 10, align: 'left', verticalAlign: 'middle', rotate: 90, formatter: '{c}V', fontSize: 24, color: '#eeeeee', fontFamily: 'Share Tech Mono' } } }, { - gridIndex: 1, type: 'category', axisLine: { - lineStyle: { color: '#c1bdbd' } - } - }], - yAxis: [{ - id: 0, gridIndex: 0, name: 'Volts', type: 'value', min: 2.5, max: 4.5, interval: 0.25, position: 'left', - axisLine: { - lineStyle: { - color: '#c1bdbd' + xAxisIndex: 0, + name: 'Min V', + yAxisIndex: 0, + type: 'line', + data: [], + label: { + normal: { + show: true, position: 'bottom', distance: 5, formatter: '{c}V', fontSize: 14, color: '#eeeeee', fontFamily: 'Share Tech Mono' } }, - axisLabel: { - formatter: function (value, index) { - return value.toFixed(2); + symbolSize: 16, + symbol: ['circle'], + itemStyle: { + normal: { + color: "#c1bdbd", lineStyle: { color: 'transparent' } } } - }, - { - id: 1, - gridIndex: 0, name: 'Bypass', type: 'value', min: 0, - max: 100, interval: 10, position: 'right', - axisLabel: { formatter: '{value}%' }, - splitLine: { show: false }, - axisLine: { lineStyle: { type: 'dotted', color: '#c1bdbd' } }, - axisTick: { show: false } - }, - { - id: 2, - gridIndex: 1, - name: 'Temperature', - type: 'value', - interval: 10, - position: 'left', - axisLine: { - lineStyle: { color: '#c1bdbd' } - }, - axisLabel: { formatter: '{value}°C' } - }], - series: [ - { - xAxisIndex: 0, - name: 'Voltage', - yAxisIndex: 0, - type: 'bar', - data: [], - markLine: { - silent: true, symbol: 'none', data: markLineData - }, - itemStyle: { color: '#55a1ea', barBorderRadius: [8, 8, 0, 0] }, - label: { - normal: { - show: true, position: 'insideBottom', distance: 10, align: 'left', verticalAlign: 'middle', rotate: 90, formatter: '{c}V', fontSize: 24, color: '#eeeeee', fontFamily: 'Share Tech Mono' - } - } - }, { - xAxisIndex: 0, - name: 'Min V', - yAxisIndex: 0, - type: 'line', - data: [], - label: { - normal: { - show: true, position: 'bottom', distance: 5, formatter: '{c}V', fontSize: 14, color: '#eeeeee', fontFamily: 'Share Tech Mono' - } - }, - symbolSize: 16, - symbol: ['circle'], - itemStyle: { - normal: { - color: "#c1bdbd", lineStyle: { color: 'transparent' } - } + } + , { + xAxisIndex: 0, + name: 'Max V', + yAxisIndex: 0, + type: 'line', + data: [], + label: { + normal: { + show: true, position: 'top', distance: 5, formatter: '{c}V', fontSize: 14, color: '#c1bdbd', fontFamily: 'Share Tech Mono' } - } - , { - xAxisIndex: 0, - name: 'Max V', - yAxisIndex: 0, - type: 'line', - data: [], - label: { - normal: { - show: true, position: 'top', distance: 5, formatter: '{c}V', fontSize: 14, color: '#c1bdbd', fontFamily: 'Share Tech Mono' - } - }, - symbolSize: 16, - symbol: ['arrow'], - itemStyle: { - normal: { - color: "#c1bdbd", lineStyle: { color: 'transparent' } - } + }, + symbolSize: 16, + symbol: ['arrow'], + itemStyle: { + normal: { + color: "#c1bdbd", lineStyle: { color: 'transparent' } } } + } - , { - xAxisIndex: 0, - name: 'Bypass', - yAxisIndex: 1, - type: 'line', - data: [], - label: { - normal: { - show: true, position: 'right', distance: 5, formatter: '{c}%', fontSize: 14, color: '#f0e400', fontFamily: 'Share Tech Mono' - } - }, - symbolSize: 16, - symbol: ['square'], - itemStyle: { normal: { color: "#f0e400", lineStyle: { color: 'transparent' } } } - } + , { + xAxisIndex: 0, + name: 'Bypass', + yAxisIndex: 1, + type: 'line', + data: [], + label: { + normal: { + show: true, position: 'right', distance: 5, formatter: '{c}%', fontSize: 14, color: '#f0e400', fontFamily: 'Share Tech Mono' + } + }, + symbolSize: 16, + symbol: ['square'], + itemStyle: { normal: { color: "#f0e400", lineStyle: { color: 'transparent' } } } + } - , { - xAxisIndex: 1, - yAxisIndex: 2, - name: 'BypassTemperature', - type: 'bar', - data: [], - itemStyle: { - color: '#55a1ea', barBorderRadius: [8, 8, 0, 0] - }, - label: { - normal: { - show: true, position: 'insideBottom', distance: 8, - align: 'left', verticalAlign: 'middle', - rotate: 90, formatter: '{c}°C', fontSize: 20, color: '#eeeeee', fontFamily: 'Share Tech Mono' - } + , { + xAxisIndex: 1, + yAxisIndex: 2, + name: 'BypassTemperature', + type: 'bar', + data: [], + itemStyle: { + color: '#55a1ea', barBorderRadius: [8, 8, 0, 0] + }, + label: { + normal: { + show: true, position: 'insideBottom', distance: 8, + align: 'left', verticalAlign: 'middle', + rotate: 90, formatter: '{c}°C', fontSize: 20, color: '#eeeeee', fontFamily: 'Share Tech Mono' } } + } - , { - xAxisIndex: 1, - yAxisIndex: 2, - name: 'CellTemperature', - type: 'bar', - data: [], - itemStyle: { - color: '#55a1ea', barBorderRadius: [8, 8, 0, 0] - }, - label: { - normal: { - show: true, position: 'insideBottom', distance: 8, - align: 'left', verticalAlign: 'middle', rotate: 90, - formatter: '{c}°C', fontSize: 20, color: '#eeeeee', fontFamily: 'Share Tech Mono' - } + , { + xAxisIndex: 1, + yAxisIndex: 2, + name: 'CellTemperature', + type: 'bar', + data: [], + itemStyle: { + color: '#55a1ea', barBorderRadius: [8, 8, 0, 0] + }, + label: { + normal: { + show: true, position: 'insideBottom', distance: 8, + align: 'left', verticalAlign: 'middle', rotate: 90, + formatter: '{c}°C', fontSize: 20, color: '#eeeeee', fontFamily: 'Share Tech Mono' } - } - ], - grid: [ - { - containLabel: false, left: '4%', right: '4%', bottom: '30%' - }, { - containLabel: false, left: '4%', right: '4%', top: '76%' - }] - }; + } + ], + grid: [ + { + containLabel: false, left: '4%', right: '4%', bottom: '30%' + }, { + containLabel: false, left: '4%', right: '4%', top: '76%' + }] + }; - if (jsondata.voltages.length > 24) { - // When lots of cell data is on screen, hide the labels to improve visability - for (const element of option.series) { - element.label.normal.show = false; - } + + if (jsondata.voltages.length > 24) { + // When lots of cell data is on screen, hide the labels to improve visability + for (const element of option.series) { + element.label.normal.show = false; } + } - // use configuration item and data specified to show chart - g1.setOption(option); + // use configuration item and data specified to show chart + g1.setOption(option); - } + } - if (window.g2 == null && $('#graph2').css('display') != 'none' && window.Graph3DAvailable === true) { - window.g2 = echarts.init(document.getElementById('graph2')); - - var Option3dBar = { - tooltip: {}, - visualMap: { max: 4, inRange: { color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026'] } }, - xAxis3D: { type: 'category', data: [], name: 'Cell', nameTextStyle: { color: '#ffffff' } }, - yAxis3D: { type: 'category', data: [], name: 'Bank', nameTextStyle: { color: '#ffffff' } }, - zAxis3D: { type: 'value', name: 'Voltage', nameTextStyle: { color: '#ffffff' } }, - grid3D: { - boxWidth: 200, - boxDepth: 80, - viewControl: { - // projection: 'orthographic' - }, - light: { - main: { - intensity: 1.2, - shadow: true - }, - ambient: { - intensity: 0.3 - } - } + if (window.g2 == null && $('#graph2').css('display') != 'none' && window.Graph3DAvailable === true) { + window.g2 = echarts.init(document.getElementById('graph2')); + + var Option3dBar = { + tooltip: {}, + visualMap: { max: 4, inRange: { color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026'] } }, + xAxis3D: { type: 'category', data: [], name: 'Cell', nameTextStyle: { color: '#ffffff' } }, + yAxis3D: { type: 'category', data: [], name: 'Bank', nameTextStyle: { color: '#ffffff' } }, + zAxis3D: { type: 'value', name: 'Voltage', nameTextStyle: { color: '#ffffff' } }, + grid3D: { + boxWidth: 200, + boxDepth: 80, + viewControl: { + // projection: 'orthographic' }, - series: [{ - type: 'bar3D', - data: [], - shading: 'lambert', - label: { textStyle: { fontSize: 16, borderWidth: 1, color: '#ffffff' } }, - - emphasis: { - label: { - textStyle: { - fontSize: 16, - color: '#aaa' - } - }, - itemStyle: { color: '#fff' } + light: { + main: { + intensity: 1.2, + shadow: true + }, + ambient: { + intensity: 0.3 } - }] - }; + } + }, + series: [{ + type: 'bar3D', + data: [], + shading: 'lambert', + label: { textStyle: { fontSize: 16, borderWidth: 1, color: '#ffffff' } }, + + emphasis: { + label: { + textStyle: { + fontSize: 16, + color: '#aaa' + } + }, + itemStyle: { color: '#fff' } + } + }] + }; - g2.setOption(Option3dBar); - } + g2.setOption(Option3dBar); + } - if (window.g1 != null && $('#graph1').css('display') != 'none') { - g1.setOption({ - markLine: { data: markLineData }, - xAxis: { data: labels }, - yAxis: [{ gridIndex: 0, min: minVoltage, max: maxVoltage }] - , series: [{ name: 'Voltage', data: voltages } - , { name: 'Min V', data: voltagesmin } - , { name: 'Max V', data: voltagesmax } - , { name: 'Bypass', data: pwm } - , { name: 'BypassTemperature', data: tempint } - , { name: 'CellTemperature', data: tempext }] - }); - } + if (window.g1 != null && $('#graph1').css('display') != 'none') { + g1.setOption({ + markLine: { data: markLineData }, + xAxis: { data: labels }, + yAxis: [{ gridIndex: 0, min: minVoltage, max: maxVoltage }] + , series: [{ name: 'Voltage', data: voltages } + , { name: 'Min V', data: voltagesmin } + , { name: 'Max V', data: voltagesmax } + , { name: 'Bypass', data: pwm } + , { name: 'BypassTemperature', data: tempint } + , { name: 'CellTemperature', data: tempext }] + }); + } - if (window.g2 != null && $('#graph2').css('display') != 'none') { - //Format the data to show as 3D Bar chart - var cells3d = []; - var banks3d = []; + if (window.g2 != null && $('#graph2').css('display') != 'none') { + //Format the data to show as 3D Bar chart + var cells3d = []; + var banks3d = []; - for (let seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { - cells3d.push({ value: 'Cell ' + seriesmodules, textStyle: { color: '#ffffff' } }); - } + for (let seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { + cells3d.push({ value: 'Cell ' + seriesmodules, textStyle: { color: '#ffffff' } }); + } - var data3d = []; - var cell = 0; - for (let bankNumber = 0; bankNumber < jsondata.banks; bankNumber++) { - banks3d.push({ value: 'Bank ' + bankNumber, textStyle: { color: '#ffffff' } }); - //Build up 3d array for cell data - for (let seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { - data3d.push({ value: [seriesmodules, bankNumber, voltages[cell].value], itemStyle: voltages[cell].itemStyle }); - cell++; - } + var data3d = []; + var cell = 0; + for (let bankNumber = 0; bankNumber < jsondata.banks; bankNumber++) { + banks3d.push({ value: 'Bank ' + bankNumber, textStyle: { color: '#ffffff' } }); + //Build up 3d array for cell data + for (let seriesmodules = 0; seriesmodules < jsondata.seriesmodules; seriesmodules++) { + data3d.push({ value: [seriesmodules, bankNumber, voltages[cell].value], itemStyle: voltages[cell].itemStyle }); + cell++; } + } - g2.setOption({ - xAxis3D: { data: cells3d }, - yAxis3D: { data: banks3d }, - zAxis3D: { min: minVoltage, max: maxVoltage }, - series: [{ data: data3d }] + g2.setOption({ + xAxis3D: { data: cells3d }, + yAxis3D: { data: banks3d }, + zAxis3D: { min: minVoltage, max: maxVoltage }, + series: [{ data: data3d }] - , grid3D: { - boxWidth: 20 * jsondata.seriesmodules > 200 ? 200 : 20 * jsondata.seriesmodules, - boxDepth: 20 * jsondata.banks > 100 ? 100 : 20 * jsondata.banks - } + , grid3D: { + boxWidth: 20 * jsondata.seriesmodules > 200 ? 200 : 20 * jsondata.seriesmodules, + boxDepth: 20 * jsondata.banks > 100 ? 100 : 20 * jsondata.banks } - - ); } - }//end homepage visible + ); + } + + }//end homepage visible - $("#homePage").css({ opacity: 1.0 }); - $("#loading").hide(); - //Call again in a few seconds - setTimeout(queryBMS, 3500); + $("#homePage").css({ opacity: 1.0 }); + $("#loading").hide(); + //Call again in a few seconds + setTimeout(queryBMS, 3500); - loadVisibleTileData(); + loadVisibleTileData(); +} - }).fail(function (jqXHR, textStatus, errorThrown) { +function queryBMS() { + $.getJSON("/api/monitor2", updateChart).fail(function (jqXHR, textStatus, errorThrown) { if (jqXHR.status == 400 && jqXHR.responseJSON.error === "Invalid cookie") { if ($("#warningXSS").data("notify") == undefined) { From b6e10666998d64994ce4c8e1118b3f93da58daec Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:54:25 +0100 Subject: [PATCH 39/39] Update EmbeddedFiles_Defines.h --- STM32All-In-One/include/EmbeddedFiles_Defines.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/STM32All-In-One/include/EmbeddedFiles_Defines.h b/STM32All-In-One/include/EmbeddedFiles_Defines.h index ffcc88a2..6e9e29b1 100644 --- a/STM32All-In-One/include/EmbeddedFiles_Defines.h +++ b/STM32All-In-One/include/EmbeddedFiles_Defines.h @@ -5,12 +5,12 @@ #ifndef EmbeddedFiles_Defines_H #define EmbeddedFiles_Defines_H -static const char GIT_VERSION[] = "256670999f7ae2237b8ed7a913f5b9631bb5f405"; +static const char GIT_VERSION[] = "6e62d616aa642884cb6458c0baf8057ccf2c11d6"; -static const uint16_t GIT_VERSION_B1 = 0x1bb5; +static const uint16_t GIT_VERSION_B1 = 0xcf2c; -static const uint16_t GIT_VERSION_B2 = 0xf405; +static const uint16_t GIT_VERSION_B2 = 0x11d6; -static const char COMPILE_DATE_TIME[] = "2023-11-13T11:48:01.770Z"; +static const char COMPILE_DATE_TIME[] = "2024-08-02T12:18:35.609Z"; #endif \ No newline at end of file