Feature: parse additional Pylontech CAN protocol fields (#1213)
I noticed that these are missing while looking at dissassembly of the
Pytes implementation of the protocol. I also found Pylontech sample
CAN messages] which match the Pytes implementation [1]:
```
CAN ID – followed by 2 to 8 bytes of data:
0x351 – 14 02 74 0E 74 0E CC 01 – Battery voltage + current limits
^^^^^ discharge cutoff voltage 46.0V
0x355 – 1A 00 64 00 – State of Health (SOH) / State of Charge (SOC)
0x356 – 4e 13 02 03 04 05 – Voltage / Current / Temp
0x359 – 00 00 00 00 0A 50 4E – Protection & Alarm flags
^^^^^ always 0x50 0x59 in Pytes implementation
^^ module count (matches the blog article image)
0x35C – C0 00 – Battery charge request flags
^^ two possible additional flags (bit 3 and bit 4)
0x35E – 50 59 4C 4F 4E 20 20 20 – Manufacturer name (“PYLON “)
^^^^^^^^^^^^^^ Note: Pytes sends a 5-byte message "PYTES" instead
padding with spaces
```
The extra charge request flag is "bit4: SOC low" (Seems to be SoC < 10%
threshold for Pytes), I haven't bothered adding that as it provides
little value.
[1] https://www.setfirelabs.com/green-energy/pylontech-can-reading-can-replication
This commit is contained in:
parent
2265992836
commit
191cc8007d
@ -118,6 +118,7 @@ class PylontechBatteryStats : public BatteryStats {
|
|||||||
|
|
||||||
float _chargeVoltage;
|
float _chargeVoltage;
|
||||||
float _chargeCurrentLimitation;
|
float _chargeCurrentLimitation;
|
||||||
|
float _dischargeVoltageLimitation;
|
||||||
uint16_t _stateOfHealth;
|
uint16_t _stateOfHealth;
|
||||||
float _temperature;
|
float _temperature;
|
||||||
|
|
||||||
@ -140,6 +141,8 @@ class PylontechBatteryStats : public BatteryStats {
|
|||||||
bool _chargeEnabled;
|
bool _chargeEnabled;
|
||||||
bool _dischargeEnabled;
|
bool _dischargeEnabled;
|
||||||
bool _chargeImmediately;
|
bool _chargeImmediately;
|
||||||
|
|
||||||
|
uint8_t _moduleCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SBSBatteryStats : public BatteryStats {
|
class SBSBatteryStats : public BatteryStats {
|
||||||
|
|||||||
@ -124,8 +124,10 @@ void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const
|
|||||||
// values go into the "Status" card of the web application
|
// values go into the "Status" card of the web application
|
||||||
addLiveViewValue(root, "chargeVoltage", _chargeVoltage, "V", 1);
|
addLiveViewValue(root, "chargeVoltage", _chargeVoltage, "V", 1);
|
||||||
addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimitation, "A", 1);
|
addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimitation, "A", 1);
|
||||||
|
addLiveViewValue(root, "dischargeVoltageLimitation", _dischargeVoltageLimitation, "V", 1);
|
||||||
addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0);
|
addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0);
|
||||||
addLiveViewValue(root, "temperature", _temperature, "°C", 1);
|
addLiveViewValue(root, "temperature", _temperature, "°C", 1);
|
||||||
|
addLiveViewValue(root, "modules", _moduleCount, "", 0);
|
||||||
|
|
||||||
addLiveViewTextValue(root, "chargeEnabled", (_chargeEnabled?"yes":"no"));
|
addLiveViewTextValue(root, "chargeEnabled", (_chargeEnabled?"yes":"no"));
|
||||||
addLiveViewTextValue(root, "dischargeEnabled", (_dischargeEnabled?"yes":"no"));
|
addLiveViewTextValue(root, "dischargeEnabled", (_dischargeEnabled?"yes":"no"));
|
||||||
@ -380,6 +382,7 @@ void PylontechBatteryStats::mqttPublish() const
|
|||||||
|
|
||||||
MqttSettings.publish("battery/settings/chargeVoltage", String(_chargeVoltage));
|
MqttSettings.publish("battery/settings/chargeVoltage", String(_chargeVoltage));
|
||||||
MqttSettings.publish("battery/settings/chargeCurrentLimitation", String(_chargeCurrentLimitation));
|
MqttSettings.publish("battery/settings/chargeCurrentLimitation", String(_chargeCurrentLimitation));
|
||||||
|
MqttSettings.publish("battery/settings/dischargeVoltageLimitation", String(_dischargeVoltageLimitation));
|
||||||
MqttSettings.publish("battery/stateOfHealth", String(_stateOfHealth));
|
MqttSettings.publish("battery/stateOfHealth", String(_stateOfHealth));
|
||||||
MqttSettings.publish("battery/temperature", String(_temperature));
|
MqttSettings.publish("battery/temperature", String(_temperature));
|
||||||
MqttSettings.publish("battery/alarm/overCurrentDischarge", String(_alarmOverCurrentDischarge));
|
MqttSettings.publish("battery/alarm/overCurrentDischarge", String(_alarmOverCurrentDischarge));
|
||||||
@ -399,6 +402,7 @@ void PylontechBatteryStats::mqttPublish() const
|
|||||||
MqttSettings.publish("battery/charging/chargeEnabled", String(_chargeEnabled));
|
MqttSettings.publish("battery/charging/chargeEnabled", String(_chargeEnabled));
|
||||||
MqttSettings.publish("battery/charging/dischargeEnabled", String(_dischargeEnabled));
|
MqttSettings.publish("battery/charging/dischargeEnabled", String(_dischargeEnabled));
|
||||||
MqttSettings.publish("battery/charging/chargeImmediately", String(_chargeImmediately));
|
MqttSettings.publish("battery/charging/chargeImmediately", String(_chargeImmediately));
|
||||||
|
MqttSettings.publish("battery/modulesTotal", String(_moduleCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SBSBatteryStats::mqttPublish() const
|
void SBSBatteryStats::mqttPublish() const
|
||||||
|
|||||||
@ -55,7 +55,9 @@ void MqttHandleBatteryHassClass::loop()
|
|||||||
publishSensor("State of Health (SOH)", "mdi:heart-plus", "stateOfHealth", NULL, "measurement", "%");
|
publishSensor("State of Health (SOH)", "mdi:heart-plus", "stateOfHealth", NULL, "measurement", "%");
|
||||||
publishSensor("Charge voltage (BMS)", NULL, "settings/chargeVoltage", "voltage", "measurement", "V");
|
publishSensor("Charge voltage (BMS)", NULL, "settings/chargeVoltage", "voltage", "measurement", "V");
|
||||||
publishSensor("Charge current limit", NULL, "settings/chargeCurrentLimitation", "current", "measurement", "A");
|
publishSensor("Charge current limit", NULL, "settings/chargeCurrentLimitation", "current", "measurement", "A");
|
||||||
|
publishSensor("Discharge voltage limit", NULL, "settings/dischargeVoltageLimitation", "voltage", "measurement", "V");
|
||||||
publishSensor("Discharge current limit", NULL, "settings/dischargeCurrentLimitation", "current", "measurement", "A");
|
publishSensor("Discharge current limit", NULL, "settings/dischargeCurrentLimitation", "current", "measurement", "A");
|
||||||
|
publishSensor("Module Count", "mdi:counter", "modulesTotal");
|
||||||
|
|
||||||
publishBinarySensor("Alarm Discharge current", "mdi:alert", "alarm/overCurrentDischarge", "1", "0");
|
publishBinarySensor("Alarm Discharge current", "mdi:alert", "alarm/overCurrentDischarge", "1", "0");
|
||||||
publishBinarySensor("Warning Discharge current", "mdi:alert-outline", "warning/highCurrentDischarge", "1", "0");
|
publishBinarySensor("Warning Discharge current", "mdi:alert-outline", "warning/highCurrentDischarge", "1", "0");
|
||||||
|
|||||||
@ -18,10 +18,12 @@ void PylontechCanReceiver::onMessage(twai_message_t rx_message)
|
|||||||
_stats->_chargeVoltage = this->scaleValue(this->readUnsignedInt16(rx_message.data), 0.1);
|
_stats->_chargeVoltage = this->scaleValue(this->readUnsignedInt16(rx_message.data), 0.1);
|
||||||
_stats->_chargeCurrentLimitation = this->scaleValue(this->readSignedInt16(rx_message.data + 2), 0.1);
|
_stats->_chargeCurrentLimitation = this->scaleValue(this->readSignedInt16(rx_message.data + 2), 0.1);
|
||||||
_stats->setDischargeCurrentLimit(this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1), millis());
|
_stats->setDischargeCurrentLimit(this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1), millis());
|
||||||
|
_stats->_dischargeVoltageLimitation = this->scaleValue(this->readUnsignedInt16(rx_message.data + 6), 0.1);
|
||||||
|
|
||||||
if (_verboseLogging) {
|
if (_verboseLogging) {
|
||||||
MessageOutput.printf("[Pylontech] chargeVoltage: %f chargeCurrentLimitation: %f dischargeCurrentLimitation: %f\r\n",
|
MessageOutput.printf("[Pylontech] chargeVoltage: %f chargeCurrentLimitation: %f dischargeCurrentLimitation: %f dischargeVoltageLimitation: %f\r\n",
|
||||||
_stats->_chargeVoltage, _stats->_chargeCurrentLimitation, _stats->getDischargeCurrentLimit());
|
_stats->_chargeVoltage, _stats->_chargeCurrentLimitation, _stats->getDischargeCurrentLimit(),
|
||||||
|
_stats->_dischargeVoltageLimitation);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -93,6 +95,13 @@ void PylontechCanReceiver::onMessage(twai_message_t rx_message)
|
|||||||
_stats->_warningBmsInternal,
|
_stats->_warningBmsInternal,
|
||||||
_stats->_warningHighCurrentCharge);
|
_stats->_warningHighCurrentCharge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_stats->_moduleCount = rx_message.data[4];
|
||||||
|
if (_verboseLogging) {
|
||||||
|
MessageOutput.printf("[Pylontech] Modules: %d\r\n",
|
||||||
|
_stats->_moduleCount);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +164,7 @@ void PylontechCanReceiver::dummyData()
|
|||||||
_stats->_chargeVoltage = dummyFloat(50);
|
_stats->_chargeVoltage = dummyFloat(50);
|
||||||
_stats->_chargeCurrentLimitation = dummyFloat(33);
|
_stats->_chargeCurrentLimitation = dummyFloat(33);
|
||||||
_stats->setDischargeCurrentLimit(dummyFloat(12), millis());
|
_stats->setDischargeCurrentLimit(dummyFloat(12), millis());
|
||||||
|
_stats->_dischargeVoltageLimitation = dummyFloat(46);
|
||||||
_stats->_stateOfHealth = 99;
|
_stats->_stateOfHealth = 99;
|
||||||
_stats->setVoltage(48.67, millis());
|
_stats->setVoltage(48.67, millis());
|
||||||
_stats->setCurrent(dummyFloat(-1), 1/*precision*/, millis());
|
_stats->setCurrent(dummyFloat(-1), 1/*precision*/, millis());
|
||||||
@ -164,6 +174,8 @@ void PylontechCanReceiver::dummyData()
|
|||||||
_stats->_dischargeEnabled = true;
|
_stats->_dischargeEnabled = true;
|
||||||
_stats->_chargeImmediately = false;
|
_stats->_chargeImmediately = false;
|
||||||
|
|
||||||
|
_stats->_moduleCount = 1;
|
||||||
|
|
||||||
_stats->_warningHighCurrentDischarge = false;
|
_stats->_warningHighCurrentDischarge = false;
|
||||||
_stats->_warningHighCurrentCharge = false;
|
_stats->_warningHighCurrentCharge = false;
|
||||||
_stats->_warningLowTemperature = false;
|
_stats->_warningLowTemperature = false;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user