Merge pull request #1208 from kyberias/can-receive

Generalize comm 'transmit' and introduce common class for shunts
This commit is contained in:
Jaakko Haakana 2025-06-08 14:52:49 +03:00 committed by GitHub
commit 30fc0f002a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 317 additions and 262 deletions

View file

@ -115,4 +115,5 @@ jobs:
# in the build matrix, and using build flags to define the # in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix. # battery and inverter set in the build matrix.
- name: Compile Sketch - name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -288,4 +288,4 @@ jobs:
# in the build matrix, and using build flags to define the # in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix. # battery and inverter set in the build matrix.
- name: Compile Sketch - name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -90,4 +90,5 @@ jobs:
# in the build matrix, and using build flags to define the # in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix. # battery and inverter set in the build matrix.
- name: Compile Sketch - name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -DDOUBLE_BATTERY -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -DDOUBLE_BATTERY -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -90,4 +90,4 @@ jobs:
# in the build matrix, and using build flags to define the # in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix. # battery and inverter set in the build matrix.
- name: Compile Sketch - name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -105,4 +105,4 @@ jobs:
# in the build matrix, and using build flags to define the # in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix. # battery and inverter set in the build matrix.
- name: Compile Sketch - name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -9,6 +9,7 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "src/communication/Transmitter.h"
#include "src/communication/can/comm_can.h" #include "src/communication/can/comm_can.h"
#include "src/communication/contactorcontrol/comm_contactorcontrol.h" #include "src/communication/contactorcontrol/comm_contactorcontrol.h"
#include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h" #include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h"
@ -88,11 +89,6 @@ void setup() {
&logging_loop_task, WIFI_CORE); &logging_loop_task, WIFI_CORE);
#endif #endif
#ifdef MQTT
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task,
WIFI_CORE);
#endif
init_CAN(); init_CAN();
init_contactors(); init_contactors();
@ -110,9 +106,8 @@ void setup() {
#ifdef EQUIPMENT_STOP_BUTTON #ifdef EQUIPMENT_STOP_BUTTON
init_equipment_stop_button(); init_equipment_stop_button();
#endif #endif
#ifdef CAN_SHUNT_SELECTED
setup_can_shunt(); setup_can_shunt();
#endif
// BOOT button at runtime is used as an input for various things // BOOT button at runtime is used as an input for various things
pinMode(0, INPUT_PULLUP); pinMode(0, INPUT_PULLUP);
@ -126,6 +121,12 @@ void setup() {
}; };
// Start tasks // Start tasks
#ifdef MQTT
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task,
WIFI_CORE);
#endif
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task, xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task,
CORE_FUNCTION_CORE); CORE_FUNCTION_CORE);
#ifdef PERIODIC_BMS_RESET_AT #ifdef PERIODIC_BMS_RESET_AT
@ -202,6 +203,12 @@ void mqtt_loop(void*) {
} }
#endif #endif
static std::list<Transmitter*> transmitters;
void register_transmitter(Transmitter* transmitter) {
transmitters.push_back(transmitter);
}
void core_loop(void*) { void core_loop(void*) {
esp_task_wdt_add(NULL); // Register this task with WDT esp_task_wdt_add(NULL); // Register this task with WDT
TickType_t xLastWakeTime = xTaskGetTickCount(); TickType_t xLastWakeTime = xTaskGetTickCount();
@ -253,15 +260,25 @@ void core_loop(void*) {
#ifdef FUNCTION_TIME_MEASUREMENT #ifdef FUNCTION_TIME_MEASUREMENT
START_TIME_MEASUREMENT(time_values); START_TIME_MEASUREMENT(time_values);
#endif #endif
update_pause_state(); // Check if we are OK to send CAN or need to pause update_pause_state(); // Check if we are OK to send CAN or need to pause
update_values_battery(); // Fetch battery values
#ifdef DOUBLE_BATTERY // Fetch battery values
update_values_battery2(); if (battery) {
check_interconnect_available(); battery->update_values();
#endif // DOUBLE_BATTERY }
if (battery2) {
battery2->update_values();
check_interconnect_available();
}
update_calculated_values(); update_calculated_values();
update_machineryprotection(); // Check safeties update_machineryprotection(); // Check safeties
update_values_inverter(); // Update values heading towards inverter
// Update values heading towards inverter
if (inverter) {
inverter->update_values();
}
#ifdef FUNCTION_TIME_MEASUREMENT #ifdef FUNCTION_TIME_MEASUREMENT
END_TIME_MEASUREMENT_MAX(time_values, datalayer.system.status.time_values_us); END_TIME_MEASUREMENT_MAX(time_values, datalayer.system.status.time_values_us);
#endif #endif
@ -269,12 +286,12 @@ void core_loop(void*) {
#ifdef FUNCTION_TIME_MEASUREMENT #ifdef FUNCTION_TIME_MEASUREMENT
START_TIME_MEASUREMENT(cantx); START_TIME_MEASUREMENT(cantx);
#endif #endif
// Output
transmit_can(currentMillis); // Send CAN messages to all components
#ifdef RS485_BATTERY_SELECTED // Let all transmitter objects send their messages
transmit_rs485(currentMillis); for (auto& transmitter : transmitters) {
#endif // RS485_BATTERY_SELECTED transmitter->transmit(currentMillis);
}
#ifdef FUNCTION_TIME_MEASUREMENT #ifdef FUNCTION_TIME_MEASUREMENT
END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us); END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us);
END_TIME_MEASUREMENT_MAX(all, datalayer.system.status.core_task_10s_max_us); END_TIME_MEASUREMENT_MAX(all, datalayer.system.status.core_task_10s_max_us);
@ -506,12 +523,6 @@ void update_calculated_values() {
lastMillisOverflowCheck = currentMillis; lastMillisOverflowCheck = currentMillis;
} }
void update_values_inverter() {
if (inverter) {
inverter->update_values();
}
}
void check_reset_reason() { void check_reset_reason() {
esp_reset_reason_t reason = esp_reset_reason(); esp_reset_reason_t reason = esp_reset_reason();
switch (reason) { switch (reason) {

View file

@ -41,43 +41,21 @@ void setup_battery() {
#endif #endif
} }
void update_values_battery() {
battery->update_values();
}
// transmit_can_battery is called once and we need to // transmit_can_battery is called once and we need to
// call both batteries. // call both batteries.
void transmit_can_battery(unsigned long currentMillis) {
((CanBattery*)battery)->transmit_can(currentMillis);
#ifdef DOUBLE_BATTERY
((CanBattery*)battery2)->transmit_can(currentMillis);
#endif
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) { void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
((CanBattery*)battery)->handle_incoming_can_frame(rx_frame); ((CanBattery*)battery)->handle_incoming_can_frame(rx_frame);
} }
#ifdef DOUBLE_BATTERY
void update_values_battery2() {
battery2->update_values();
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
((CanBattery*)battery2)->handle_incoming_can_frame(rx_frame); ((CanBattery*)battery2)->handle_incoming_can_frame(rx_frame);
} }
#endif
#ifdef RS485_BATTERY_SELECTED #ifdef RS485_BATTERY_SELECTED
void transmit_rs485(unsigned long currentMillis) {
((RS485Battery*)battery)->transmit_rs485(currentMillis);
}
void receive_RS485() { void receive_RS485() {
((RS485Battery*)battery)->receive_RS485(); ((RS485Battery*)battery)->receive_RS485();
} }
#endif #endif
#endif #endif

View file

@ -9,11 +9,11 @@ class Battery;
extern Battery* battery; extern Battery* battery;
extern Battery* battery2; extern Battery* battery2;
void setup_can_shunt();
#ifdef BMW_SBOX #ifdef BMW_SBOX
#include "BMW-SBOX.h" #include "BMW-SBOX.h"
void handle_incoming_can_frame_shunt(CAN_frame rx_frame); void handle_incoming_can_frame_shunt(CAN_frame rx_frame);
void transmit_can_shunt(unsigned long currentMillis);
void setup_can_shunt();
#endif #endif
#ifdef BMW_I3_BATTERY #ifdef BMW_I3_BATTERY
@ -159,7 +159,6 @@ void setup_can_shunt();
#endif #endif
void setup_battery(void); void setup_battery(void);
void update_values_battery();
#ifdef RS485_BATTERY_SELECTED #ifdef RS485_BATTERY_SELECTED
void transmit_rs485(unsigned long currentMillis); void transmit_rs485(unsigned long currentMillis);
@ -169,7 +168,6 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame);
void transmit_can_battery(unsigned long currentMillis); void transmit_can_battery(unsigned long currentMillis);
#endif #endif
void update_values_battery2();
void handle_incoming_can_frame_battery2(CAN_frame rx_frame); void handle_incoming_can_frame_battery2(CAN_frame rx_frame);
#endif #endif

View file

@ -1,41 +1,9 @@
#include "../include.h" #include "../include.h"
#ifdef BMW_SBOX #ifdef BMW_SBOX
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "BMW-SBOX.h" #include "BMW-SBOX.h"
#define MAX_ALLOWED_FAULT_TICKS 1000
enum SboxState { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
SboxState contactorStatus = DISCONNECTED;
unsigned long prechargeStartTime = 0;
unsigned long negativeStartTime = 0;
unsigned long positiveStartTime = 0;
unsigned long timeSpentInFaultedMode = 0;
unsigned long LastMsgTime = 0; // will store last time a 20ms CAN Message was send
unsigned long LastAvgTime = 0; // Last current storage time
unsigned long ShuntLastSeen = 0;
uint32_t avg_mA_array[10];
uint32_t avg_sum;
uint8_t k; //avg array pointer
uint8_t CAN100_cnt = 0;
CAN_frame SBOX_100 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x100,
.data = {0x55, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00}}; // Byte 0: relay control, Byte 1: counter 0-E, Byte 4: CRC
CAN_frame SBOX_300 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x300,
.data = {0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}}; // Static frame
uint8_t reverse_bits(uint8_t byte) { uint8_t reverse_bits(uint8_t byte) {
uint8_t reversed = 0; uint8_t reversed = 0;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
@ -64,7 +32,7 @@ uint8_t calculateCRC(CAN_frame CAN) {
return crc; return crc;
} }
void handle_incoming_can_frame_shunt(CAN_frame rx_frame) { void BmwSbox::handle_incoming_can_frame(CAN_frame rx_frame) {
unsigned long currentTime = millis(); unsigned long currentTime = millis();
if (rx_frame.ID == 0x200) { if (rx_frame.ID == 0x200) {
ShuntLastSeen = currentTime; ShuntLastSeen = currentTime;
@ -100,7 +68,7 @@ void handle_incoming_can_frame_shunt(CAN_frame rx_frame) {
} }
} }
void transmit_can_shunt(unsigned long currentMillis) { void BmwSbox::transmit_can(unsigned long currentMillis) {
/** Shunt can frames seen? **/ /** Shunt can frames seen? **/
if (ShuntLastSeen + 1000 < currentMillis) { if (ShuntLastSeen + 1000 < currentMillis) {
@ -210,7 +178,7 @@ void transmit_can_shunt(unsigned long currentMillis) {
} }
} }
void setup_can_shunt() { void BmwSbox::setup() {
strncpy(datalayer.system.info.shunt_protocol, "BMW SBOX", 63); strncpy(datalayer.system.info.shunt_protocol, "BMW SBOX", 63);
datalayer.system.info.shunt_protocol[63] = '\0'; datalayer.system.info.shunt_protocol[63] = '\0';
} }

View file

@ -1,22 +1,67 @@
#ifndef BMW_SBOX_CONTROL_H #ifndef BMW_SBOX_CONTROL_H
#define BMW_SBOX_CONTROL_H #define BMW_SBOX_CONTROL_H
#include "../include.h" #include "../include.h"
#define CAN_SHUNT_SELECTED #define CAN_SHUNT_SELECTED
void transmit_can(CAN_frame* tx_frame, int interface); #define SELECTED_SHUNT_CLASS BmwSbox
/** Minimum input voltage required to enable relay control **/ #include "Shunt.h"
#define MINIMUM_INPUT_VOLTAGE 250
/** Minimum required percentage of input voltage at the output port to engage the positive relay. **/ class BmwSbox : public CanShunt {
/** Prevents engagement of the positive contactor if the precharge resistor is faulty. **/ public:
#define MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT 0.99 void setup();
void transmit_can(unsigned long currentMillis);
void handle_incoming_can_frame(CAN_frame rx_frame);
/* NOTE: modify the T2 time constant below to account for the resistance and capacitance of the target system. private:
/** Minimum input voltage required to enable relay control **/
static const int MINIMUM_INPUT_VOLTAGE = 250;
/** Minimum required percentage of input voltage at the output port to engage the positive relay. **/
/** Prevents engagement of the positive contactor if the precharge resistor is faulty. **/
static constexpr const double MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT = 0.99;
/* NOTE: modify the T2 time constant below to account for the resistance and capacitance of the target system.
* t=3RC at minimum, t=5RC ideally * t=3RC at minimum, t=5RC ideally
*/ */
#define CONTACTOR_CONTROL_T1 5000 // Time before negative contactor engages and precharging starts static const int CONTACTOR_CONTROL_T1 = 5000; // Time before negative contactor engages and precharging starts
#define CONTACTOR_CONTROL_T2 5000 // Precharge time before precharge resistor is bypassed by positive contactor static const int CONTACTOR_CONTROL_T2 =
#define CONTACTOR_CONTROL_T3 2000 // Precharge relay lead time after positive contactor has been engaged 5000; // Precharge time before precharge resistor is bypassed by positive contactor
static const int CONTACTOR_CONTROL_T3 = 2000; // Precharge relay lead time after positive contactor has been engaged
static const int MAX_ALLOWED_FAULT_TICKS = 1000;
enum SboxState { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
SboxState contactorStatus = DISCONNECTED;
unsigned long prechargeStartTime = 0;
unsigned long negativeStartTime = 0;
unsigned long positiveStartTime = 0;
unsigned long timeSpentInFaultedMode = 0;
unsigned long LastMsgTime = 0; // will store last time a 20ms CAN Message was send
unsigned long LastAvgTime = 0; // Last current storage time
unsigned long ShuntLastSeen = 0;
uint32_t avg_mA_array[10];
uint32_t avg_sum;
uint8_t k; //avg array pointer
uint8_t CAN100_cnt = 0;
CAN_frame SBOX_100 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x100,
.data = {0x55, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00}}; // Byte 0: relay control, Byte 1: counter 0-E, Byte 4: CRC
CAN_frame SBOX_300 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x300,
.data = {0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}}; // Static frame
};
#endif #endif

View file

@ -47,6 +47,9 @@ class Battery {
// This allows for battery specific SOC plausibility calculations to be performed. // This allows for battery specific SOC plausibility calculations to be performed.
virtual bool soc_plausible() { return true; } virtual bool soc_plausible() { return true; }
// Battery reports total_charged_battery_Wh and total_discharged_battery_Wh
virtual bool supports_charged_energy() { return false; }
virtual BatteryHtmlRenderer& get_status_renderer() { return defaultRenderer; } virtual BatteryHtmlRenderer& get_status_renderer() { return defaultRenderer; }
private: private:

View file

@ -3,22 +3,31 @@
#include "Battery.h" #include "Battery.h"
#include "src/communication/Transmitter.h"
#include "src/devboard/utils/types.h" #include "src/devboard/utils/types.h"
// Abstract base class for batteries using the CAN bus // Abstract base class for batteries using the CAN bus
class CanBattery : public Battery { class CanBattery : public Battery, Transmitter {
public: public:
virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0; virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0;
virtual void transmit_can(unsigned long currentMillis) = 0; virtual void transmit_can(unsigned long currentMillis) = 0;
String interface_name() { return getCANInterfaceName(can_interface); } String interface_name() { return getCANInterfaceName(can_interface); }
void transmit(unsigned long currentMillis) { transmit_can(currentMillis); }
protected: protected:
CAN_Interface can_interface; CAN_Interface can_interface;
CanBattery() { can_interface = can_config.battery; } CanBattery() {
can_interface = can_config.battery;
register_transmitter(this);
}
CanBattery(CAN_Interface interface) { can_interface = interface; } CanBattery(CAN_Interface interface) {
can_interface = interface;
register_transmitter(this);
}
}; };
#endif #endif

View file

@ -15,6 +15,7 @@ class MebBattery : public CanBattery {
virtual void update_values(); virtual void update_values();
virtual void transmit_can(unsigned long currentMillis); virtual void transmit_can(unsigned long currentMillis);
bool supports_real_BMS_status() { return true; } bool supports_real_BMS_status() { return true; }
bool supports_charged_energy() { return true; }
BatteryHtmlRenderer& get_status_renderer() { return renderer; } BatteryHtmlRenderer& get_status_renderer() { return renderer; }

View file

@ -43,9 +43,14 @@ class NissanLeafBattery : public CanBattery {
virtual void transmit_can(unsigned long currentMillis); virtual void transmit_can(unsigned long currentMillis);
bool supports_reset_SOH(); bool supports_reset_SOH();
void reset_SOH() { datalayer_extended.nissanleaf.UserRequestSOHreset = true; } void reset_SOH() { datalayer_extended.nissanleaf.UserRequestSOHreset = true; }
bool soc_plausible() {
// When pack voltage is close to max, and SOC% is still low, SOC is not plausible
return !((datalayer.battery.status.voltage_dV > (datalayer.battery.info.max_design_voltage_dV - 100)) &&
(datalayer.battery.status.real_soc < 6500));
}
BatteryHtmlRenderer& get_status_renderer() { return renderer; } BatteryHtmlRenderer& get_status_renderer() { return renderer; }
private: private:

View file

@ -3,14 +3,20 @@
#include "Battery.h" #include "Battery.h"
#include "src/communication/Transmitter.h"
#include "src/devboard/utils/types.h" #include "src/devboard/utils/types.h"
// Abstract base class for batteries using the RS485 interface // Abstract base class for batteries using the RS485 interface
class RS485Battery : public Battery { class RS485Battery : public Battery, Transmitter {
public: public:
virtual void receive_RS485() = 0; virtual void receive_RS485() = 0;
virtual void transmit_rs485(unsigned long currentMillis) = 0; virtual void transmit_rs485(unsigned long currentMillis) = 0;
String interface_name() { return "RS485"; } String interface_name() { return "RS485"; }
void transmit(unsigned long currentMillis) { transmit_rs485(currentMillis); }
RS485Battery() { register_transmitter(this); }
}; };
#endif #endif

View file

@ -0,0 +1,31 @@
#ifndef _SHUNT_H
#define _SHUNT_H
#include "../communication/can/comm_can.h"
#include "src/communication/Transmitter.h"
#include "src/devboard/utils/types.h"
class CanShunt : public Transmitter {
public:
virtual void setup() = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0;
void transmit(unsigned long currentMillis) {
if (allowed_to_send_CAN) {
transmit_can(currentMillis);
}
}
protected:
CAN_Interface can_interface;
CanShunt() {
can_interface = can_config.battery;
register_transmitter(this);
}
};
extern CanShunt* shunt;
#endif

View file

@ -0,0 +1,19 @@
#include "../include.h"
#include "Shunt.h"
CanShunt* shunt = nullptr;
void setup_can_shunt() {
#if defined(CAN_SHUNT_SELECTED) && defined(SELECTED_SHUNT_CLASS)
shunt = new SELECTED_SHUNT_CLASS();
if (shunt) {
shunt->setup();
}
#endif
}
void handle_incoming_can_frame_shunt(CAN_frame rx_frame) {
if (shunt) {
shunt->handle_incoming_can_frame(rx_frame);
}
}

View file

@ -30,6 +30,8 @@ class TeslaBattery : public CanBattery {
bool supports_reset_BMS() { return true; } bool supports_reset_BMS() { return true; }
void reset_BMS() { datalayer.battery.settings.user_requests_tesla_bms_reset = true; } void reset_BMS() { datalayer.battery.settings.user_requests_tesla_bms_reset = true; }
bool supports_charged_energy() { return true; }
BatteryHtmlRenderer& get_status_renderer() { return renderer; } BatteryHtmlRenderer& get_status_renderer() { return renderer; }
private: private:

View file

@ -4,6 +4,7 @@
#include "src/devboard/utils/types.h" #include "src/devboard/utils/types.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "src/communication/Transmitter.h"
enum class ChargerType { NissanLeaf, ChevyVolt }; enum class ChargerType { NissanLeaf, ChevyVolt };
@ -36,13 +37,19 @@ class Charger {
}; };
// Base class for chargers on a CAN bus // Base class for chargers on a CAN bus
class CanCharger : public Charger { class CanCharger : public Charger, Transmitter {
public: public:
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0; virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
virtual void transmit_can(unsigned long currentMillis) = 0; virtual void transmit_can(unsigned long currentMillis) = 0;
void transmit(unsigned long currentMillis) {
if (allowed_to_send_CAN) {
transmit_can(currentMillis);
}
}
protected: protected:
CanCharger(ChargerType type) : Charger(type) {} CanCharger(ChargerType type) : Charger(type) { register_transmitter(this); }
}; };
#endif #endif

View file

@ -0,0 +1,11 @@
#ifndef _TRANSMITTER_H
#define _TRANSMITTER_H
class Transmitter {
public:
virtual void transmit(unsigned long currentMillis) = 0;
};
void register_transmitter(Transmitter* transmitter);
#endif

View file

@ -104,30 +104,6 @@ void init_CAN() {
#endif // CANFD_ADDON #endif // CANFD_ADDON
} }
// Transmit functions
void transmit_can(unsigned long currentMillis) {
if (!allowed_to_send_CAN) {
return; //Global block of CAN messages
}
#ifndef RS485_BATTERY_SELECTED
transmit_can_battery(currentMillis);
#endif
#ifdef CAN_INVERTER_SELECTED
transmit_can_inverter(currentMillis);
#endif // CAN_INVERTER_SELECTED
if (charger) {
charger->transmit_can(currentMillis);
}
#ifdef CAN_SHUNT_SELECTED
transmit_can_shunt(currentMillis);
#endif // CAN_SHUNT_SELECTED
}
void transmit_can_frame(CAN_frame* tx_frame, int interface) { void transmit_can_frame(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) { if (!allowed_to_send_CAN) {
return; return;
@ -325,10 +301,8 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) {
map_can_frame_to_variable_inverter(*rx_frame); map_can_frame_to_variable_inverter(*rx_frame);
#endif #endif
} }
if (interface == can_config.battery_double) { if (interface == can_config.battery_double && battery2) {
#ifdef DOUBLE_BATTERY
handle_incoming_can_frame_battery2(*rx_frame); handle_incoming_can_frame_battery2(*rx_frame);
#endif
} }
if (interface == can_config.charger && charger) { if (interface == can_config.charger && charger) {
charger->map_can_frame_to_variable(*rx_frame); charger->map_can_frame_to_variable(*rx_frame);

View file

@ -37,16 +37,6 @@ void init_CAN();
*/ */
void transmit_can_frame(); void transmit_can_frame();
/**
* @brief Send CAN messages to all components
*
* @param[in] void
* @param[in] unsigned long currentMillis
*
* @return void
*/
void transmit_can(unsigned long currentMillis);
/** /**
* @brief Receive CAN messages from all interfaces * @brief Receive CAN messages from all interfaces
* *

View file

@ -79,12 +79,8 @@
#define EQUIPMENT_STOP_PIN 35 #define EQUIPMENT_STOP_PIN 35
// BMW_I3_BATTERY wake up pin // BMW_I3_BATTERY wake up pin
#ifdef BMW_I3_BATTERY
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 #define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1
#ifdef DOUBLE_BATTERY
#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 #define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2
#endif // DOUBLE_BATTERY
#endif // BMW_I3_BATTERY
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ /* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED #ifndef HW_CONFIGURED

View file

@ -67,12 +67,8 @@ The pin layout below supports the following:
#define INVERTER_DISCONNECT_CONTACTOR_PIN GPIO_NUM_5 #define INVERTER_DISCONNECT_CONTACTOR_PIN GPIO_NUM_5
// BMW_I3_BATTERY wake up pin // BMW_I3_BATTERY wake up pin
#ifdef BMW_I3_BATTERY
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 #define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1
#ifdef DOUBLE_BATTERY
#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 #define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2
#endif // DOUBLE_BATTERY
#endif // BMW_I3_BATTERY
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ /* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED #ifndef HW_CONFIGURED

View file

@ -74,12 +74,8 @@
#define EQUIPMENT_STOP_PIN 35 #define EQUIPMENT_STOP_PIN 35
// BMW_I3_BATTERY wake up pin // BMW_I3_BATTERY wake up pin
#ifdef BMW_I3_BATTERY
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 #define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1
#ifdef DOUBLE_BATTERY
#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 #define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2
#endif // DOUBLE_BATTERY
#endif // BMW_I3_BATTERY
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ /* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED #ifndef HW_CONFIGURED

View file

@ -66,12 +66,8 @@ GPIOs on extra header
#define EQUIPMENT_STOP_PIN 2 #define EQUIPMENT_STOP_PIN 2
// BMW_I3_BATTERY wake up pin // BMW_I3_BATTERY wake up pin
#ifdef BMW_I3_BATTERY
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 #define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1
#ifdef DOUBLE_BATTERY
#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 #define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2
#endif // DOUBLE_BATTERY
#endif // BMW_I3_BATTERY
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ /* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED #ifndef HW_CONFIGURED

View file

@ -62,55 +62,62 @@ struct SensorConfig {
const char* value_template; const char* value_template;
const char* unit; const char* unit;
const char* device_class; const char* device_class;
// A function that returns true for the battery if it supports this config
std::function<bool(Battery*)> condition;
};
static std::function<bool(Battery*)> always = [](Battery* b) {
return true;
};
static std::function<bool(Battery*)> supports_charged = [](Battery* b) {
return b->supports_charged_energy();
}; };
SensorConfig sensorConfigTemplate[] = { SensorConfig sensorConfigTemplate[] = {
{"SOC", "SOC (Scaled)", "", "%", "battery"}, {"SOC", "SOC (Scaled)", "", "%", "battery", always},
{"SOC_real", "SOC (real)", "", "%", "battery"}, {"SOC_real", "SOC (real)", "", "%", "battery", always},
{"state_of_health", "State Of Health", "", "%", "battery"}, {"state_of_health", "State Of Health", "", "%", "battery", always},
{"temperature_min", "Temperature Min", "", "°C", "temperature"}, {"temperature_min", "Temperature Min", "", "°C", "temperature", always},
{"temperature_max", "Temperature Max", "", "°C", "temperature"}, {"temperature_max", "Temperature Max", "", "°C", "temperature", always},
{"cpu_temp", "CPU Temperature", "", "°C", "temperature"}, {"cpu_temp", "CPU Temperature", "", "°C", "temperature", always},
{"stat_batt_power", "Stat Batt Power", "", "W", "power"}, {"stat_batt_power", "Stat Batt Power", "", "W", "power", always},
{"battery_current", "Battery Current", "", "A", "current"}, {"battery_current", "Battery Current", "", "A", "current", always},
{"cell_max_voltage", "Cell Max Voltage", "", "V", "voltage"}, {"cell_max_voltage", "Cell Max Voltage", "", "V", "voltage", always},
{"cell_min_voltage", "Cell Min Voltage", "", "V", "voltage"}, {"cell_min_voltage", "Cell Min Voltage", "", "V", "voltage", always},
{"cell_voltage_delta", "Cell Voltage Delta", "", "mV", "voltage"}, {"cell_voltage_delta", "Cell Voltage Delta", "", "mV", "voltage", always},
{"battery_voltage", "Battery Voltage", "", "V", "voltage"}, {"battery_voltage", "Battery Voltage", "", "V", "voltage", always},
{"total_capacity", "Battery Total Capacity", "", "Wh", "energy"}, {"total_capacity", "Battery Total Capacity", "", "Wh", "energy", always},
{"remaining_capacity", "Battery Remaining Capacity (scaled)", "", "Wh", "energy"}, {"remaining_capacity", "Battery Remaining Capacity (scaled)", "", "Wh", "energy", always},
{"remaining_capacity_real", "Battery Remaining Capacity (real)", "", "Wh", "energy"}, {"remaining_capacity_real", "Battery Remaining Capacity (real)", "", "Wh", "energy", always},
{"max_discharge_power", "Battery Max Discharge Power", "", "W", "power"}, {"max_discharge_power", "Battery Max Discharge Power", "", "W", "power", always},
{"max_charge_power", "Battery Max Charge Power", "", "W", "power"}, {"max_charge_power", "Battery Max Charge Power", "", "W", "power", always},
#if defined(MEB_BATTERY) || defined(TESLA_BATTERY) {"charged_energy", "Battery Charged Energy", "", "Wh", "energy", supports_charged},
{"charged_energy", "Battery Charged Energy", "", "Wh", "energy"}, {"discharged_energy", "Battery Discharged Energy", "", "Wh", "energy", supports_charged},
{"discharged_energy", "Battery Discharged Energy", "", "Wh", "energy"}, {"bms_status", "BMS Status", "", "", "", always},
#endif {"pause_status", "Pause Status", "", "", "", always}};
{"bms_status", "BMS Status", "", "", ""},
{"pause_status", "Pause Status", "", "", ""}};
#ifdef DOUBLE_BATTERY // Enough space for two batteries
SensorConfig sensorConfigs[((sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])) * 2) - 2]; SensorConfig sensorConfigs[((sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])) * 2) - 2];
#else
SensorConfig sensorConfigs[sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0])];
#endif // DOUBLE_BATTERY
void create_sensor_configs() { void create_sensor_configs() {
int number_of_templates = sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0]); int number_of_templates = sizeof(sensorConfigTemplate) / sizeof(sensorConfigTemplate[0]);
for (int i = 0; i < number_of_templates; i++) { for (int i = 0; i < number_of_templates; i++) {
SensorConfig config = sensorConfigTemplate[i]; SensorConfig config = sensorConfigTemplate[i];
config.value_template = strdup(("{{ value_json." + std::string(config.object_id) + " }}").c_str()); config.value_template = strdup(("{{ value_json." + std::string(config.object_id) + " }}").c_str());
sensorConfigs[i] = config; sensorConfigs[i] = config;
#ifdef DOUBLE_BATTERY
if (config.object_id == "pause_status" || config.object_id == "bms_status") { if (battery2) {
continue; if (config.object_id == "pause_status" || config.object_id == "bms_status") {
continue;
}
sensorConfigs[i + number_of_templates] = config;
sensorConfigs[i + number_of_templates].name = strdup(String(config.name + String(" 2")).c_str());
sensorConfigs[i + number_of_templates].object_id = strdup(String(config.object_id + String("_2")).c_str());
sensorConfigs[i + number_of_templates].value_template =
strdup(("{{ value_json." + std::string(config.object_id) + "_2 }}").c_str());
} }
sensorConfigs[i + number_of_templates] = config;
sensorConfigs[i + number_of_templates].name = strdup(String(config.name + String(" 2")).c_str());
sensorConfigs[i + number_of_templates].object_id = strdup(String(config.object_id + String("_2")).c_str());
sensorConfigs[i + number_of_templates].value_template =
strdup(("{{ value_json." + std::string(config.object_id) + "_2 }}").c_str());
#endif // DOUBLE_BATTERY
} }
} }
@ -164,7 +171,8 @@ static String generateButtonTopic(const char* subtype) {
return topic_name + "/command/" + String(subtype); return topic_name + "/command/" + String(subtype);
} }
void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& battery, const String& suffix) { void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& battery, const String& suffix,
bool supports_charged) {
doc["SOC" + suffix] = ((float)battery.status.reported_soc) / 100.0; doc["SOC" + suffix] = ((float)battery.status.reported_soc) / 100.0;
doc["SOC_real" + suffix] = ((float)battery.status.real_soc) / 100.0; doc["SOC_real" + suffix] = ((float)battery.status.real_soc) / 100.0;
doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0; doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0;
@ -185,13 +193,14 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat
doc["remaining_capacity" + suffix] = ((float)battery.status.reported_remaining_capacity_Wh); doc["remaining_capacity" + suffix] = ((float)battery.status.reported_remaining_capacity_Wh);
doc["max_discharge_power" + suffix] = ((float)battery.status.max_discharge_power_W); doc["max_discharge_power" + suffix] = ((float)battery.status.max_discharge_power_W);
doc["max_charge_power" + suffix] = ((float)battery.status.max_charge_power_W); doc["max_charge_power" + suffix] = ((float)battery.status.max_charge_power_W);
#if defined(MEB_BATTERY) || defined(TESLA_BATTERY)
if (datalayer.battery.status.total_charged_battery_Wh != 0 && if (supports_charged) {
datalayer.battery.status.total_discharged_battery_Wh != 0) { if (datalayer.battery.status.total_charged_battery_Wh != 0 &&
doc["charged_energy" + suffix] = ((float)datalayer.battery.status.total_charged_battery_Wh); datalayer.battery.status.total_discharged_battery_Wh != 0) {
doc["discharged_energy" + suffix] = ((float)datalayer.battery.status.total_discharged_battery_Wh); doc["charged_energy" + suffix] = ((float)datalayer.battery.status.total_charged_battery_Wh);
doc["discharged_energy" + suffix] = ((float)datalayer.battery.status.total_discharged_battery_Wh);
}
} }
#endif
} }
static std::vector<EventData> order_events; static std::vector<EventData> order_events;
@ -203,13 +212,19 @@ static bool publish_common_info(void) {
if (ha_common_info_published == false) { if (ha_common_info_published == false) {
for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) { for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) {
SensorConfig& config = sensorConfigs[i]; SensorConfig& config = sensorConfigs[i];
if (!config.condition(battery)) {
continue;
}
doc["name"] = config.name; doc["name"] = config.name;
doc["state_topic"] = state_topic; doc["state_topic"] = state_topic;
doc["unique_id"] = topic_name + "_" + String(config.object_id); doc["unique_id"] = topic_name + "_" + String(config.object_id);
doc["object_id"] = object_id_prefix + String(config.object_id); doc["object_id"] = object_id_prefix + String(config.object_id);
doc["value_template"] = config.value_template; doc["value_template"] = config.value_template;
if (config.unit != nullptr && strlen(config.unit) > 0) if (config.unit != nullptr && strlen(config.unit) > 0) {
doc["unit_of_measurement"] = config.unit; doc["unit_of_measurement"] = config.unit;
}
if (config.device_class != nullptr && strlen(config.device_class) > 0) { if (config.device_class != nullptr && strlen(config.device_class) > 0) {
doc["device_class"] = config.device_class; doc["device_class"] = config.device_class;
doc["state_class"] = "measurement"; doc["state_class"] = "measurement";
@ -231,14 +246,15 @@ static bool publish_common_info(void) {
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
set_battery_attributes(doc, datalayer.battery, ""); set_battery_attributes(doc, datalayer.battery, "", battery->supports_charged_energy());
} }
#ifdef DOUBLE_BATTERY
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) if (battery2) {
if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
set_battery_attributes(doc, datalayer.battery2, "_2"); if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
set_battery_attributes(doc, datalayer.battery2, "_2", battery2->supports_charged_energy());
}
} }
#endif // DOUBLE_BATTERY
serializeJson(doc, mqtt_msg); serializeJson(doc, mqtt_msg);
if (mqtt_publish(state_topic.c_str(), mqtt_msg, false) == false) { if (mqtt_publish(state_topic.c_str(), mqtt_msg, false) == false) {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
@ -256,9 +272,7 @@ static bool publish_common_info(void) {
static bool publish_cell_voltages(void) { static bool publish_cell_voltages(void) {
static JsonDocument doc; static JsonDocument doc;
static String state_topic = topic_name + "/spec_data"; static String state_topic = topic_name + "/spec_data";
#ifdef DOUBLE_BATTERY
static String state_topic_2 = topic_name + "/spec_data_2"; static String state_topic_2 = topic_name + "/spec_data_2";
#endif // DOUBLE_BATTERY
#ifdef HA_AUTODISCOVERY #ifdef HA_AUTODISCOVERY
bool failed_to_publish = false; bool failed_to_publish = false;
@ -280,24 +294,26 @@ static bool publish_cell_voltages(void) {
} }
doc.clear(); // clear after sending autoconfig doc.clear(); // clear after sending autoconfig
} }
#ifdef DOUBLE_BATTERY
// If the cell voltage number isn't initialized...
if (datalayer.battery2.info.number_of_cells != 0u) {
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { if (battery2) {
int cellNumber = i + 1; // TODO: Combine this identical block with the previous one.
set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_", " 2"); // If the cell voltage number isn't initialized...
set_common_discovery_attributes(doc); if (datalayer.battery2.info.number_of_cells != 0u) {
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true) == false) { int cellNumber = i + 1;
failed_to_publish = true; set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_", " 2");
return false; set_common_discovery_attributes(doc);
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true) == false) {
failed_to_publish = true;
return false;
}
} }
doc.clear(); // clear after sending autoconfig
} }
doc.clear(); // clear after sending autoconfig
} }
#endif // DOUBLE_BATTERY
} }
if (failed_to_publish == false) { if (failed_to_publish == false) {
ha_cell_voltages_published = true; ha_cell_voltages_published = true;
@ -324,27 +340,27 @@ static bool publish_cell_voltages(void) {
doc.clear(); doc.clear();
} }
#ifdef DOUBLE_BATTERY if (battery2) {
// If cell voltages have been populated... // If cell voltages have been populated...
if (datalayer.battery2.info.number_of_cells != 0u && if (datalayer.battery2.info.number_of_cells != 0u &&
datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) { datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) {
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>(); JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
for (size_t i = 0; i < datalayer.battery2.info.number_of_cells; ++i) { for (size_t i = 0; i < datalayer.battery2.info.number_of_cells; ++i) {
cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0); cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0);
} }
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) { if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("Cell voltage MQTT msg could not be sent"); logging.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_LOG #endif // DEBUG_LOG
return false; return false;
}
doc.clear();
} }
doc.clear();
} }
#endif // DOUBLE_BATTERY
return true; return true;
} }

View file

@ -156,18 +156,9 @@ void update_machineryprotection() {
clear_event(EVENT_SOH_LOW); clear_event(EVENT_SOH_LOW);
} }
#ifdef NISSAN_LEAF_BATTERY if (!battery->soc_plausible()) {
// Check if SOC% is plausible set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery.status.real_soc);
if (datalayer.battery.status.voltage_dV >
(datalayer.battery.info.max_design_voltage_dV -
100)) { // When pack voltage is close to max, and SOC% is still low, raise event
if (datalayer.battery.status.real_soc < 6500) { // 65.00%
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery.status.real_soc);
} else {
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
}
} }
#endif //NISSAN_LEAF_BATTERY
// Check diff between highest and lowest cell // Check diff between highest and lowest cell
cell_deviation_mV = cell_deviation_mV =

View file

@ -5,11 +5,20 @@
#include "src/devboard/utils/types.h" #include "src/devboard/utils/types.h"
class CanInverterProtocol : public InverterProtocol { class CanInverterProtocol : public InverterProtocol, Transmitter {
public: public:
virtual const char* interface_name() { return getCANInterfaceName(can_config.inverter); } virtual const char* interface_name() { return getCANInterfaceName(can_config.inverter); }
virtual void transmit_can(unsigned long currentMillis) = 0; virtual void transmit_can(unsigned long currentMillis) = 0;
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0; virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
void transmit(unsigned long currentMillis) {
if (allowed_to_send_CAN) {
transmit_can(currentMillis);
}
}
protected:
CanInverterProtocol() { register_transmitter(this); }
}; };
#endif #endif

View file

@ -34,17 +34,10 @@ void setup_inverter() {
} }
#ifdef CAN_INVERTER_SELECTED #ifdef CAN_INVERTER_SELECTED
void update_values_can_inverter() {
can_inverter->update_values();
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
can_inverter->map_can_frame_to_variable(rx_frame); can_inverter->map_can_frame_to_variable(rx_frame);
} }
void transmit_can_inverter(unsigned long currentMillis) {
can_inverter->transmit_can(currentMillis);
}
#endif #endif
#ifdef RS485_INVERTER_SELECTED #ifdef RS485_INVERTER_SELECTED

View file

@ -34,7 +34,6 @@ extern InverterProtocol* inverter;
void setup_inverter(); void setup_inverter();
#ifdef CAN_INVERTER_SELECTED #ifdef CAN_INVERTER_SELECTED
void update_values_can_inverter();
void map_can_frame_to_variable_inverter(CAN_frame rx_frame); void map_can_frame_to_variable_inverter(CAN_frame rx_frame);
void transmit_can_inverter(unsigned long currentMillis); void transmit_can_inverter(unsigned long currentMillis);
#endif #endif

View file

@ -251,7 +251,10 @@ void KostalInverterProtocol::receive_RS485() // Runs as fast as possible to han
int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100; int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100;
if (code == 0x44a) { if (code == 0x44a) {
//Send cyclic data //Send cyclic data
update_values_battery(); // TODO: Probably not a good idea to use the battery object here like this.
if (battery) {
battery->update_values();
}
update_values(); update_values();
if (f2_startup_count < 15) { if (f2_startup_count < 15) {
f2_startup_count++; f2_startup_count++;