158 lines
8.7 KiB
C++
158 lines
8.7 KiB
C++
#include "cbf_pylon.h"
|
|
|
|
namespace solar
|
|
{
|
|
// see common.h for definition of publish_spec_t
|
|
// publish_spec_t(int on_count, int interval, int timeout)
|
|
const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 15, 30); // default publish spec for Pylontech battery messages
|
|
const publish_spec_t cbf_pylon::rtr_publish_spec = publish_spec_t(2, 10, 15); // for remote transmission requests
|
|
|
|
cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src)
|
|
{
|
|
if (this != &src) {
|
|
cb_frame::operator=(std::move(src));
|
|
src.clear();
|
|
//ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str());
|
|
}
|
|
return *this;
|
|
}
|
|
void cbf_pylon::clear()
|
|
{
|
|
cb_frame::clear();
|
|
}
|
|
|
|
// float cbf_pylon::_get_battery_charge_voltage_limit() const
|
|
// {
|
|
// const auto& x = this->frame;
|
|
// return 0.1 * (float)((x[1] << 8) + x[0]);
|
|
// }
|
|
|
|
// Function to build a message from the pylon canbus frame
|
|
std::string cbf_pylon::to_string() const
|
|
{
|
|
char buffer[80];
|
|
switch (can_id)
|
|
{
|
|
case CB_BATTERY_LIMITS:
|
|
{
|
|
if(rtr) {
|
|
return "Request for BATTERY LIMITS info";
|
|
}
|
|
if (this->frame.size() < 6) {
|
|
return "Invalid frame size for CB_BATTERY_LIMITS";
|
|
}
|
|
const auto& x = this->frame;
|
|
float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V
|
|
float charge_current_limit = 0.1 * static_cast<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A
|
|
float discharge_current_limit = 0.1 * static_cast<int16_t>((x[5] << 8) + x[4]); // unit = 0.1A
|
|
snprintf(buffer, sizeof(buffer), "BATTERY MAX CHARGE: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit);
|
|
return buffer;
|
|
}
|
|
break;
|
|
case CB_BATTERY_STATE:
|
|
{
|
|
if(rtr) {
|
|
return "Request for BATTERY STATE info";
|
|
}
|
|
if (this->frame.size() < 4) {
|
|
return "Invalid frame size for CB_BATTERY_STATE";
|
|
}
|
|
const auto& x = this->frame;
|
|
uint soc = static_cast<uint16_t>((x[1] << 8) + x[0]);
|
|
uint soh = static_cast<uint16_t>((x[3] << 8) + x[2]);
|
|
snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh);
|
|
return buffer;
|
|
}
|
|
break;
|
|
case CB_BATTERY_STATUS:
|
|
{
|
|
if(rtr) {
|
|
return "Request for BATTERY STATUS info";
|
|
}
|
|
if (this->frame.size() < 6) {
|
|
return "Invalid frame size for CB_BATTERY_STATUS";
|
|
}
|
|
const auto& x = this->frame;
|
|
float system_voltage = 0.01 * static_cast<int16_t>((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system
|
|
float system_current = 0.1 * static_cast<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current
|
|
float average_cell_temperature = 0.1 * static_cast<int16_t>((x[5] << 8) + x[4]); // unit = 0.1°C
|
|
snprintf(buffer, sizeof(buffer), "BATTERY STATUS: VSYS= %.2fV ISYS= %.1fA TSYS= %.1f°C", system_voltage, system_current, average_cell_temperature);
|
|
return buffer;
|
|
}
|
|
break;
|
|
case CB_BATTERY_FAULT:
|
|
{
|
|
if(rtr) {
|
|
return "Request for BATTERY FAULT info";
|
|
}
|
|
if (this->frame.size() < 8) {
|
|
return "Invalid frame size for CB_BATTERY_FAULT";
|
|
}
|
|
const auto& x = this->frame;
|
|
uint8_t protection1 = x[0];
|
|
uint8_t protection2 = x[1];
|
|
uint8_t alarm1 = x[2];
|
|
uint8_t alarm2 = x[3];
|
|
uint8_t module_numbers = x[4];
|
|
char ch5 = x[5];
|
|
char ch6 = x[6];
|
|
bool discharge_over_current = protection1 & 0x80;
|
|
bool cell_under_temperature = protection1 & 0x10;
|
|
bool cell_over_temperature = protection1 & 0x08;
|
|
bool cell_or_module_under_voltage = protection1 & 0x04;
|
|
bool cell_or_module_over_voltage = protection1 & 0x02;
|
|
bool system_error = protection2 & 0x8;
|
|
bool charge_over_current = protection2 & 0x01;
|
|
bool discharge_high_current = alarm1 & 0x80;
|
|
bool cell_low_temperature = alarm1 & 0x10;
|
|
bool cell_high_temperature = alarm1 & 0x08;
|
|
bool cell_or_module_low_voltage = alarm1 & 0x04;
|
|
bool cell_or_module_high_voltage = alarm1 & 0x02;
|
|
bool internal_communication_fail = alarm2 & 0x8;
|
|
bool charge_high_current = alarm2 & 0x01;
|
|
snprintf(buffer, sizeof(buffer), "BATTERY PROTECT: %s%s%s%s%s%s%s ALARM= %s%s%s%s%s%s%s MN=%d %c%c", discharge_over_current ? "DOC " : "", cell_under_temperature ? "CUT " : "", cell_over_temperature ? "COT " : "", cell_or_module_under_voltage ? "CMUV " : "", cell_or_module_over_voltage ? "CMOV" : "", system_error ? "SERR " : "", charge_over_current ? "COC ": "", discharge_high_current ? "DHC " : "", cell_low_temperature ? "CLT " : "", cell_high_temperature ? "CHT " : "", cell_or_module_low_voltage ? "CMLV " : "", cell_or_module_high_voltage ? "CMHV" : "", internal_communication_fail ? "ICF " : "", charge_high_current ? "CHC ": "", module_numbers, ch5, ch6);
|
|
return buffer;
|
|
}
|
|
break;
|
|
case CB_BATTERY_REQUEST_FLAGS:
|
|
{
|
|
if(rtr) {
|
|
return "Request for BATTERY REQUEST FLAGS info";
|
|
}
|
|
if (this->frame.size() < 1) {
|
|
return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS";
|
|
}
|
|
const auto& x = this->frame;
|
|
uint8_t request_flag = x[0];
|
|
bool charge_enable = request_flag & 0x80;
|
|
bool discharge_enable = request_flag & 0x40;
|
|
bool request_force_charge1 = request_flag & 0x20; // use bit 5, the SOC range is: 15~19%. Bit 4 is NULL. Bit 5 is designed for inverter allows battery to shut down, and able to wake battery up to charge it.
|
|
bool request_force_charge2 = request_flag & 0x10; // Bit 5 the SOC range is 5~10%, Bit 4 the SOC range is 9~13%. Bit 4 is designed for inverter doesn`t want battery to shut down, able to charge battery before shut down to avoid low energy. We suggest inverter to use this bit, In this case, inverter itself should set a threshold of SOC: after force charge, only when battery SOC is higher than this threshold then inverter will allow discharge, to avoid force charge and discharge status change frequently.
|
|
bool request_full_charge = request_flag & 0x08; // Reason: when battery is not full charged for long time, the accumulative error of SOC calculation will be too high and may not able to be charged or discharged as expected capacity. Logic: if SOC never higher than 97% in 30 days, will set this flag to 1. And when the SOC is 97%, the flag will be 0. How to: we suggest inverter to charge the battery by grid when this flag is 1.
|
|
snprintf(buffer, sizeof(buffer), "BATTERY REQUEST: %s%s%s%s%s", charge_enable ? "CE " : "", discharge_enable ? "DE " : "", request_force_charge1 ? "RFORCECH1 " : "", request_force_charge2 ? "RFORCECH2 " : "", request_full_charge ? "RFULLCH" : "");
|
|
return buffer;
|
|
}
|
|
break;
|
|
case CB_BATTERY_MANUFACTURER:
|
|
{
|
|
if(rtr) {
|
|
return "Request for BATTERY MANUFACTURER info";
|
|
}
|
|
if (this->frame.size() < 8) {
|
|
return "Invalid frame size for CB_BATTERY_MANUFACTURER";
|
|
}
|
|
const auto& x = this->frame;
|
|
// Manufacturer name is in the first 8 bytes, padded with spaces
|
|
// Convert to string and trim trailing spaces
|
|
std::string manufacturer(reinterpret_cast<const char*>(x.data()), 8);
|
|
manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end());
|
|
return "BATTERY OEM: " + manufacturer;
|
|
}
|
|
break;
|
|
}
|
|
if(rtr) {
|
|
return "Request for unknown pylon CAN ID";
|
|
}
|
|
return "Unknown CAN ID";
|
|
}
|
|
} // namespace solar
|