refactor PylontechCanBattery to be more generic (#1064)

This commit is contained in:
Andreas Böhm 2024-06-23 19:42:04 +02:00 committed by GitHub
parent cb0f8f20a8
commit 79214d750f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 198 additions and 153 deletions

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Battery.h"
#include <driver/twai.h>
#include <Arduino.h>
class BatteryCanReceiver : public BatteryProvider {
public:
bool init(bool verboseLogging, char const* providerName);
void deinit() final;
void loop() final;
virtual void onMessage(twai_message_t rx_message) = 0;
protected:
uint16_t readUnsignedInt16(uint8_t *data);
int16_t readSignedInt16(uint8_t *data);
float scaleValue(int16_t value, float factor);
bool getBit(uint8_t value, uint8_t bit);
bool _verboseLogging = true;
private:
char const* _providerName = "Battery CAN";
};

View File

@ -3,27 +3,20 @@
#include "Configuration.h"
#include "Battery.h"
#include <espMqttClient.h>
#include "BatteryCanReceiver.h"
#include <driver/twai.h>
#include <Arduino.h>
#include <memory>
class PylontechCanReceiver : public BatteryProvider {
class PylontechCanReceiver : public BatteryCanReceiver {
public:
bool init(bool verboseLogging) final;
void deinit() final;
void loop() final;
void onMessage(twai_message_t rx_message) final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
private:
uint16_t readUnsignedInt16(uint8_t *data);
int16_t readSignedInt16(uint8_t *data);
float scaleValue(int16_t value, float factor);
bool getBit(uint8_t value, uint8_t bit);
void dummyData();
bool _verboseLogging = true;
std::shared_ptr<PylontechBatteryStats> _stats =
std::make_shared<PylontechBatteryStats>();
};

157
src/BatteryCanReceiver.cpp Normal file
View File

@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "BatteryCanReceiver.h"
#include "MessageOutput.h"
#include "PinMapping.h"
#include <driver/twai.h>
bool BatteryCanReceiver::init(bool verboseLogging, char const* providerName)
{
_verboseLogging = verboseLogging;
_providerName = providerName;
MessageOutput.printf("[%s] Initialize interface...\r\n",
_providerName);
const PinMapping_t& pin = PinMapping.get();
MessageOutput.printf("[%s] Interface rx = %d, tx = %d\r\n",
_providerName, pin.battery_rx, pin.battery_tx);
if (pin.battery_rx < 0 || pin.battery_tx < 0) {
MessageOutput.printf("[%s] Invalid pin config\r\n",
_providerName);
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.printf("[%s] Twai driver installed\r\n",
_providerName);
break;
case ESP_ERR_INVALID_ARG:
MessageOutput.printf("[%s] Twai driver install - invalid arg\r\n",
_providerName);
return false;
break;
case ESP_ERR_NO_MEM:
MessageOutput.printf("[%s] Twai driver install - no memory\r\n",
_providerName);
return false;
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.printf("[%s] Twai driver install - invalid state\r\n",
_providerName);
return false;
break;
}
// Start TWAI driver
twaiLastResult = twai_start();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.printf("[%s] Twai driver started\r\n",
_providerName);
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.printf("[%s] Twai driver start - invalid state\r\n",
_providerName);
return false;
break;
}
return true;
}
void BatteryCanReceiver::deinit()
{
// Stop TWAI driver
esp_err_t twaiLastResult = twai_stop();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.printf("[%s] Twai driver stopped\r\n",
_providerName);
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.printf("[%s] Twai driver stop - invalid state\r\n",
_providerName);
break;
}
// Uninstall TWAI driver
twaiLastResult = twai_driver_uninstall();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.printf("[%s] Twai driver uninstalled\r\n",
_providerName);
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.printf("[%s] Twai driver uninstall - invalid state\r\n",
_providerName);
break;
}
}
void BatteryCanReceiver::loop()
{
// 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.printf("[%s] Twai driver get status - invalid arg\r\n",
_providerName);
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.printf("[%s] Twai driver get status - invalid state\r\n",
_providerName);
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.printf("[%s] Failed to receive message",
_providerName);
return;
}
onMessage(rx_message);
}
uint16_t BatteryCanReceiver::readUnsignedInt16(uint8_t *data)
{
uint8_t bytes[2];
bytes[0] = *data;
bytes[1] = *(data + 1);
return (bytes[1] << 8) + bytes[0];
}
int16_t BatteryCanReceiver::readSignedInt16(uint8_t *data)
{
return this->readUnsignedInt16(data);
}
float BatteryCanReceiver::scaleValue(int16_t value, float factor)
{
return value * factor;
}
bool BatteryCanReceiver::getBit(uint8_t value, uint8_t bit)
{
return (value & (1 << bit)) >> bit;
}

View File

@ -1,127 +1,18 @@
// 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("[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("[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("[Pylontech] Twai driver installed");
break;
case ESP_ERR_INVALID_ARG:
MessageOutput.println("[Pylontech] Twai driver install - invalid arg");
return false;
break;
case ESP_ERR_NO_MEM:
MessageOutput.println("[Pylontech] Twai driver install - no memory");
return false;
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println("[Pylontech] Twai driver install - invalid state");
return false;
break;
}
// Start TWAI driver
twaiLastResult = twai_start();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.println("[Pylontech] Twai driver started");
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println("[Pylontech] Twai driver start - invalid state");
return false;
break;
}
return true;
return BatteryCanReceiver::init(verboseLogging, "Pylontech");
}
void PylontechCanReceiver::deinit()
void PylontechCanReceiver::onMessage(twai_message_t rx_message)
{
// Stop TWAI driver
esp_err_t twaiLastResult = twai_stop();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.println("[Pylontech] Twai driver stopped");
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println("[Pylontech] Twai driver stop - invalid state");
break;
}
// Uninstall TWAI driver
twaiLastResult = twai_driver_uninstall();
switch (twaiLastResult) {
case ESP_OK:
MessageOutput.println("[Pylontech] Twai driver uninstalled");
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println("[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("[Pylontech] Twai driver get status - invalid arg");
break;
case ESP_ERR_INVALID_STATE:
MessageOutput.println("[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("[Pylontech] Failed to receive message");
return;
}
switch (rx_message.identifier) {
case 0x351: {
_stats->_chargeVoltage = this->scaleValue(this->readUnsignedInt16(rx_message.data), 0.1);
@ -129,7 +20,7 @@ void PylontechCanReceiver::loop()
_stats->_dischargeCurrentLimitation = this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] chargeVoltage: %f chargeCurrentLimitation: %f dischargeCurrentLimitation: %f\n",
MessageOutput.printf("[Pylontech] chargeVoltage: %f chargeCurrentLimitation: %f dischargeCurrentLimitation: %f\r\n",
_stats->_chargeVoltage, _stats->_chargeCurrentLimitation, _stats->_dischargeCurrentLimitation);
}
break;
@ -140,7 +31,7 @@ void PylontechCanReceiver::loop()
_stats->_stateOfHealth = this->readUnsignedInt16(rx_message.data + 2);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] soc: %d soh: %d\n",
MessageOutput.printf("[Pylontech] soc: %d soh: %d\r\n",
_stats->getSoC(), _stats->_stateOfHealth);
}
break;
@ -152,7 +43,7 @@ void PylontechCanReceiver::loop()
_stats->_temperature = this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] voltage: %f current: %f temperature: %f\n",
MessageOutput.printf("[Pylontech] voltage: %f current: %f temperature: %f\r\n",
_stats->getVoltage(), _stats->_current, _stats->_temperature);
}
break;
@ -171,7 +62,7 @@ void PylontechCanReceiver::loop()
_stats->_alarmOverCurrentCharge = this->getBit(alarmBits, 0);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] Alarms: %d %d %d %d %d %d %d\n",
MessageOutput.printf("[Pylontech] Alarms: %d %d %d %d %d %d %d\r\n",
_stats->_alarmOverCurrentDischarge,
_stats->_alarmUnderTemperature,
_stats->_alarmOverTemperature,
@ -193,7 +84,7 @@ void PylontechCanReceiver::loop()
_stats->_warningHighCurrentCharge = this->getBit(warningBits, 0);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] Warnings: %d %d %d %d %d %d %d\n",
MessageOutput.printf("[Pylontech] Warnings: %d %d %d %d %d %d %d\r\n",
_stats->_warningHighCurrentDischarge,
_stats->_warningLowTemperature,
_stats->_warningHighTemperature,
@ -212,7 +103,7 @@ void PylontechCanReceiver::loop()
if (manufacturer.isEmpty()) { break; }
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] Manufacturer: %s\n", manufacturer.c_str());
MessageOutput.printf("[Pylontech] Manufacturer: %s\r\n", manufacturer.c_str());
}
_stats->setManufacturer(std::move(manufacturer));
@ -226,7 +117,7 @@ void PylontechCanReceiver::loop()
_stats->_chargeImmediately = this->getBit(chargeStatusBits, 5);
if (_verboseLogging) {
MessageOutput.printf("[Pylontech] chargeStatusBits: %d %d %d\n",
MessageOutput.printf("[Pylontech] chargeStatusBits: %d %d %d\r\n",
_stats->_chargeEnabled,
_stats->_dischargeEnabled,
_stats->_chargeImmediately);
@ -243,29 +134,7 @@ void PylontechCanReceiver::loop()
_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;
}
// Currently not called because there is no nice way to integrate it right now
#ifdef PYLONTECH_DUMMY
void PylontechCanReceiver::dummyData()
{