OpenDTU-old/src/PylontechCanReceiver.cpp

345 lines
12 KiB
C++

// SPDX-License-Identifier: GPL-2.0-or-later
#include "PylontechCanReceiver.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "PinMapping.h"
#include <driver/twai.h>
#include <ctime>
//#define PYLONTECH_DUMMY
bool PylontechCanReceiver::init(bool verboseLogging)
{
_verboseLogging = verboseLogging;
MessageOutput.println(F("[Pylontech] Initialize interface..."));
const PinMapping_t& pin = PinMapping.get();
MessageOutput.printf("[Pylontech] Interface rx = %d, tx = %d\r\n",
pin.battery_rx, pin.battery_tx);
if (pin.battery_rx < 0 || pin.battery_tx < 0) {
MessageOutput.println(F("[Pylontech] Invalid pin config"));
return false;
}
auto tx = static_cast<gpio_num_t>(pin.battery_tx);
auto rx = static_cast<gpio_num_t>(pin.battery_rx);
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(tx, rx, TWAI_MODE_NORMAL);
// Initialize configuration structures using macro initializers
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// Install TWAI driver
esp_err_t twaiLastResult = twai_driver_install(&g_config, &t_config, &f_config);
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.println(F("[Pylontech] Twai driver installed"));
break;
case ESP_ERR_INVALID_ARG:
MessageOutput.println(F("[Pylontech] Twai driver install - invalid arg"));
return false;
break;
case ESP_ERR_NO_MEM:
MessageOutput.println(F("[Pylontech] Twai driver install - no memory"));
return false;
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println(F("[Pylontech] Twai driver install - invalid state"));
return false;
break;
}
// Start TWAI driver
twaiLastResult = twai_start();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.println(F("[Pylontech] Twai driver started"));
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println(F("[Pylontech] Twai driver start - invalid state"));
return false;
break;
}
return true;
}
void PylontechCanReceiver::deinit()
{
// Stop TWAI driver
esp_err_t twaiLastResult = twai_stop();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.println(F("[Pylontech] Twai driver stopped"));
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println(F("[Pylontech] Twai driver stop - invalid state"));
break;
}
// Uninstall TWAI driver
twaiLastResult = twai_driver_uninstall();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.println(F("[Pylontech] Twai driver uninstalled"));
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println(F("[Pylontech] Twai driver uninstall - invalid state"));
break;
}
}
void PylontechCanReceiver::loop()
{
#ifdef PYLONTECH_DUMMY
return dummyData();
#endif
// Check for messages. twai_receive is blocking when there is no data so we return if there are no frames in the buffer
twai_status_info_t status_info;
esp_err_t twaiLastResult = twai_get_status_info(&status_info);
if (twaiLastResult != ESP_OK) {
switch (twaiLastResult) {
case ESP_ERR_INVALID_ARG:
MessageOutput.println(F("[Pylontech] Twai driver get status - invalid arg"));
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println(F("[Pylontech] Twai driver get status - invalid state"));
break;
}
return;
}
if (status_info.msgs_to_rx == 0) {
return;
}
// Wait for message to be received, function is blocking
twai_message_t rx_message;
if (twai_receive(&rx_message, pdMS_TO_TICKS(100)) != ESP_OK) {
MessageOutput.println(F("[Pylontech] Failed to receive message"));
return;
}
switch (rx_message.identifier) {
case 0x351: {
_stats->_chargeVoltage = this->scaleValue(this->readUnsignedInt16(rx_message.data), 0.1);
_stats->_chargeCurrentLimitation = this->scaleValue(this->readSignedInt16(rx_message.data + 2), 0.1);
_stats->_dischargeCurrentLimitation = this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] chargeVoltage: %f chargeCurrentLimitation: %f dischargeCurrentLimitation: %f\n",
_stats->_chargeVoltage, _stats->_chargeCurrentLimitation, _stats->_dischargeCurrentLimitation);
}
break;
}
case 0x355: {
_stats->setSoC(static_cast<uint8_t>(this->readUnsignedInt16(rx_message.data)));
_stats->_stateOfHealth = this->readUnsignedInt16(rx_message.data + 2);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] soc: %d soh: %d\n",
_stats->getSoC(), _stats->_stateOfHealth);
}
break;
}
case 0x356: {
_stats->_voltage = this->scaleValue(this->readSignedInt16(rx_message.data), 0.01);
_stats->_current = this->scaleValue(this->readSignedInt16(rx_message.data + 2), 0.1);
_stats->_temperature = this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] voltage: %f current: %f temperature: %f\n",
_stats->_voltage, _stats->_current, _stats->_temperature);
}
break;
}
case 0x359: {
uint16_t alarmBits = rx_message.data[0];
_stats->_alarmOverCurrentDischarge = this->getBit(alarmBits, 7);
_stats->_alarmUnderTemperature = this->getBit(alarmBits, 4);
_stats->_alarmOverTemperature = this->getBit(alarmBits, 3);
_stats->_alarmUnderVoltage = this->getBit(alarmBits, 2);
_stats->_alarmOverVoltage= this->getBit(alarmBits, 1);
alarmBits = rx_message.data[1];
_stats->_alarmBmsInternal= this->getBit(alarmBits, 3);
_stats->_alarmOverCurrentCharge = this->getBit(alarmBits, 0);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] Alarms: %d %d %d %d %d %d %d\n",
_stats->_alarmOverCurrentDischarge,
_stats->_alarmUnderTemperature,
_stats->_alarmOverTemperature,
_stats->_alarmUnderVoltage,
_stats->_alarmOverVoltage,
_stats->_alarmBmsInternal,
_stats->_alarmOverCurrentCharge);
}
uint16_t warningBits = rx_message.data[2];
_stats->_warningHighCurrentDischarge = this->getBit(warningBits, 7);
_stats->_warningLowTemperature = this->getBit(warningBits, 4);
_stats->_warningHighTemperature = this->getBit(warningBits, 3);
_stats->_warningLowVoltage = this->getBit(warningBits, 2);
_stats->_warningHighVoltage = this->getBit(warningBits, 1);
warningBits = rx_message.data[3];
_stats->_warningBmsInternal= this->getBit(warningBits, 3);
_stats->_warningHighCurrentCharge = this->getBit(warningBits, 0);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] Warnings: %d %d %d %d %d %d %d\n",
_stats->_warningHighCurrentDischarge,
_stats->_warningLowTemperature,
_stats->_warningHighTemperature,
_stats->_warningLowVoltage,
_stats->_warningHighVoltage,
_stats->_warningBmsInternal,
_stats->_warningHighCurrentCharge);
}
break;
}
case 0x35E: {
String manufacturer(reinterpret_cast<char*>(rx_message.data),
rx_message.data_length_code);
if (manufacturer.isEmpty()) { break; }
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] Manufacturer: %s\n", manufacturer.c_str());
}
_stats->setManufacturer(std::move(manufacturer));
break;
}
case 0x35C: {
uint16_t chargeStatusBits = rx_message.data[0];
_stats->_chargeEnabled = this->getBit(chargeStatusBits, 7);
_stats->_dischargeEnabled = this->getBit(chargeStatusBits, 6);
_stats->_chargeImmediately = this->getBit(chargeStatusBits, 5);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] chargeStatusBits: %d %d %d\n",
_stats->_chargeEnabled,
_stats->_dischargeEnabled,
_stats->_chargeImmediately);
}
break;
}
default:
return; // do not update last update timestamp
break;
}
_stats->setLastUpdate(millis());
}
uint16_t PylontechCanReceiver::readUnsignedInt16(uint8_t *data)
{
uint8_t bytes[2];
bytes[0] = *data;
bytes[1] = *(data + 1);
return (bytes[1] << 8) + bytes[0];
}
int16_t PylontechCanReceiver::readSignedInt16(uint8_t *data)
{
return this->readUnsignedInt16(data);
}
float PylontechCanReceiver::scaleValue(int16_t value, float factor)
{
return value * factor;
}
bool PylontechCanReceiver::getBit(uint8_t value, uint8_t bit)
{
return (value & (1 << bit)) >> bit;
}
#ifdef PYLONTECH_DUMMY
void PylontechCanReceiver::dummyData()
{
static uint32_t lastUpdate = millis();
static uint8_t issues = 0;
if (millis() < (lastUpdate + 5 * 1000)) { return; }
lastUpdate = millis();
_stats->setLastUpdate(lastUpdate);
auto dummyFloat = [](int offset) -> float {
return offset + (static_cast<float>((lastUpdate + offset) % 10) / 10);
};
_stats->setManufacturer("Pylontech US3000C");
_stats->setSoC(42);
_stats->_chargeVoltage = dummyFloat(50);
_stats->_chargeCurrentLimitation = dummyFloat(33);
_stats->_dischargeCurrentLimitation = dummyFloat(12);
_stats->_stateOfHealth = 99;
_stats->_voltage = 48.67;
_stats->_current = dummyFloat(-1);
_stats->_temperature = dummyFloat(20);
_stats->_chargeEnabled = true;
_stats->_dischargeEnabled = true;
_stats->_chargeImmediately = false;
_stats->_warningHighCurrentDischarge = false;
_stats->_warningHighCurrentCharge = false;
_stats->_warningLowTemperature = false;
_stats->_warningHighTemperature = false;
_stats->_warningLowVoltage = false;
_stats->_warningHighVoltage = false;
_stats->_warningBmsInternal = false;
_stats->_alarmOverCurrentDischarge = false;
_stats->_alarmOverCurrentCharge = false;
_stats->_alarmUnderTemperature = false;
_stats->_alarmOverTemperature = false;
_stats->_alarmUnderVoltage = false;
_stats->_alarmOverVoltage = false;
_stats->_alarmBmsInternal = false;
if (issues == 1 || issues == 3) {
_stats->_warningHighCurrentDischarge = true;
_stats->_warningHighCurrentCharge = true;
_stats->_warningLowTemperature = true;
_stats->_warningHighTemperature = true;
_stats->_warningLowVoltage = true;
_stats->_warningHighVoltage = true;
_stats->_warningBmsInternal = true;
}
if (issues == 2 || issues == 3) {
_stats->_alarmOverCurrentDischarge = true;
_stats->_alarmOverCurrentCharge = true;
_stats->_alarmUnderTemperature = true;
_stats->_alarmOverTemperature = true;
_stats->_alarmUnderVoltage = true;
_stats->_alarmOverVoltage = true;
_stats->_alarmBmsInternal = true;
}
if (issues == 4) {
_stats->_warningHighCurrentCharge = true;
_stats->_warningLowTemperature = true;
_stats->_alarmUnderVoltage = true;
_stats->_dischargeEnabled = false;
_stats->_chargeImmediately = true;
}
issues = (issues + 1) % 5;
}
#endif