Feature: SML power meter: handle checksum error

cache the values decoded in the SML datagram and only copy them to the
local stash of values if the checksum of the SML datagram matched. also
makes sure that values from incomplete SML datagrams are not used.

moreover, we now only publish values to the MQTT broker that we actually
decoded (successfully) from an SML datagram (we previously published 0.0
as values to topics we never decoded a value for).
This commit is contained in:
Bernhard Kirchen 2024-06-17 20:15:37 +02:00
parent 5be1615f4e
commit ea454972b9
2 changed files with 61 additions and 44 deletions

View File

@ -3,6 +3,7 @@
#include <list> #include <list>
#include <mutex> #include <mutex>
#include <optional>
#include <stdint.h> #include <stdint.h>
#include <Arduino.h> #include <Arduino.h>
#include <HTTPClient.h> #include <HTTPClient.h>
@ -25,38 +26,43 @@ private:
std::string _user; std::string _user;
mutable std::mutex _mutex; mutable std::mutex _mutex;
float _activePowerTotal = 0.0; using values_t = struct {
float _activePowerL1 = 0.0; std::optional<float> activePowerTotal = std::nullopt;
float _activePowerL2 = 0.0; std::optional<float> activePowerL1 = std::nullopt;
float _activePowerL3 = 0.0; std::optional<float> activePowerL2 = std::nullopt;
float _voltageL1 = 0.0; std::optional<float> activePowerL3 = std::nullopt;
float _voltageL2 = 0.0; std::optional<float> voltageL1 = std::nullopt;
float _voltageL3 = 0.0; std::optional<float> voltageL2 = std::nullopt;
float _currentL1 = 0.0; std::optional<float> voltageL3 = std::nullopt;
float _currentL2 = 0.0; std::optional<float> currentL1 = std::nullopt;
float _currentL3 = 0.0; std::optional<float> currentL2 = std::nullopt;
float _energyImport = 0.0; std::optional<float> currentL3 = std::nullopt;
float _energyExport = 0.0; std::optional<float> energyImport = std::nullopt;
std::optional<float> energyExport = std::nullopt;
};
typedef struct { values_t _values;
values_t _cache;
using OBISHandler = struct {
uint8_t const OBIS[6]; uint8_t const OBIS[6];
void (*decoder)(float&); void (*decoder)(float&);
float* target; std::optional<float>* target;
char const* name; char const* name;
} OBISHandler; };
const std::list<OBISHandler> smlHandlerList{ const std::list<OBISHandler> smlHandlerList{
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerTotal, "active power total"}, {{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerTotal, "active power total"},
{{0x01, 0x00, 0x24, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerL1, "active power L1"}, {{0x01, 0x00, 0x24, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL1, "active power L1"},
{{0x01, 0x00, 0x38, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerL2, "active power L2"}, {{0x01, 0x00, 0x38, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL2, "active power L2"},
{{0x01, 0x00, 0x4c, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerL3, "active power L3"}, {{0x01, 0x00, 0x4c, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL3, "active power L3"},
{{0x01, 0x00, 0x20, 0x07, 0x00, 0xff}, &smlOBISVolt, &_voltageL1, "voltage L1"}, {{0x01, 0x00, 0x20, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL1, "voltage L1"},
{{0x01, 0x00, 0x34, 0x07, 0x00, 0xff}, &smlOBISVolt, &_voltageL2, "voltage L2"}, {{0x01, 0x00, 0x34, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL2, "voltage L2"},
{{0x01, 0x00, 0x48, 0x07, 0x00, 0xff}, &smlOBISVolt, &_voltageL3, "voltage L3"}, {{0x01, 0x00, 0x48, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL3, "voltage L3"},
{{0x01, 0x00, 0x1f, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_currentL1, "current L1"}, {{0x01, 0x00, 0x1f, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL1, "current L1"},
{{0x01, 0x00, 0x33, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_currentL2, "current L2"}, {{0x01, 0x00, 0x33, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL2, "current L2"},
{{0x01, 0x00, 0x47, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_currentL3, "current L3"}, {{0x01, 0x00, 0x47, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL3, "current L3"},
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_energyImport, "energy import"}, {{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_cache.energyImport, "energy import"},
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_energyExport, "energy export"} {{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_cache.energyExport, "energy export"}
}; };
}; };

View File

@ -5,23 +5,29 @@
float PowerMeterSml::getPowerTotal() const float PowerMeterSml::getPowerTotal() const
{ {
std::lock_guard<std::mutex> l(_mutex); std::lock_guard<std::mutex> l(_mutex);
return _activePowerTotal; if (_values.activePowerTotal.has_value()) { return *_values.activePowerTotal; }
return 0;
} }
void PowerMeterSml::doMqttPublish() const void PowerMeterSml::doMqttPublish() const
{ {
#define PUB(t, m) \
if (_values.m.has_value()) { mqttPublish(t, *_values.m); }
std::lock_guard<std::mutex> l(_mutex); std::lock_guard<std::mutex> l(_mutex);
mqttPublish("power1", _activePowerL1); PUB("power1", activePowerL1);
mqttPublish("power2", _activePowerL2); PUB("power2", activePowerL2);
mqttPublish("power3", _activePowerL3); PUB("power3", activePowerL3);
mqttPublish("voltage1", _voltageL1); PUB("voltage1", voltageL1);
mqttPublish("voltage2", _voltageL2); PUB("voltage2", voltageL2);
mqttPublish("voltage3", _voltageL3); PUB("voltage3", voltageL3);
mqttPublish("current1", _currentL1); PUB("current1", currentL1);
mqttPublish("current2", _currentL2); PUB("current2", currentL2);
mqttPublish("current3", _currentL3); PUB("current3", currentL3);
mqttPublish("import", _energyImport); PUB("import", energyImport);
mqttPublish("export", _energyExport); PUB("export", energyExport);
#undef PUB
} }
void PowerMeterSml::processSmlByte(uint8_t byte) void PowerMeterSml::processSmlByte(uint8_t byte)
@ -31,22 +37,27 @@ void PowerMeterSml::processSmlByte(uint8_t byte)
for (auto& handler: smlHandlerList) { for (auto& handler: smlHandlerList) {
if (!smlOBISCheck(handler.OBIS)) { continue; } if (!smlOBISCheck(handler.OBIS)) { continue; }
gotUpdate(); float helper = 0.0;
handler.decoder(helper);
std::lock_guard<std::mutex> l(_mutex);
handler.decoder(*handler.target);
if (_verboseLogging) { if (_verboseLogging) {
MessageOutput.printf("[%s] decoded %s to %.2f\r\n", MessageOutput.printf("[%s] decoded %s to %.2f\r\n",
_user.c_str(), handler.name, *handler.target); _user.c_str(), handler.name, helper);
} }
std::lock_guard<std::mutex> l(_mutex);
*handler.target = helper;
} }
break; break;
case SML_FINAL: case SML_FINAL:
gotUpdate();
_values = _cache;
_cache = { std::nullopt };
MessageOutput.printf("[%s] TotalPower: %5.2f\r\n", MessageOutput.printf("[%s] TotalPower: %5.2f\r\n",
_user.c_str(), getPowerTotal()); _user.c_str(), getPowerTotal());
break; break;
case SML_CHECKSUM_ERROR: case SML_CHECKSUM_ERROR:
_cache = { std::nullopt };
MessageOutput.printf("[%s] checksum verification failed\r\n", MessageOutput.printf("[%s] checksum verification failed\r\n",
_user.c_str()); _user.c_str());
break; break;