Skip to content

Commit

Permalink
Merge pull request #195 from ruza87/pylon_rs485
Browse files Browse the repository at this point in the history
PylonTech RS485 protocol emulation
  • Loading branch information
stuartpittaway authored Dec 27, 2023
2 parents db0e666 + 55b850d commit 822a4f1
Show file tree
Hide file tree
Showing 15 changed files with 686 additions and 300 deletions.
2 changes: 1 addition & 1 deletion ESPController/data/cfg_lifepo4_16cells.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"diybms_settings":{"totalNumberOfBanks":1,"totalNumberOfSeriesModules":16,"baudRate":10000,"interpacketgap":3000,"graph_voltagehigh":3650,"graph_voltagelow":3000,"BypassOverTempShutdown":65,"BypassThresholdmV":3450,"timeZone":0,"minutesTimeZone":0,"daylight":false,"ntpServer":"time.google.com","loggingEnabled":false,"loggingFrequencySeconds":15,"currentMonitoringEnabled":true,"currentMonitoringModBusAddress":90,"rs485baudrate":19200,"rs485databits":3,"rs485parity":0,"rs485stopbits":1,"language":"en","mqtt":{"enabled":true,"uri":"mqtt://192.168.0.26:1883","topic":"emon/diybms","username":"emonpi","password":"emonpimqtt2016"},"influxdb":{"enabled":false,"apitoken":"","bucket":"bucketname","org":"organisation","url":"http://192.168.0.49:8086/api/v2/write","logfreq":15},"outputs":{"default":[153,153,153,153],"type":[0,0,0,0]},"rules":{"EmergencyStop":{"value":0,"hysteresis":0,"state":[0,0,0,0]},"BMSError":{"value":0,"hysteresis":0,"state":[0,0,0,0]},"CurrentMonitorOverCurrentAmps":{"value":100,"hysteresis":100,"state":[0,0,0,0]},"ModuleOverVoltage":{"value":3650,"hysteresis":3500,"state":[255,0,0,0]},"ModuleUnderVoltage":{"value":2900,"hysteresis":3050,"state":[255,0,0,0]},"ModuleOverTemperatureInternal":{"value":75,"hysteresis":75,"state":[0,0,0,0]},"ModuleUnderTemperatureInternal":{"value":5,"hysteresis":5,"state":[0,0,0,0]},"ModuleOverTemperatureExternal":{"value":50,"hysteresis":50,"state":[0,0,0,0]},"ModuleUnderTemperatureExternal":{"value":2,"hysteresis":2,"state":[0,0,0,0]},"CurrentMonitorOverVoltage":{"value":57600,"hysteresis":57000,"state":[0,0,0,0]},"CurrentMonitorUnderVoltage":{"value":48000,"hysteresis":49000,"state":[0,0,0,0]},"BankOverVoltage":{"value":57600,"hysteresis":57000,"state":[0,0,0,0]},"BankUnderVoltage":{"value":48000,"hysteresis":49000,"state":[0,0,0,0]},"Timer2":{"value":1020,"hysteresis":1020,"state":[0,0,0,0]},"Timer1":{"value":480,"hysteresis":480,"state":[0,0,0,0]}},"canbusprotocol":2,"nominalbatcap":280,"chargevolt":568,"chargecurrent":650,"dischargecurrent":650,"dischargevolt":488,"chargetemplow":0,"chargetemphigh":50,"dischargetemplow":-30,"dischargetemphigh":55,"stopchargebalance":false,"socoverride":false,"socforcelow":false,"dynamiccharge":true,"preventdischarge":false,"preventcharging":false,"cellminmv":3050,"cellmaxmv":3460,"kneemv":3320,"cellmaxspikemv":3550,"sensitivity":27,"cur_val1":36,"cur_val2":7,"tilevisibility":[49152,0,62209,0,0]}}
{"diybms_settings":{"totalNumberOfBanks":1,"totalNumberOfSeriesModules":16,"baudRate":10000,"interpacketgap":3000,"graph_voltagehigh":3650,"graph_voltagelow":3000,"BypassOverTempShutdown":65,"BypassThresholdmV":3450,"timeZone":0,"minutesTimeZone":0,"daylight":false,"ntpServer":"time.google.com","loggingEnabled":false,"loggingFrequencySeconds":15,"currentMonitoringEnabled":true,"currentMonitoringModBusAddress":90,"rs485baudrate":19200,"rs485databits":3,"rs485parity":0,"rs485stopbits":1,"language":"en","mqtt":{"enabled":true,"uri":"mqtt://192.168.0.26:1883","topic":"emon/diybms","username":"emonpi","password":"emonpimqtt2016"},"influxdb":{"enabled":false,"apitoken":"","bucket":"bucketname","org":"organisation","url":"http://192.168.0.49:8086/api/v2/write","logfreq":15},"outputs":{"default":[153,153,153,153],"type":[0,0,0,0]},"rules":{"EmergencyStop":{"value":0,"hysteresis":0,"state":[0,0,0,0]},"BMSError":{"value":0,"hysteresis":0,"state":[0,0,0,0]},"CurrentMonitorOverCurrentAmps":{"value":100,"hysteresis":100,"state":[0,0,0,0]},"ModuleOverVoltage":{"value":3650,"hysteresis":3500,"state":[255,0,0,0]},"ModuleUnderVoltage":{"value":2900,"hysteresis":3050,"state":[255,0,0,0]},"ModuleOverTemperatureInternal":{"value":75,"hysteresis":75,"state":[0,0,0,0]},"ModuleUnderTemperatureInternal":{"value":5,"hysteresis":5,"state":[0,0,0,0]},"ModuleOverTemperatureExternal":{"value":50,"hysteresis":50,"state":[0,0,0,0]},"ModuleUnderTemperatureExternal":{"value":2,"hysteresis":2,"state":[0,0,0,0]},"CurrentMonitorOverVoltage":{"value":57600,"hysteresis":57000,"state":[0,0,0,0]},"CurrentMonitorUnderVoltage":{"value":48000,"hysteresis":49000,"state":[0,0,0,0]},"BankOverVoltage":{"value":57600,"hysteresis":57000,"state":[0,0,0,0]},"BankUnderVoltage":{"value":48000,"hysteresis":49000,"state":[0,0,0,0]},"Timer2":{"value":1020,"hysteresis":1020,"state":[0,0,0,0]},"Timer1":{"value":480,"hysteresis":480,"state":[0,0,0,0]}},"protocol":2,"nominalbatcap":280,"chargevolt":568,"chargecurrent":650,"dischargecurrent":650,"dischargevolt":488,"chargetemplow":0,"chargetemphigh":50,"dischargetemplow":-30,"dischargetemphigh":55,"stopchargebalance":false,"socoverride":false,"socforcelow":false,"dynamiccharge":true,"preventdischarge":false,"preventcharging":false,"cellminmv":3050,"cellmaxmv":3460,"kneemv":3320,"cellmaxspikemv":3550,"sensitivity":27,"cur_val1":36,"cur_val2":7,"tilevisibility":[49152,0,62209,0,0]}}
135 changes: 12 additions & 123 deletions ESPController/include/HAL_ESP32.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,135 +107,24 @@ class HAL_ESP32

void CANBUSEnable(bool value);

bool IsVSPIMutexAvailable()
{
if (xVSPIMutex == NULL)
return false;
bool IsVSPIMutexAvailable();
bool GetVSPIMutex();
bool ReleaseVSPIMutex();

return (uxSemaphoreGetCount(xVSPIMutex) == 1);
}
bool GetDisplayMutex();
bool ReleaseDisplayMutex();

bool GetDisplayMutex()
{
if (xDisplayMutex == NULL)
return false;

// Wait 100ms max
if (xSemaphoreTake(xDisplayMutex, pdMS_TO_TICKS(100)) == pdFALSE)
{
ESP_LOGE(TAG, "Unable to get Display mutex");
return false;
}
return true;
}
bool ReleaseDisplayMutex()
{
if (xDisplayMutex == NULL)
return false;

return (xSemaphoreGive(xDisplayMutex) == pdTRUE);
}
bool Geti2cMutex();
bool Releasei2cMutex();

bool GetVSPIMutex()
{
if (xVSPIMutex == NULL)
return false;

// Wait 100ms max
if (xSemaphoreTake(xVSPIMutex, pdMS_TO_TICKS(100)) == pdFALSE)
{
ESP_LOGE(TAG, "Unable to get VSPI mutex");
return false;
}
return true;
}
bool ReleaseVSPIMutex()
{
if (xVSPIMutex == NULL)
return false;

if (xSemaphoreGive(xVSPIMutex) == pdFALSE)
{
ESP_LOGE(TAG, "Unable to release VSPI mutex");
return false;
}
return true;
}

bool Geti2cMutex()
{
if (xi2cMutex == NULL)
return false;

// Wait 100ms max
if (xSemaphoreTake(xi2cMutex, pdMS_TO_TICKS(100)) == pdFALSE)
{
ESP_LOGE(TAG, "Unable to get I2C mutex");
return false;
}
return true;
}
bool Releasei2cMutex()
{
if (xi2cMutex == NULL)
return false;

if (xSemaphoreGive(xi2cMutex) == pdFALSE)
{
ESP_LOGE(TAG, "Unable to release I2C mutex");
return false;
}
return true;
}

bool GetRS485Mutex()
{
if (RS485Mutex == NULL)
return false;

// Wait 100ms max
if (xSemaphoreTake(RS485Mutex, pdMS_TO_TICKS(100)) == pdFALSE)
{
ESP_LOGE(TAG, "Unable to get RS485 mutex");
return false;
}
return true;
}
bool ReleaseRS485Mutex()
{
if (RS485Mutex == NULL)
return false;

if (xSemaphoreGive(RS485Mutex) == pdFALSE)
{
ESP_LOGE(TAG, "Unable to release RS485 mutex");
return false;
}
return true;
}
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();
Expand Down
17 changes: 2 additions & 15 deletions ESPController/include/Rules.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,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;
Expand All @@ -180,13 +173,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;
Expand Down
9 changes: 5 additions & 4 deletions ESPController/include/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,13 @@ enum CanBusInverter : uint8_t
};


enum CanBusProtocolEmulation : uint8_t
enum ProtocolEmulation : uint8_t
{
CANBUS_DISABLED = 0x00,
EMULATION_DISABLED = 0x00,
CANBUS_VICTRON = 0x01,
CANBUS_PYLONTECH = 0x02,
CANBUS_PYLONFORCEH2 = 0x03
CANBUS_PYLONFORCEH2 = 0x03,
RS485_PYLONTECH = 0x04
};

enum CurrentMonitorDevice : uint8_t
Expand Down Expand Up @@ -183,7 +184,7 @@ struct diybms_eeprom_settings

char language[2 + 1];

CanBusProtocolEmulation canbusprotocol;
ProtocolEmulation protocol;
CanBusInverter canbusinverter;
//CANBUS baud rate, 250=250k, 500=500k
uint16_t canbusbaud;
Expand Down
54 changes: 54 additions & 0 deletions ESPController/include/pylon_rs485.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#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 insertLength(char *buf, int payload_len);
int appendChecksum(char *buf, int buf_size, int payload_len);
};

#endif
1 change: 1 addition & 0 deletions ESPController/include/webserver_json_post.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 822a4f1

Please sign in to comment.