Skip to content

Commit

Permalink
feature: Add support for native pytes CAN protocol (#1196)
Browse files Browse the repository at this point in the history
* Allow scaleValue() for 32bit values

* Victron: Implement CAN message 0x360

This one-byte message is set to 0xff to request charging below a
 certain SoC threshold (10% in my tests).

* Pytes: Add support for native CAN protocol

The recently added PytesCanReceiver.cpp implements the Victron CAN protocol.

This change additionally adds support for the native Pytes CAN
protocol messages.

Features only supported in Pytes protocol:
- High-resolution state of charge / full and remaining mAh
- Charge cycle counter
- Balancing state

Features only supported in Victron protocol:
- FW version
- Serial number

Note that the only known way to select the native Pytes protocol is
via the serial console (Cisco-compatible cables work):

```
login config
setprt PYTES
logout
```

to return to Victron protocol use:
```
login config
setprt VICTRON
logout
```

to return to DIP-switch based protocol setting:
```
login config
setprt DIP
logout
```
  • Loading branch information
ranma authored Oct 10, 2024
1 parent c483347 commit c523f06
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 21 deletions.
2 changes: 1 addition & 1 deletion include/BatteryCanReceiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class BatteryCanReceiver : public BatteryProvider {
int16_t readSignedInt16(uint8_t *data);
uint32_t readUnsignedInt32(uint8_t *data);
int32_t readSignedInt24(uint8_t *data);
float scaleValue(int16_t value, float factor);
float scaleValue(int32_t value, float factor);
bool getBit(uint8_t value, uint8_t bit);

bool _verboseLogging = true;
Expand Down
10 changes: 8 additions & 2 deletions include/BatteryStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ class PytesBatteryStats : public BatteryStats {
public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
bool getImmediateChargingRequest() const { return _chargeImmediately; };
float getChargeCurrentLimitation() const { return _chargeCurrentLimit; };

private:
Expand All @@ -201,6 +202,8 @@ class PytesBatteryStats : public BatteryStats {
float _dischargeVoltageLimit;

uint16_t _stateOfHealth;
int _chargeCycles = -1;
int _balance = -1;

float _temperature;

Expand All @@ -220,8 +223,9 @@ class PytesBatteryStats : public BatteryStats {
uint8_t _moduleCountBlockingCharge;
uint8_t _moduleCountBlockingDischarge;

uint16_t _totalCapacity;
uint16_t _availableCapacity;
float _totalCapacity;
float _availableCapacity;
uint8_t _capacityPrecision = 0; // decimal places

float _chargedEnergy = -1;
float _dischargedEnergy = -1;
Expand All @@ -247,6 +251,8 @@ class PytesBatteryStats : public BatteryStats {
bool _warningHighTemperatureCharge;
bool _warningInternalFailure;
bool _warningCellImbalance;

bool _chargeImmediately;
};

class JkBmsBatteryStats : public BatteryStats {
Expand Down
2 changes: 1 addition & 1 deletion src/BatteryCanReceiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ uint32_t BatteryCanReceiver::readUnsignedInt32(uint8_t *data)
return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0];
}

float BatteryCanReceiver::scaleValue(int16_t value, float factor)
float BatteryCanReceiver::scaleValue(int32_t value, float factor)
{
return value * factor;
}
Expand Down
20 changes: 18 additions & 2 deletions src/BatteryStats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,13 @@ void PytesBatteryStats::getLiveViewData(JsonVariant& root) const
addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimit, "A", 1);
addLiveViewValue(root, "dischargeVoltageLimitation", _dischargeVoltageLimit, "V", 1);
addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0);
if (_chargeCycles != -1) {
addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0);
}
addLiveViewValue(root, "temperature", _temperature, "°C", 1);

addLiveViewValue(root, "capacity", _totalCapacity, "Ah", 0);
addLiveViewValue(root, "availableCapacity", _availableCapacity, "Ah", 0);
addLiveViewValue(root, "capacity", _totalCapacity, "Ah", _capacityPrecision);
addLiveViewValue(root, "availableCapacity", _availableCapacity, "Ah", _capacityPrecision);

if (_chargedEnergy != -1) {
addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "kWh", 1);
Expand All @@ -201,6 +204,11 @@ void PytesBatteryStats::getLiveViewData(JsonVariant& root) const
if (_dischargedEnergy != -1) {
addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "kWh", 1);
}
addLiveViewTextValue(root, "chargeImmediately", (_chargeImmediately?"yes":"no"));

if (_balance != -1) {
addLiveViewTextValue(root, "balancingActive", (_balance?"yes":"no"));
}

addLiveViewInSection(root, "cells", "cellMinVoltage", static_cast<float>(_cellMinMilliVolt)/1000, "V", 3);
addLiveViewInSection(root, "cells", "cellMaxVoltage", static_cast<float>(_cellMaxMilliVolt)/1000, "V", 3);
Expand Down Expand Up @@ -433,6 +441,12 @@ void PytesBatteryStats::mqttPublish() const
MqttSettings.publish("battery/settings/dischargeVoltageLimitation", String(_dischargeVoltageLimit));

MqttSettings.publish("battery/stateOfHealth", String(_stateOfHealth));
if (_chargeCycles != -1) {
MqttSettings.publish("battery/chargeCycles", String(_chargeCycles));
}
if (_balance != -1) {
MqttSettings.publish("battery/balancingActive", String(_balance ? 1 : 0));
}
MqttSettings.publish("battery/temperature", String(_temperature));

if (_chargedEnergy != -1) {
Expand Down Expand Up @@ -482,6 +496,8 @@ void PytesBatteryStats::mqttPublish() const
MqttSettings.publish("battery/warning/highTemperatureCharge", String(_warningHighTemperatureCharge));
MqttSettings.publish("battery/warning/bmsInternal", String(_warningInternalFailure));
MqttSettings.publish("battery/warning/cellImbalance", String(_warningCellImbalance));

MqttSettings.publish("battery/charging/chargeImmediately", String(_chargeImmediately));
}

void JkBmsBatteryStats::mqttPublish() const
Expand Down
4 changes: 4 additions & 0 deletions src/MqttHandleBatteryHass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ void MqttHandleBatteryHassClass::loop()
publishSensor("Current", "mdi:current-dc", "current", "current", "measurement", "A");
publishSensor("State of Health (SOH)", "mdi:heart-plus", "stateOfHealth", NULL, "measurement", "%");
publishSensor("Temperature", "mdi:thermometer", "temperature", "temperature", "measurement", "°C");
publishSensor("Charge Cycles", "mdi:counter", "chargeCycles");

publishSensor("Charged Energy", NULL, "chargedEnergy", "energy", "total_increasing", "kWh");
publishSensor("Discharged Energy", NULL, "dischargedEnergy", "energy", "total_increasing", "kWh");
Expand Down Expand Up @@ -182,6 +183,9 @@ void MqttHandleBatteryHassClass::loop()
publishBinarySensor("Warning Temperature high (charge)", "mdi:thermometer-high", "warning/highTemperatureCharge", "1", "0");
publishBinarySensor("Warning BMS internal", "mdi:alert-outline", "warning/bmsInternal", "1", "0");
publishBinarySensor("Warning Cell Imbalance", "mdi:alert-outline", "warning/cellImbalance", "1", "0");

publishBinarySensor("Balancing Active", "mdi:scale-balance", "balancingActive", "1", "0");
publishBinarySensor("Charge immediately", "mdi:alert", "charging/chargeImmediately", "1", "0");
break;

case 5: // SBS Unipower
Expand Down
Loading

0 comments on commit c523f06

Please sign in to comment.