Merge branch 'main' into improvement/webserver-robustness

This commit is contained in:
Daniel Öster 2025-06-10 13:00:19 +03:00
commit 4f96ed40dd
45 changed files with 143 additions and 184 deletions

View file

@ -41,21 +41,4 @@ void setup_battery() {
#endif
}
// transmit_can_battery is called once and we need to
// call both batteries.
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
((CanBattery*)battery)->handle_incoming_can_frame(rx_frame);
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
((CanBattery*)battery2)->handle_incoming_can_frame(rx_frame);
}
#ifdef RS485_BATTERY_SELECTED
void receive_RS485() {
((RS485Battery*)battery)->receive_RS485();
}
#endif
#endif

View file

@ -13,7 +13,6 @@ void setup_can_shunt();
#ifdef BMW_SBOX
#include "BMW-SBOX.h"
void handle_incoming_can_frame_shunt(CAN_frame rx_frame);
#endif
#ifdef BMW_I3_BATTERY
@ -160,14 +159,4 @@ void handle_incoming_can_frame_shunt(CAN_frame rx_frame);
void setup_battery(void);
#ifdef RS485_BATTERY_SELECTED
void transmit_rs485(unsigned long currentMillis);
void receive_RS485();
#else
void handle_incoming_can_frame_battery(CAN_frame rx_frame);
void transmit_can_battery(unsigned long currentMillis);
#endif
void handle_incoming_can_frame_battery2(CAN_frame rx_frame);
#endif

View file

@ -44,7 +44,7 @@ class Battery {
virtual void toggle_SOC_method() {}
virtual void set_fake_voltage(float v) {}
virtual float get_voltage() { static_cast<float>(datalayer.battery.status.voltage_dV) / 10.0; }
virtual float get_voltage() { return static_cast<float>(datalayer.battery.status.voltage_dV) / 10.0; }
// This allows for battery specific SOC plausibility calculations to be performed.
virtual bool soc_plausible() { return true; }

View file

@ -341,6 +341,8 @@ void ChademoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
handle_chademo_sequence();
ISA_handleFrame(&rx_frame);
}
/* (re)initialize evse structures to pre-charge/discharge states */

View file

@ -4,10 +4,11 @@
#include "Battery.h"
#include "src/communication/Transmitter.h"
#include "src/communication/can/CanReceiver.h"
#include "src/devboard/utils/types.h"
// Abstract base class for batteries using the CAN bus
class CanBattery : public Battery, Transmitter {
class CanBattery : public Battery, Transmitter, CanReceiver {
public:
virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
@ -16,17 +17,21 @@ class CanBattery : public Battery, Transmitter {
void transmit(unsigned long currentMillis) { transmit_can(currentMillis); }
void receive_can_frame(CAN_frame* frame) { handle_incoming_can_frame(*frame); }
protected:
CAN_Interface can_interface;
CanBattery() {
can_interface = can_config.battery;
register_transmitter(this);
register_can_receiver(this, can_interface);
}
CanBattery(CAN_Interface interface) {
can_interface = interface;
register_transmitter(this);
register_can_receiver(this, can_interface);
}
};

View file

@ -178,7 +178,7 @@ void DalyBms::transmit_rs485(unsigned long currentMillis) {
}
}
void DalyBms::receive_RS485() {
void DalyBms::receive() {
static uint8_t recv_buff[13] = {0};
static uint8_t recv_len = 0;

View file

@ -23,7 +23,7 @@ class DalyBms : public RS485Battery {
void setup();
void update_values();
void transmit_rs485(unsigned long currentMillis);
void receive_RS485();
void receive();
private:
int baud_rate() { return 9600; }

View file

@ -41,12 +41,16 @@ class GeelyGeometryCBattery : public CanBattery {
private:
GeelyGeometryCHtmlRenderer renderer;
const int MAX_PACK_VOLTAGE_70_DV 4420 //70kWh
const int MIN_PACK_VOLTAGE_70_DV 2860 const int MAX_PACK_VOLTAGE_53_DV 4160 //53kWh
const int MIN_PACK_VOLTAGE_53_DV 2700 const int MAX_CELL_DEVIATION_MV 150 const int
MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
DATALAYER_BATTERY_TYPE* datalayer_battery;
static const int MAX_PACK_VOLTAGE_70_DV = 4420; //70kWh
static const int MIN_PACK_VOLTAGE_70_DV = 2860;
static const int MAX_PACK_VOLTAGE_53_DV = 4160; //53kWh
static const int MIN_PACK_VOLTAGE_53_DV = 2700;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_GEELY_GEOMETRY_C* datalayer_geometryc;
CAN_frame GEELY_191 = {.FD = false, //PAS_APA_Status , 10ms
@ -213,7 +217,7 @@ class GeelyGeometryCBattery : public CanBattery {
uint16_t poll_unknown7 = 0;
uint16_t poll_unknown8 = 0;
int16_t poll_temperature[6] = {0};
#define TEMP_OFFSET 30 //TODO, not calibrated yet, best guess
static const int TEMP_OFFSET = 30; //TODO, not calibrated yet, best guess
uint8_t poll_software_version[16] = {0};
uint8_t poll_hardware_version[16] = {0};
};

View file

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

View file

@ -1,11 +1,11 @@
#ifndef _SHUNT_H
#define _SHUNT_H
#include "../communication/can/comm_can.h"
#include "src/communication/Transmitter.h"
#include "src/communication/can/CanReceiver.h"
#include "src/devboard/utils/types.h"
class CanShunt : public Transmitter {
class CanShunt : public Transmitter, CanReceiver {
public:
virtual void setup() = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
@ -17,12 +17,15 @@ class CanShunt : public Transmitter {
}
}
void receive_can_frame(CAN_frame* frame) { handle_incoming_can_frame(*frame); }
protected:
CAN_Interface can_interface;
CanShunt() {
can_interface = can_config.battery;
register_transmitter(this);
register_can_receiver(this, can_interface);
}
};

View file

@ -4,16 +4,10 @@
CanShunt* shunt = nullptr;
void setup_can_shunt() {
#if defined(CAN_SHUNT_SELECTED) && defined(SELECTED_SHUNT_CLASS)
#if 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

@ -5,6 +5,7 @@
#include "../datalayer/datalayer.h"
#include "src/communication/Transmitter.h"
#include "src/communication/can/CanReceiver.h"
enum class ChargerType { NissanLeaf, ChevyVolt };
@ -37,7 +38,7 @@ class Charger {
};
// Base class for chargers on a CAN bus
class CanCharger : public Charger, Transmitter {
class CanCharger : public Charger, Transmitter, CanReceiver {
public:
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
@ -48,8 +49,13 @@ class CanCharger : public Charger, Transmitter {
}
}
void receive_can_frame(CAN_frame* frame) { map_can_frame_to_variable(*frame); }
protected:
CanCharger(ChargerType type) : Charger(type) { register_transmitter(this); }
CanCharger(ChargerType type) : Charger(type) {
register_transmitter(this);
register_can_receiver(this, can_config.charger);
}
};
#endif

View file

@ -0,0 +1,15 @@
#ifndef _CANRECEIVER_H
#define _CANRECEIVER_H
#include "src/devboard/utils/types.h"
#include "src/include.h"
class CanReceiver {
public:
virtual void receive_can_frame(CAN_frame* rx_frame) = 0;
};
// Register a receiver object for a given CAN interface
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface);
#endif

View file

@ -1,4 +1,5 @@
#include "comm_can.h"
#include <map>
#include "../../include.h"
#include "src/devboard/sdcard/sdcard.h"
@ -10,6 +11,8 @@ volatile bool send_ok_2515 = 0;
volatile bool send_ok_2518 = 0;
static unsigned long previousMillis10 = 0;
void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface);
#ifdef CAN_ADDON
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
SPIClass SPI2515;
@ -273,7 +276,13 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) {
}
}
void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) {
static std::multimap<CAN_Interface, CanReceiver*> can_receivers;
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface) {
can_receivers.insert({interface, receiver});
}
void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface) {
if (interface !=
CANFD_NATIVE) { //Avoid printing twice due to receive_frame_canfd_addon sending to both FD interfaces
//TODO: This check can be removed later when refactored to use inline functions for logging
@ -288,31 +297,15 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) {
}
#endif
if (interface == can_config.battery) {
#ifndef RS485_BATTERY_SELECTED
handle_incoming_can_frame_battery(*rx_frame);
#endif
#ifdef CHADEMO_BATTERY
ISA_handleFrame(rx_frame);
#endif
}
if (interface == can_config.inverter) {
#ifdef CAN_INVERTER_SELECTED
map_can_frame_to_variable_inverter(*rx_frame);
#endif
}
if (interface == can_config.battery_double && battery2) {
handle_incoming_can_frame_battery2(*rx_frame);
}
if (interface == can_config.charger && charger) {
charger->map_can_frame_to_variable(*rx_frame);
}
if (interface == can_config.shunt) {
#ifdef CAN_SHUNT_SELECTED
handle_incoming_can_frame_shunt(*rx_frame);
#endif
// Send the frame to all the receivers registered for this interface.
auto receivers = can_receivers.equal_range(interface);
for (auto it = receivers.first; it != receivers.second; ++it) {
auto& receiver = it->second;
receiver->receive_can_frame(rx_frame);
}
}
void dump_can_frame(CAN_frame& frame, frameDirection msgDir) {
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer

View file

@ -27,16 +27,6 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface);
*/
void init_CAN();
/**
* @brief Transmit one CAN frame
*
* @param[in] CAN_frame* tx_frame
* @param[in] int interface
*
* @return void
*/
void transmit_can_frame();
/**
* @brief Receive CAN messages from all interfaces
*
@ -82,14 +72,4 @@ void receive_frame_canfd_addon();
*/
void print_can_frame(CAN_frame frame, frameDirection msgDir);
/**
* @brief Map CAN frame from specified interface to variable
*
* @param[in] CAN_frame* rx_frame
* @param[in] int interface
*
* @return void
*/
void map_can_frame_to_variable(CAN_frame* rx_frame, int interface);
#endif

View file

@ -1,6 +1,8 @@
#include "comm_rs485.h"
#include "../../include.h"
#include <list>
void init_rs485() {
#ifdef RS485_EN_PIN
pinMode(RS485_EN_PIN, OUTPUT);
@ -18,3 +20,15 @@ void init_rs485() {
// Inverters and batteries are expected to initialize their serial port in their setup-function
// for RS485 or Modbus comms.
}
static std::list<Rs485Receiver*> receivers;
void receive_rs485() {
for (auto& receiver : receivers) {
receiver->receive();
}
}
void register_receiver(Rs485Receiver* receiver) {
receivers.push_back(receiver);
}

View file

@ -1,12 +1,6 @@
#ifndef _COMM_RS485_H_
#define _COMM_RS485_H_
#include "../../include.h"
#include "../../lib/eModbus-eModbus/Logging.h"
#include "../../lib/eModbus-eModbus/ModbusServerRTU.h"
#include "../../lib/eModbus-eModbus/scripts/mbServerFCs.h"
/**
* @brief Initialization of RS485
*
@ -16,4 +10,17 @@
*/
void init_rs485();
// Defines an interface for any object that needs to receive a signal to handle RS485 comm.
// Can be extended later for more complex operation.
class Rs485Receiver {
public:
virtual void receive() = 0;
};
// Forwards the call to all registered RS485 receivers
void receive_rs485();
// Registers the given object as a receiver.
void register_receiver(Rs485Receiver* receiver);
#endif

View file

@ -212,17 +212,18 @@ void update_machineryprotection() {
clear_event(EVENT_CAN_CORRUPTED_WARNING);
}
#ifdef CAN_INVERTER_SELECTED
// Check if the inverter is still sending CAN messages. If we go 60s without messages we raise a warning
if (!datalayer.system.status.CAN_inverter_still_alive) {
set_event(EVENT_CAN_INVERTER_MISSING, can_config.inverter);
} else {
datalayer.system.status.CAN_inverter_still_alive--;
clear_event(EVENT_CAN_INVERTER_MISSING);
if (inverter && inverter->interface_type() == InverterInterfaceType::Can) {
// Check if the inverter is still sending CAN messages. If we go 60s without messages we raise a warning
if (!datalayer.system.status.CAN_inverter_still_alive) {
set_event(EVENT_CAN_INVERTER_MISSING, can_config.inverter);
} else {
datalayer.system.status.CAN_inverter_still_alive--;
clear_event(EVENT_CAN_INVERTER_MISSING);
}
}
#endif //CAN_INVERTER_SELECTED
if (charger) {
// Assuming chargers are all CAN here.
// Check if the charger is still sending CAN messages. If we go 60s without messages we raise a warning
if (!datalayer.charger.CAN_charger_still_alive) {
set_event(EVENT_CAN_CHARGER_MISSING, can_config.charger);

View file

@ -3,7 +3,6 @@
#include "../include.h"
#ifdef AFORE_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS AforeCanInverter
#endif

View file

@ -3,7 +3,6 @@
#include "../include.h"
#ifdef BYD_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS BydCanInverter
#endif

View file

@ -3,7 +3,6 @@
#include "../include.h"
#ifdef BYD_MODBUS
#define MODBUS_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS BydModbusInverter
#endif

View file

@ -3,11 +3,14 @@
#include "InverterProtocol.h"
#include "src/communication/can/CanReceiver.h"
#include "src/devboard/utils/types.h"
class CanInverterProtocol : public InverterProtocol, Transmitter {
class CanInverterProtocol : public InverterProtocol, Transmitter, CanReceiver {
public:
virtual const char* interface_name() { return getCANInterfaceName(can_config.inverter); }
InverterInterfaceType interface_type() { return InverterInterfaceType::Can; }
virtual void transmit_can(unsigned long currentMillis) = 0;
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
@ -17,8 +20,13 @@ class CanInverterProtocol : public InverterProtocol, Transmitter {
}
}
void receive_can_frame(CAN_frame* frame) { map_can_frame_to_variable(*frame); }
protected:
CanInverterProtocol() { register_transmitter(this); }
CanInverterProtocol() {
register_transmitter(this);
register_can_receiver(this, can_config.inverter);
}
};
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef FERROAMP_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS FerroampCanInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef FOXESS_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS FoxessCanInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef GROWATT_HV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS GrowattHvInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef GROWATT_LV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS GrowattLvInverter
#endif

View file

@ -1,47 +1,18 @@
#include "../include.h"
// These functions adapt the old C-style global functions inverter-API to the
// object-oriented inverter protocol API.
InverterProtocol* inverter = nullptr;
#ifdef CAN_INVERTER_SELECTED
CanInverterProtocol* can_inverter;
#endif
#ifdef MODBUS_INVERTER_SELECTED
ModbusInverterProtocol* modbus_inverter;
#endif
void setup_inverter() {
#ifdef MODBUS_INVERTER_SELECTED
modbus_inverter = new SELECTED_INVERTER_CLASS();
inverter = modbus_inverter;
#endif
if (inverter) {
// The inverter is setup only once.
return;
}
#ifdef CAN_INVERTER_SELECTED
can_inverter = new SELECTED_INVERTER_CLASS();
inverter = can_inverter;
#endif
#ifdef RS485_INVERTER_SELECTED
#ifdef SELECTED_INVERTER_CLASS
inverter = new SELECTED_INVERTER_CLASS();
#endif
if (inverter) {
inverter->setup();
}
}
#ifdef CAN_INVERTER_SELECTED
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
can_inverter->map_can_frame_to_variable(rx_frame);
}
#endif
#ifdef RS485_INVERTER_SELECTED
void receive_RS485() {
((Rs485InverterProtocol*)inverter)->receive_RS485();
}
#endif

View file

@ -33,13 +33,4 @@ extern InverterProtocol* inverter;
// Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected.
void setup_inverter();
#ifdef CAN_INVERTER_SELECTED
void map_can_frame_to_variable_inverter(CAN_frame rx_frame);
void transmit_can_inverter(unsigned long currentMillis);
#endif
#ifdef RS485_INVERTER_SELECTED
void receive_RS485();
#endif
#endif

View file

@ -1,11 +1,14 @@
#ifndef INVERTER_PROTOCOL_H
#define INVERTER_PROTOCOL_H
enum class InverterInterfaceType { Can, Rs485, Modbus };
// The abstract base class for all inverter protocols
class InverterProtocol {
public:
virtual void setup() = 0;
virtual const char* interface_name() = 0;
virtual InverterInterfaceType interface_type() = 0;
// This function maps all the values fetched from battery to the correct battery emulator data structures
virtual void update_values() = 0;

View file

@ -203,7 +203,7 @@ void KostalInverterProtocol::update_values() {
}
}
void KostalInverterProtocol::receive_RS485() // Runs as fast as possible to handle the serial stream
void KostalInverterProtocol::receive() // Runs as fast as possible to handle the serial stream
{
currentMillis = millis();

View file

@ -20,7 +20,7 @@
class KostalInverterProtocol : public Rs485InverterProtocol {
public:
void setup();
void receive_RS485();
void receive();
void update_values();
private:

View file

@ -10,7 +10,8 @@ extern uint16_t mbPV[];
// The abstract base class for all Modbus inverter protocols
class ModbusInverterProtocol : public InverterProtocol {
virtual const char* interface_name() { return "RS485 / Modbus"; }
const char* interface_name() { return "RS485 / Modbus"; }
InverterInterfaceType interface_type() { return InverterInterfaceType::Modbus; }
protected:
// Create a ModbusRTU server instance with 2000ms timeout

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef PYLON_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS PylonInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef PYLON_LV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS PylonLvInverter
#endif

View file

@ -1,12 +1,14 @@
#ifndef RS485CANINVERTER_PROTOCOL_H
#define RS485INVERTER_PROTOCOL_H
#ifndef _RS485INVERTER_PROTOCOL_H
#define _RS485INVERTER_PROTOCOL_H
#include "InverterProtocol.h"
class Rs485InverterProtocol : public InverterProtocol {
#include "src/communication/rs485/comm_rs485.h"
class Rs485InverterProtocol : public InverterProtocol, Rs485Receiver {
public:
virtual const char* interface_name() { return "RS485"; }
virtual void receive_RS485() = 0;
InverterInterfaceType interface_type() { return InverterInterfaceType::Rs485; }
virtual int baud_rate() = 0;
};

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SCHNEIDER_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SchneiderInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SMA_BYD_H_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaBydHInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SMA_BYD_HVS_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaBydHvsInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SMA_LV_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaLvInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SMA_TRIPOWER_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SmaTripowerInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SOFAR_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SofarInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SOLAX_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SolaxInverter
#endif

View file

@ -5,7 +5,6 @@
#include "CanInverterProtocol.h"
#ifdef SUNGROW_CAN
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS SungrowInverter
#endif