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 <mutex>
#include <optional>
#include <stdint.h>
#include <Arduino.h>
#include <HTTPClient.h>
@ -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<float> activePowerTotal = std::nullopt;
std::optional<float> activePowerL1 = std::nullopt;
std::optional<float> activePowerL2 = std::nullopt;
std::optional<float> activePowerL3 = std::nullopt;
std::optional<float> voltageL1 = std::nullopt;
std::optional<float> voltageL2 = std::nullopt;
std::optional<float> voltageL3 = std::nullopt;
std::optional<float> currentL1 = std::nullopt;
std::optional<float> currentL2 = std::nullopt;
std::optional<float> currentL3 = std::nullopt;
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];
void (*decoder)(float&);
float* target;
std::optional<float>* target;
char const* name;
} OBISHandler;
};
const std::list<OBISHandler> 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"}
};
};

View File

@ -5,23 +5,29 @@
float PowerMeterSml::getPowerTotal() const
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;