From ea454972b9e9ecf08f58abcea672af45d73e6bf6 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Mon, 17 Jun 2024 20:15:37 +0200 Subject: [PATCH] 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). --- include/PowerMeterSml.h | 60 ++++++++++++++++++++++------------------- src/PowerMeterSml.cpp | 45 +++++++++++++++++++------------ 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/include/PowerMeterSml.h b/include/PowerMeterSml.h index 2e180c57..a0d61420 100644 --- a/include/PowerMeterSml.h +++ b/include/PowerMeterSml.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -25,38 +26,43 @@ private: std::string _user; mutable std::mutex _mutex; - float _activePowerTotal = 0.0; - float _activePowerL1 = 0.0; - float _activePowerL2 = 0.0; - float _activePowerL3 = 0.0; - float _voltageL1 = 0.0; - float _voltageL2 = 0.0; - float _voltageL3 = 0.0; - float _currentL1 = 0.0; - float _currentL2 = 0.0; - float _currentL3 = 0.0; - float _energyImport = 0.0; - float _energyExport = 0.0; + using values_t = struct { + std::optional activePowerTotal = std::nullopt; + std::optional activePowerL1 = std::nullopt; + std::optional activePowerL2 = std::nullopt; + std::optional activePowerL3 = std::nullopt; + std::optional voltageL1 = std::nullopt; + std::optional voltageL2 = std::nullopt; + std::optional voltageL3 = std::nullopt; + std::optional currentL1 = std::nullopt; + std::optional currentL2 = std::nullopt; + std::optional currentL3 = std::nullopt; + std::optional energyImport = std::nullopt; + std::optional energyExport = std::nullopt; + }; - typedef struct { + values_t _values; + values_t _cache; + + using OBISHandler = struct { uint8_t const OBIS[6]; void (*decoder)(float&); - float* target; + std::optional* target; char const* name; - } OBISHandler; + }; const std::list smlHandlerList{ - {{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerTotal, "active power total"}, - {{0x01, 0x00, 0x24, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerL1, "active power L1"}, - {{0x01, 0x00, 0x38, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerL2, "active power L2"}, - {{0x01, 0x00, 0x4c, 0x07, 0x00, 0xff}, &smlOBISW, &_activePowerL3, "active power L3"}, - {{0x01, 0x00, 0x20, 0x07, 0x00, 0xff}, &smlOBISVolt, &_voltageL1, "voltage L1"}, - {{0x01, 0x00, 0x34, 0x07, 0x00, 0xff}, &smlOBISVolt, &_voltageL2, "voltage L2"}, - {{0x01, 0x00, 0x48, 0x07, 0x00, 0xff}, &smlOBISVolt, &_voltageL3, "voltage L3"}, - {{0x01, 0x00, 0x1f, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_currentL1, "current L1"}, - {{0x01, 0x00, 0x33, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_currentL2, "current L2"}, - {{0x01, 0x00, 0x47, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_currentL3, "current L3"}, - {{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_energyImport, "energy import"}, - {{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_energyExport, "energy export"} + {{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerTotal, "active power total"}, + {{0x01, 0x00, 0x24, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL1, "active power L1"}, + {{0x01, 0x00, 0x38, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL2, "active power L2"}, + {{0x01, 0x00, 0x4c, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL3, "active power L3"}, + {{0x01, 0x00, 0x20, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL1, "voltage L1"}, + {{0x01, 0x00, 0x34, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL2, "voltage L2"}, + {{0x01, 0x00, 0x48, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL3, "voltage L3"}, + {{0x01, 0x00, 0x1f, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL1, "current L1"}, + {{0x01, 0x00, 0x33, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL2, "current L2"}, + {{0x01, 0x00, 0x47, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL3, "current L3"}, + {{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_cache.energyImport, "energy import"}, + {{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_cache.energyExport, "energy export"} }; }; diff --git a/src/PowerMeterSml.cpp b/src/PowerMeterSml.cpp index 105d20f9..021b48cd 100644 --- a/src/PowerMeterSml.cpp +++ b/src/PowerMeterSml.cpp @@ -5,23 +5,29 @@ float PowerMeterSml::getPowerTotal() const { std::lock_guard l(_mutex); - return _activePowerTotal; + if (_values.activePowerTotal.has_value()) { return *_values.activePowerTotal; } + return 0; } void PowerMeterSml::doMqttPublish() const { +#define PUB(t, m) \ + if (_values.m.has_value()) { mqttPublish(t, *_values.m); } + std::lock_guard l(_mutex); - mqttPublish("power1", _activePowerL1); - mqttPublish("power2", _activePowerL2); - mqttPublish("power3", _activePowerL3); - mqttPublish("voltage1", _voltageL1); - mqttPublish("voltage2", _voltageL2); - mqttPublish("voltage3", _voltageL3); - mqttPublish("current1", _currentL1); - mqttPublish("current2", _currentL2); - mqttPublish("current3", _currentL3); - mqttPublish("import", _energyImport); - mqttPublish("export", _energyExport); + PUB("power1", activePowerL1); + PUB("power2", activePowerL2); + PUB("power3", activePowerL3); + PUB("voltage1", voltageL1); + PUB("voltage2", voltageL2); + PUB("voltage3", voltageL3); + PUB("current1", currentL1); + PUB("current2", currentL2); + PUB("current3", currentL3); + PUB("import", energyImport); + PUB("export", energyExport); + +#undef PUB } void PowerMeterSml::processSmlByte(uint8_t byte) @@ -31,22 +37,27 @@ void PowerMeterSml::processSmlByte(uint8_t byte) for (auto& handler: smlHandlerList) { if (!smlOBISCheck(handler.OBIS)) { continue; } - gotUpdate(); - - std::lock_guard l(_mutex); - handler.decoder(*handler.target); + float helper = 0.0; + handler.decoder(helper); if (_verboseLogging) { 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 l(_mutex); + *handler.target = helper; } break; case SML_FINAL: + gotUpdate(); + _values = _cache; + _cache = { std::nullopt }; MessageOutput.printf("[%s] TotalPower: %5.2f\r\n", _user.c_str(), getPowerTotal()); break; case SML_CHECKSUM_ERROR: + _cache = { std::nullopt }; MessageOutput.printf("[%s] checksum verification failed\r\n", _user.c_str()); break;