Skip to content

Commit

Permalink
Feature: SML power meter: handle checksum error
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
schlimmchen committed Jun 27, 2024
1 parent 5be1615 commit ea45497
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 45 deletions.
62 changes: 34 additions & 28 deletions include/PowerMeterSml.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <list>
#include <mutex>
#include <optional>
#include <stdint.h>
#include <Arduino.h>
#include <HTTPClient.h>
Expand All @@ -25,38 +26,43 @@ class PowerMeterSml : public PowerMeterProvider {
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;

typedef struct {
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;
};

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"}
};
};
45 changes: 28 additions & 17 deletions src/PowerMeterSml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
Expand Down

0 comments on commit ea45497

Please sign in to comment.