From 49964b1f9ba4ab5ac71c307967ee4cc4daaa9fc6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20Bo=CC=88hm?= <andreas@boehm.cx>
Date: Thu, 23 Jan 2025 16:44:41 +0100
Subject: [PATCH] improvement: restructure powermeter providers

---
 .../{PowerMeter.h => powermeter/Controller.h} | 12 ++-
 .../Provider.h}                               | 10 +-
 .../json/http/Provider.h}                     | 16 +--
 .../mqtt/Provider.h}                          | 14 ++-
 .../sdm/serial/Provider.h}                    | 16 +--
 .../sml/Provider.h}                           | 14 ++-
 .../sml/http/Provider.h}                      | 18 ++--
 .../sml/serial/Provider.h}                    | 14 ++-
 .../udp/smahm/Provider.h}                     | 10 +-
 src/Display_Graphic.cpp                       |  2 +-
 src/PowerLimiter.cpp                          |  2 +-
 src/PowerMeter.cpp                            | 94 ------------------
 src/WebApi_mqtt.cpp                           |  2 +-
 src/WebApi_powermeter.cpp                     | 16 +--
 src/WebApi_ws_live.cpp                        |  2 +-
 src/gridcharger/huawei/Controller.cpp         |  2 +-
 src/main.cpp                                  |  2 +-
 src/powermeter/Controller.cpp                 | 98 +++++++++++++++++++
 .../Provider.cpp}                             | 14 ++-
 .../json/http/Provider.cpp}                   | 42 ++++----
 .../mqtt/Provider.cpp}                        | 28 +++---
 .../sdm/serial/Provider.cpp}                  | 50 +++++-----
 .../sml/Provider.cpp}                         | 16 +--
 .../sml/http/Provider.cpp}                    | 34 ++++---
 .../sml/serial/Provider.cpp}                  | 30 +++---
 .../udp/smahm/Provider.cpp}                   | 26 ++---
 26 files changed, 328 insertions(+), 256 deletions(-)
 rename include/{PowerMeter.h => powermeter/Controller.h} (65%)
 rename include/{PowerMeterProvider.h => powermeter/Provider.h} (91%)
 rename include/{PowerMeterHttpJson.h => powermeter/json/http/Provider.h} (79%)
 rename include/{PowerMeterMqtt.h => powermeter/mqtt/Provider.h} (78%)
 rename include/{PowerMeterSerialSdm.h => powermeter/sdm/serial/Provider.h} (81%)
 rename include/{PowerMeterSml.h => powermeter/sml/Provider.h} (91%)
 rename include/{PowerMeterHttpSml.h => powermeter/sml/http/Provider.h} (68%)
 rename include/{PowerMeterSerialSml.h => powermeter/sml/serial/Provider.h} (83%)
 rename include/{PowerMeterUdpSmaHomeManager.h => powermeter/udp/smahm/Provider.h} (79%)
 delete mode 100644 src/PowerMeter.cpp
 create mode 100644 src/powermeter/Controller.cpp
 rename src/{PowerMeterProvider.cpp => powermeter/Provider.cpp} (68%)
 rename src/{PowerMeterHttpJson.cpp => powermeter/json/http/Provider.cpp} (81%)
 rename src/{PowerMeterMqtt.cpp => powermeter/mqtt/Provider.cpp} (79%)
 rename src/{PowerMeterSerialSdm.cpp => powermeter/sdm/serial/Provider.cpp} (80%)
 rename src/{PowerMeterSml.cpp => powermeter/sml/Provider.cpp} (86%)
 rename src/{PowerMeterHttpSml.cpp => powermeter/sml/http/Provider.cpp} (74%)
 rename src/{PowerMeterSerialSml.cpp => powermeter/sml/serial/Provider.cpp} (81%)
 rename src/{PowerMeterUdpSmaHomeManager.cpp => powermeter/udp/smahm/Provider.cpp} (86%)

diff --git a/include/PowerMeter.h b/include/powermeter/Controller.h
similarity index 65%
rename from include/PowerMeter.h
rename to include/powermeter/Controller.h
index 44c99d062..b25b7df95 100644
--- a/include/PowerMeter.h
+++ b/include/powermeter/Controller.h
@@ -1,12 +1,14 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #pragma once
 
-#include "PowerMeterProvider.h"
+#include <powermeter/Provider.h>
 #include <TaskSchedulerDeclarations.h>
 #include <memory>
 #include <mutex>
 
-class PowerMeterClass {
+namespace PowerMeters {
+
+class Controller {
 public:
     void init(Scheduler& scheduler);
 
@@ -21,7 +23,9 @@ class PowerMeterClass {
 
     Task _loopTask;
     mutable std::mutex _mutex;
-    std::unique_ptr<PowerMeterProvider> _upProvider = nullptr;
+    std::unique_ptr<Provider> _upProvider = nullptr;
 };
 
-extern PowerMeterClass PowerMeter;
+} // namespace PowerMeters
+
+extern PowerMeters::Controller PowerMeter;
diff --git a/include/PowerMeterProvider.h b/include/powermeter/Provider.h
similarity index 91%
rename from include/PowerMeterProvider.h
rename to include/powermeter/Provider.h
index 0ca7bcdf5..ca8c4995b 100644
--- a/include/PowerMeterProvider.h
+++ b/include/powermeter/Provider.h
@@ -4,9 +4,11 @@
 #include <atomic>
 #include "Configuration.h"
 
-class PowerMeterProvider {
+namespace PowerMeters {
+
+class Provider {
 public:
-    virtual ~PowerMeterProvider() { }
+    virtual ~Provider() { }
 
     enum class Type : unsigned {
         MQTT = 0,
@@ -29,7 +31,7 @@ class PowerMeterProvider {
     void mqttLoop() const;
 
 protected:
-    PowerMeterProvider() {
+    Provider() {
         auto const& config = Configuration.get();
         _verboseLogging = config.PowerMeter.VerboseLogging;
     }
@@ -49,3 +51,5 @@ class PowerMeterProvider {
 
     mutable uint32_t _lastMqttPublish = 0;
 };
+
+} // namespace PowerMeters
diff --git a/include/PowerMeterHttpJson.h b/include/powermeter/json/http/Provider.h
similarity index 79%
rename from include/PowerMeterHttpJson.h
rename to include/powermeter/json/http/Provider.h
index 2a9e1dd2d..c29fbb518 100644
--- a/include/PowerMeterHttpJson.h
+++ b/include/powermeter/json/http/Provider.h
@@ -8,19 +8,21 @@
 #include <condition_variable>
 #include <mutex>
 #include <stdint.h>
-#include "HttpGetter.h"
-#include "Configuration.h"
-#include "PowerMeterProvider.h"
+#include <Configuration.h>
+#include <HttpGetter.h>
+#include <powermeter/Provider.h>
 
 using Auth_t = HttpRequestConfig::Auth;
 using Unit_t = PowerMeterHttpJsonValue::Unit;
 
-class PowerMeterHttpJson : public PowerMeterProvider {
+namespace PowerMeters::Json::Http {
+
+class Provider : public ::PowerMeters::Provider {
 public:
-    explicit PowerMeterHttpJson(PowerMeterHttpJsonConfig const& cfg)
+    explicit Provider(PowerMeterHttpJsonConfig const& cfg)
         : _cfg(cfg) { }
 
-    ~PowerMeterHttpJson();
+    ~Provider();
 
     bool init() final;
     void loop() final;
@@ -51,3 +53,5 @@ class PowerMeterHttpJson : public PowerMeterProvider {
     mutable std::mutex _pollingMutex;
     std::condition_variable _cv;
 };
+
+} // namespace PowerMeters::Json::Http
diff --git a/include/PowerMeterMqtt.h b/include/powermeter/mqtt/Provider.h
similarity index 78%
rename from include/PowerMeterMqtt.h
rename to include/powermeter/mqtt/Provider.h
index b12e919bc..8a99e91ea 100644
--- a/include/PowerMeterMqtt.h
+++ b/include/powermeter/mqtt/Provider.h
@@ -1,19 +1,21 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #pragma once
 
-#include "Configuration.h"
-#include "PowerMeterProvider.h"
+#include <Configuration.h>
+#include <powermeter/Provider.h>
 #include <espMqttClient.h>
 #include <vector>
 #include <mutex>
 #include <array>
 
-class PowerMeterMqtt : public PowerMeterProvider {
+namespace PowerMeters::Mqtt {
+
+class Provider : public ::PowerMeters::Provider {
 public:
-    explicit PowerMeterMqtt(PowerMeterMqttConfig const& cfg)
+    explicit Provider(PowerMeterMqttConfig const& cfg)
         : _cfg(cfg) { }
 
-    ~PowerMeterMqtt();
+    ~Provider();
 
     bool init() final;
     void loop() final { }
@@ -37,3 +39,5 @@ class PowerMeterMqtt : public PowerMeterProvider {
 
     mutable std::mutex _mutex;
 };
+
+} // namespace PowerMeters::Mqtt
diff --git a/include/PowerMeterSerialSdm.h b/include/powermeter/sdm/serial/Provider.h
similarity index 81%
rename from include/PowerMeterSerialSdm.h
rename to include/powermeter/sdm/serial/Provider.h
index d676fcbbd..0019141ff 100644
--- a/include/PowerMeterSerialSdm.h
+++ b/include/powermeter/sdm/serial/Provider.h
@@ -5,22 +5,24 @@
 #include <mutex>
 #include <condition_variable>
 #include <SoftwareSerial.h>
-#include "Configuration.h"
-#include "PowerMeterProvider.h"
-#include "SDM.h"
+#include <Configuration.h>
+#include <powermeter/Provider.h>
+#include <SDM.h>
 
-class PowerMeterSerialSdm : public PowerMeterProvider {
+namespace PowerMeters::Sdm::Serial {
+
+class Provider : public ::PowerMeters::Provider {
 public:
     enum class Phases {
         One,
         Three
     };
 
-    PowerMeterSerialSdm(Phases phases, PowerMeterSerialSdmConfig const& cfg)
+    Provider(Phases phases, PowerMeterSerialSdmConfig const& cfg)
         : _phases(phases)
         , _cfg(cfg) { }
 
-    ~PowerMeterSerialSdm();
+    ~Provider();
 
     bool init() final;
     void loop() final;
@@ -58,3 +60,5 @@ class PowerMeterSerialSdm : public PowerMeterProvider {
     mutable std::mutex _pollingMutex;
     std::condition_variable _cv;
 };
+
+} // namespace PowerMeters::Sdm::Serial
diff --git a/include/PowerMeterSml.h b/include/powermeter/sml/Provider.h
similarity index 91%
rename from include/PowerMeterSml.h
rename to include/powermeter/sml/Provider.h
index 79e37ab9a..59a192ee3 100644
--- a/include/PowerMeterSml.h
+++ b/include/powermeter/sml/Provider.h
@@ -7,17 +7,19 @@
 #include <stdint.h>
 #include <Arduino.h>
 #include <HTTPClient.h>
-#include "Configuration.h"
-#include "PowerMeterProvider.h"
-#include "sml.h"
+#include <Configuration.h>
+#include <powermeter/Provider.h>
+#include <sml.h>
 
-class PowerMeterSml : public PowerMeterProvider {
+namespace PowerMeters::Sml {
+
+class Provider : public ::PowerMeters::Provider {
 public:
     float getPowerTotal() const final;
     void doMqttPublish() const final;
 
 protected:
-    explicit PowerMeterSml(char const* user)
+    explicit Provider(char const* user)
         : _user(user) { }
 
     void reset();
@@ -67,3 +69,5 @@ class PowerMeterSml : public PowerMeterProvider {
         {{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_cache.energyExport, "energy export"}
     };
 };
+
+} // namespace PowerMeters::Sml
diff --git a/include/PowerMeterHttpSml.h b/include/powermeter/sml/http/Provider.h
similarity index 68%
rename from include/PowerMeterHttpSml.h
rename to include/powermeter/sml/http/Provider.h
index 9c49b639c..08cd2f069 100644
--- a/include/PowerMeterHttpSml.h
+++ b/include/powermeter/sml/http/Provider.h
@@ -7,17 +7,19 @@
 #include <mutex>
 #include <stdint.h>
 #include <Arduino.h>
-#include "HttpGetter.h"
-#include "Configuration.h"
-#include "PowerMeterSml.h"
+#include <HttpGetter.h>
+#include <Configuration.h>
+#include <powermeter/sml/Provider.h>
 
-class PowerMeterHttpSml : public PowerMeterSml {
+namespace PowerMeters::Sml::Http {
+
+class Provider : public ::PowerMeters::Sml::Provider {
 public:
-    explicit PowerMeterHttpSml(PowerMeterHttpSmlConfig const& cfg)
-        : PowerMeterSml("PowerMeterHttpSml")
+    explicit Provider(PowerMeterHttpSmlConfig const& cfg)
+        : ::PowerMeters::Sml::Provider("PowerMeterHttpSml")
         , _cfg(cfg) { }
 
-    ~PowerMeterHttpSml();
+    ~Provider();
 
     bool init() final;
     void loop() final;
@@ -43,3 +45,5 @@ class PowerMeterHttpSml : public PowerMeterSml {
     mutable std::mutex _pollingMutex;
     std::condition_variable _cv;
 };
+
+} // namespace PowerMeters::Sml::Http
diff --git a/include/PowerMeterSerialSml.h b/include/powermeter/sml/serial/Provider.h
similarity index 83%
rename from include/PowerMeterSerialSml.h
rename to include/powermeter/sml/serial/Provider.h
index abe595332..c620871ca 100644
--- a/include/PowerMeterSerialSml.h
+++ b/include/powermeter/sml/serial/Provider.h
@@ -1,15 +1,17 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #pragma once
 
-#include "PowerMeterSml.h"
 #include <SoftwareSerial.h>
+#include <powermeter/sml/Provider.h>
 
-class PowerMeterSerialSml : public PowerMeterSml {
+namespace PowerMeters::Sml::Serial {
+
+class Provider : public ::PowerMeters::Sml::Provider {
 public:
-    PowerMeterSerialSml()
-        : PowerMeterSml("PowerMeterSerialSml") { }
+    Provider()
+        : ::PowerMeters::Sml::Provider("PowerMeterSerialSml") { }
 
-    ~PowerMeterSerialSml();
+    ~Provider();
 
     bool init() final;
     void loop() final;
@@ -42,3 +44,5 @@ class PowerMeterSerialSml : public PowerMeterSml {
 
     std::unique_ptr<SoftwareSerial> _upSmlSerial = nullptr;
 };
+
+} // namespace PowerMeters::Sml::Serial
diff --git a/include/PowerMeterUdpSmaHomeManager.h b/include/powermeter/udp/smahm/Provider.h
similarity index 79%
rename from include/PowerMeterUdpSmaHomeManager.h
rename to include/powermeter/udp/smahm/Provider.h
index 5d4b3a8d3..3abdd1c41 100644
--- a/include/PowerMeterUdpSmaHomeManager.h
+++ b/include/powermeter/udp/smahm/Provider.h
@@ -5,11 +5,13 @@
 #pragma once
 
 #include <cstdint>
-#include "PowerMeterProvider.h"
+#include <powermeter/Provider.h>
 
-class PowerMeterUdpSmaHomeManager : public PowerMeterProvider {
+namespace PowerMeters::Udp::SmaHM {
+
+class Provider : public ::PowerMeters::Provider {
 public:
-    ~PowerMeterUdpSmaHomeManager();
+    ~Provider();
 
     bool init() final;
     void loop() final;
@@ -29,3 +31,5 @@ class PowerMeterUdpSmaHomeManager : public PowerMeterProvider {
     uint32_t _previousMillis = 0;
     uint32_t _serial = 0;
 };
+
+} // namespace PowerMeters::Udp::SmaHM
diff --git a/src/Display_Graphic.cpp b/src/Display_Graphic.cpp
index ce845cec7..54273a73d 100644
--- a/src/Display_Graphic.cpp
+++ b/src/Display_Graphic.cpp
@@ -5,7 +5,7 @@
 #include "Display_Graphic.h"
 #include "Datastore.h"
 #include "I18n.h"
-#include "PowerMeter.h"
+#include <powermeter/Controller.h>
 #include "Configuration.h"
 #include <NetworkSettings.h>
 #include <map>
diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp
index fe47258c2..a1f85877d 100644
--- a/src/PowerLimiter.cpp
+++ b/src/PowerLimiter.cpp
@@ -4,7 +4,7 @@
  */
 
 #include "Battery.h"
-#include "PowerMeter.h"
+#include <powermeter/Controller.h>
 #include "PowerLimiter.h"
 #include "Configuration.h"
 #include "MqttSettings.h"
diff --git a/src/PowerMeter.cpp b/src/PowerMeter.cpp
deleted file mode 100644
index b6041e34e..000000000
--- a/src/PowerMeter.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-#include "PowerMeter.h"
-#include "Configuration.h"
-#include "PowerMeterHttpJson.h"
-#include "PowerMeterHttpSml.h"
-#include "PowerMeterMqtt.h"
-#include "PowerMeterSerialSdm.h"
-#include "PowerMeterSerialSml.h"
-#include "PowerMeterUdpSmaHomeManager.h"
-
-PowerMeterClass PowerMeter;
-
-void PowerMeterClass::init(Scheduler& scheduler)
-{
-    scheduler.addTask(_loopTask);
-    _loopTask.setCallback(std::bind(&PowerMeterClass::loop, this));
-    _loopTask.setIterations(TASK_FOREVER);
-    _loopTask.enable();
-
-    updateSettings();
-}
-
-void PowerMeterClass::updateSettings()
-{
-    std::lock_guard<std::mutex> l(_mutex);
-
-    if (_upProvider) { _upProvider.reset(); }
-
-    auto const& pmcfg = Configuration.get().PowerMeter;
-
-    if (!pmcfg.Enabled) { return; }
-
-    switch(static_cast<PowerMeterProvider::Type>(pmcfg.Source)) {
-        case PowerMeterProvider::Type::MQTT:
-            _upProvider = std::make_unique<PowerMeterMqtt>(pmcfg.Mqtt);
-            break;
-        case PowerMeterProvider::Type::SDM1PH:
-            _upProvider = std::make_unique<PowerMeterSerialSdm>(
-                    PowerMeterSerialSdm::Phases::One, pmcfg.SerialSdm);
-            break;
-        case PowerMeterProvider::Type::SDM3PH:
-            _upProvider = std::make_unique<PowerMeterSerialSdm>(
-                    PowerMeterSerialSdm::Phases::Three, pmcfg.SerialSdm);
-            break;
-        case PowerMeterProvider::Type::HTTP_JSON:
-            _upProvider = std::make_unique<PowerMeterHttpJson>(pmcfg.HttpJson);
-            break;
-        case PowerMeterProvider::Type::SERIAL_SML:
-            _upProvider = std::make_unique<PowerMeterSerialSml>();
-            break;
-        case PowerMeterProvider::Type::SMAHM2:
-            _upProvider = std::make_unique<PowerMeterUdpSmaHomeManager>();
-            break;
-        case PowerMeterProvider::Type::HTTP_SML:
-            _upProvider = std::make_unique<PowerMeterHttpSml>(pmcfg.HttpSml);
-            break;
-    }
-
-    if (!_upProvider->init()) {
-        _upProvider = nullptr;
-    }
-}
-
-float PowerMeterClass::getPowerTotal() const
-{
-    std::lock_guard<std::mutex> l(_mutex);
-    if (!_upProvider) { return 0.0; }
-    return _upProvider->getPowerTotal();
-}
-
-uint32_t PowerMeterClass::getLastUpdate() const
-{
-    std::lock_guard<std::mutex> l(_mutex);
-    if (!_upProvider) { return 0; }
-    return _upProvider->getLastUpdate();
-}
-
-bool PowerMeterClass::isDataValid() const
-{
-    std::lock_guard<std::mutex> l(_mutex);
-    if (!_upProvider) { return false; }
-    return _upProvider->isDataValid();
-}
-
-void PowerMeterClass::loop()
-{
-    std::lock_guard<std::mutex> lock(_mutex);
-    if (!_upProvider) { return; }
-    _upProvider->loop();
-
-    auto const& pmcfg = Configuration.get().PowerMeter;
-    if (pmcfg.Source == static_cast<uint8_t>(PowerMeterProvider::Type::MQTT)) { return; }
-    _upProvider->mqttLoop();
-}
diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp
index 8727dd220..cdcfb67eb 100644
--- a/src/WebApi_mqtt.cpp
+++ b/src/WebApi_mqtt.cpp
@@ -15,7 +15,7 @@
 #include "WebApi_errors.h"
 #include "helper.h"
 #include "PowerLimiter.h"
-#include "PowerMeter.h"
+#include <powermeter/Controller.h>
 #include <AsyncJson.h>
 #include <solarcharger/Controller.h>
 
diff --git a/src/WebApi_powermeter.cpp b/src/WebApi_powermeter.cpp
index 35fc04dcd..8ffbbc22b 100644
--- a/src/WebApi_powermeter.cpp
+++ b/src/WebApi_powermeter.cpp
@@ -9,9 +9,9 @@
 #include "MqttHandleHass.h"
 #include "MqttSettings.h"
 #include "PowerLimiter.h"
-#include "PowerMeter.h"
-#include "PowerMeterHttpJson.h"
-#include "PowerMeterHttpSml.h"
+#include <powermeter/Controller.h>
+#include <powermeter/json/http/Provider.h>
+#include <powermeter/sml/http/Provider.h>
 #include "WebApi.h"
 #include "helper.h"
 
@@ -116,7 +116,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
         return true;
     };
 
-    if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::HTTP_JSON) {
+    if (static_cast<::PowerMeters::Provider::Type>(root["source"].as<uint8_t>()) == ::PowerMeters::Provider::Type::HTTP_JSON) {
         JsonObject httpJson = root["http_json"];
         JsonArray valueConfigs = httpJson["values"];
         for (uint8_t i = 0; i < valueConfigs.size(); i++) {
@@ -142,7 +142,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
         }
     }
 
-    if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::HTTP_SML) {
+    if (static_cast<::PowerMeters::Provider::Type>(root["source"].as<uint8_t>()) == ::PowerMeters::Provider::Type::HTTP_SML) {
         JsonObject httpSml = root["http_sml"];
         if (!checkHttpConfig(httpSml["http_request"].as<JsonObject>())) {
             return;
@@ -195,10 +195,10 @@ void WebApiPowerMeterClass::onTestHttpJsonRequest(AsyncWebServerRequest* request
     auto powerMeterConfig = std::make_unique<PowerMeterHttpJsonConfig>();
     Configuration.deserializePowerMeterHttpJsonConfig(root["http_json"].as<JsonObject>(),
             *powerMeterConfig);
-    auto upMeter = std::make_unique<PowerMeterHttpJson>(*powerMeterConfig);
+    auto upMeter = std::make_unique<::PowerMeters::Json::Http::Provider>(*powerMeterConfig);
     upMeter->init();
     auto res = upMeter->poll();
-    using values_t = PowerMeterHttpJson::power_values_t;
+    using values_t = ::PowerMeters::Json::Http::Provider::power_values_t;
     if (std::holds_alternative<values_t>(res)) {
         retMsg["type"] = "success";
         auto vals = std::get<values_t>(res);
@@ -236,7 +236,7 @@ void WebApiPowerMeterClass::onTestHttpSmlRequest(AsyncWebServerRequest* request)
     auto powerMeterConfig = std::make_unique<PowerMeterHttpSmlConfig>();
     Configuration.deserializePowerMeterHttpSmlConfig(root["http_sml"].as<JsonObject>(),
             *powerMeterConfig);
-    auto upMeter = std::make_unique<PowerMeterHttpSml>(*powerMeterConfig);
+    auto upMeter = std::make_unique<::PowerMeters::Sml::Http::Provider>(*powerMeterConfig);
     upMeter->init();
     auto res = upMeter->poll();
     if (res.isEmpty()) {
diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp
index 1eddcb277..95f447bae 100644
--- a/src/WebApi_ws_live.cpp
+++ b/src/WebApi_ws_live.cpp
@@ -9,7 +9,7 @@
 #include "WebApi.h"
 #include "Battery.h"
 #include <gridcharger/huawei/Controller.h>
-#include "PowerMeter.h"
+#include <powermeter/Controller.h>
 #include "defaults.h"
 #include <solarcharger/Controller.h>
 #include <AsyncJson.h>
diff --git a/src/gridcharger/huawei/Controller.cpp b/src/gridcharger/huawei/Controller.cpp
index 6359b2e66..e5b9c3ea0 100644
--- a/src/gridcharger/huawei/Controller.cpp
+++ b/src/gridcharger/huawei/Controller.cpp
@@ -7,7 +7,7 @@
 #include <gridcharger/huawei/MCP2515.h>
 #include <gridcharger/huawei/TWAI.h>
 #include "MessageOutput.h"
-#include "PowerMeter.h"
+#include <powermeter/Controller.h>
 #include "PowerLimiter.h"
 #include "Configuration.h"
 
diff --git a/src/main.cpp b/src/main.cpp
index ba12c129d..06216a0c1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -29,7 +29,7 @@
 #include "SunPosition.h"
 #include "Utils.h"
 #include "WebApi.h"
-#include "PowerMeter.h"
+#include <powermeter/Controller.h>
 #include "PowerLimiter.h"
 #include "defaults.h"
 #include <solarcharger/Controller.h>
diff --git a/src/powermeter/Controller.cpp b/src/powermeter/Controller.cpp
new file mode 100644
index 000000000..a5b51e60c
--- /dev/null
+++ b/src/powermeter/Controller.cpp
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <powermeter/Controller.h>
+#include <Configuration.h>
+#include <powermeter/json/http/Provider.h>
+#include <powermeter/sml/http/Provider.h>
+#include <powermeter/mqtt/Provider.h>
+#include <powermeter/sdm/serial/Provider.h>
+#include <powermeter/sml/serial/Provider.h>
+#include <powermeter/udp/smahm/Provider.h>
+
+PowerMeters::Controller PowerMeter;
+
+namespace PowerMeters {
+
+void Controller::init(Scheduler& scheduler)
+{
+    scheduler.addTask(_loopTask);
+    _loopTask.setCallback(std::bind(&Controller::loop, this));
+    _loopTask.setIterations(TASK_FOREVER);
+    _loopTask.enable();
+
+    updateSettings();
+}
+
+void Controller::updateSettings()
+{
+    std::lock_guard<std::mutex> l(_mutex);
+
+    if (_upProvider) { _upProvider.reset(); }
+
+    auto const& pmcfg = Configuration.get().PowerMeter;
+
+    if (!pmcfg.Enabled) { return; }
+
+    switch(static_cast<Provider::Type>(pmcfg.Source)) {
+        case Provider::Type::MQTT:
+            _upProvider = std::make_unique<::PowerMeters::Mqtt::Provider>(pmcfg.Mqtt);
+            break;
+        case Provider::Type::SDM1PH:
+            _upProvider = std::make_unique<::PowerMeters::Sdm::Serial::Provider>(
+                    ::PowerMeters::Sdm::Serial::Provider::Phases::One, pmcfg.SerialSdm);
+            break;
+        case Provider::Type::SDM3PH:
+            _upProvider = std::make_unique<::PowerMeters::Sdm::Serial::Provider>(
+                    ::PowerMeters::Sdm::Serial::Provider::Phases::Three, pmcfg.SerialSdm);
+            break;
+        case Provider::Type::HTTP_JSON:
+            _upProvider = std::make_unique<::PowerMeters::Json::Http::Provider>(pmcfg.HttpJson);
+            break;
+        case Provider::Type::SERIAL_SML:
+            _upProvider = std::make_unique<::PowerMeters::Sml::Serial::Provider>();
+            break;
+        case Provider::Type::SMAHM2:
+            _upProvider = std::make_unique<::PowerMeters::Udp::SmaHM::Provider>();
+            break;
+        case Provider::Type::HTTP_SML:
+            _upProvider = std::make_unique<::PowerMeters::Sml::Http::Provider>(pmcfg.HttpSml);
+            break;
+    }
+
+    if (!_upProvider->init()) {
+        _upProvider = nullptr;
+    }
+}
+
+float Controller::getPowerTotal() const
+{
+    std::lock_guard<std::mutex> l(_mutex);
+    if (!_upProvider) { return 0.0; }
+    return _upProvider->getPowerTotal();
+}
+
+uint32_t Controller::getLastUpdate() const
+{
+    std::lock_guard<std::mutex> l(_mutex);
+    if (!_upProvider) { return 0; }
+    return _upProvider->getLastUpdate();
+}
+
+bool Controller::isDataValid() const
+{
+    std::lock_guard<std::mutex> l(_mutex);
+    if (!_upProvider) { return false; }
+    return _upProvider->isDataValid();
+}
+
+void Controller::loop()
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+    if (!_upProvider) { return; }
+    _upProvider->loop();
+
+    auto const& pmcfg = Configuration.get().PowerMeter;
+    if (pmcfg.Source == static_cast<uint8_t>(Provider::Type::MQTT)) { return; }
+    _upProvider->mqttLoop();
+}
+
+} // namespace PowerMeters
diff --git a/src/PowerMeterProvider.cpp b/src/powermeter/Provider.cpp
similarity index 68%
rename from src/PowerMeterProvider.cpp
rename to src/powermeter/Provider.cpp
index d1c7d6282..6c0bfeef7 100644
--- a/src/PowerMeterProvider.cpp
+++ b/src/powermeter/Provider.cpp
@@ -1,18 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-#include "PowerMeterProvider.h"
-#include "MqttSettings.h"
+#include <powermeter/Provider.h>
+#include <MqttSettings.h>
 
-bool PowerMeterProvider::isDataValid() const
+namespace PowerMeters {
+
+bool Provider::isDataValid() const
 {
     return _lastUpdate > 0 && ((millis() - _lastUpdate) < (30 * 1000));
 }
 
-void PowerMeterProvider::mqttPublish(String const& topic, float const& value) const
+void Provider::mqttPublish(String const& topic, float const& value) const
 {
     MqttSettings.publish("powermeter/" + topic, String(value));
 }
 
-void PowerMeterProvider::mqttLoop() const
+void Provider::mqttLoop() const
 {
     if (!MqttSettings.getConnected()) { return; }
 
@@ -27,3 +29,5 @@ void PowerMeterProvider::mqttLoop() const
 
     _lastMqttPublish = millis();
 }
+
+} // namespace PowerMeters
diff --git a/src/PowerMeterHttpJson.cpp b/src/powermeter/json/http/Provider.cpp
similarity index 81%
rename from src/PowerMeterHttpJson.cpp
rename to src/powermeter/json/http/Provider.cpp
index 3d4431f3d..9ce349414 100644
--- a/src/PowerMeterHttpJson.cpp
+++ b/src/powermeter/json/http/Provider.cpp
@@ -1,14 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-#include "Utils.h"
-#include "PowerMeterHttpJson.h"
-#include "MessageOutput.h"
+#include <Utils.h>
+#include <powermeter/json/http/Provider.h>
+#include <MessageOutput.h>
 #include <WiFiClientSecure.h>
 #include <ArduinoJson.h>
-#include "mbedtls/sha256.h"
+#include <mbedtls/sha256.h>
 #include <base64.h>
 #include <ESPmDNS.h>
 
-PowerMeterHttpJson::~PowerMeterHttpJson()
+namespace PowerMeters::Json::Http {
+
+Provider::~Provider()
 {
     _taskDone = false;
 
@@ -24,7 +26,7 @@ PowerMeterHttpJson::~PowerMeterHttpJson()
     }
 }
 
-bool PowerMeterHttpJson::init()
+bool Provider::init()
 {
     for (uint8_t i = 0; i < POWERMETER_HTTP_JSON_MAX_VALUES; i++) {
         auto const& valueConfig = _cfg.Values[i];
@@ -43,15 +45,15 @@ bool PowerMeterHttpJson::init()
             continue;
         }
 
-        MessageOutput.printf("[PowerMeterHttpJson] Initializing HTTP getter for value %d failed:\r\n", i + 1);
-        MessageOutput.printf("[PowerMeterHttpJson] %s\r\n", _httpGetters[i]->getErrorText());
+        MessageOutput.printf("[PowerMeters::Json::Http] Initializing HTTP getter for value %d failed:\r\n", i + 1);
+        MessageOutput.printf("[PowerMeters::Json::Http] %s\r\n", _httpGetters[i]->getErrorText());
         return false;
     }
 
     return true;
 }
 
-void PowerMeterHttpJson::loop()
+void Provider::loop()
 {
     if (_taskHandle != nullptr) { return; }
 
@@ -60,19 +62,19 @@ void PowerMeterHttpJson::loop()
     lock.unlock();
 
     uint32_t constexpr stackSize = 3072;
-    xTaskCreate(PowerMeterHttpJson::pollingLoopHelper, "PM:HTTP+JSON",
+    xTaskCreate(Provider::pollingLoopHelper, "PM:HTTP+JSON",
             stackSize, this, 1/*prio*/, &_taskHandle);
 }
 
-void PowerMeterHttpJson::pollingLoopHelper(void* context)
+void Provider::pollingLoopHelper(void* context)
 {
-    auto pInstance = static_cast<PowerMeterHttpJson*>(context);
+    auto pInstance = static_cast<Provider*>(context);
     pInstance->pollingLoop();
     pInstance->_taskDone = true;
     vTaskDelete(nullptr);
 }
 
-void PowerMeterHttpJson::pollingLoop()
+void Provider::pollingLoop()
 {
     std::unique_lock<std::mutex> lock(_pollingMutex);
 
@@ -93,17 +95,17 @@ void PowerMeterHttpJson::pollingLoop()
         lock.lock();
 
         if (std::holds_alternative<String>(res)) {
-            MessageOutput.printf("[PowerMeterHttpJson] %s\r\n", std::get<String>(res).c_str());
+            MessageOutput.printf("[PowerMeters::Json::Http] %s\r\n", std::get<String>(res).c_str());
             continue;
         }
 
-        MessageOutput.printf("[PowerMeterHttpJson] New total: %.2f\r\n", getPowerTotal());
+        MessageOutput.printf("[PowerMeters::Json::Http] New total: %.2f\r\n", getPowerTotal());
 
         gotUpdate();
     }
 }
 
-PowerMeterHttpJson::poll_result_t PowerMeterHttpJson::poll()
+Provider::poll_result_t Provider::poll()
 {
     power_values_t cache;
     JsonDocument jsonResponse;
@@ -169,7 +171,7 @@ PowerMeterHttpJson::poll_result_t PowerMeterHttpJson::poll()
     return cache;
 }
 
-float PowerMeterHttpJson::getPowerTotal() const
+float Provider::getPowerTotal() const
 {
     float sum = 0.0;
     std::unique_lock<std::mutex> lock(_valueMutex);
@@ -177,16 +179,18 @@ float PowerMeterHttpJson::getPowerTotal() const
     return sum;
 }
 
-bool PowerMeterHttpJson::isDataValid() const
+bool Provider::isDataValid() const
 {
     uint32_t age = millis() - getLastUpdate();
     return getLastUpdate() > 0 && (age < (3 * _cfg.PollingInterval * 1000));
 }
 
-void PowerMeterHttpJson::doMqttPublish() const
+void Provider::doMqttPublish() const
 {
     std::unique_lock<std::mutex> lock(_valueMutex);
     mqttPublish("power1", _powerValues[0]);
     mqttPublish("power2", _powerValues[1]);
     mqttPublish("power3", _powerValues[2]);
 }
+
+} // namespace PowerMeters::Json::Http
diff --git a/src/PowerMeterMqtt.cpp b/src/powermeter/mqtt/Provider.cpp
similarity index 79%
rename from src/PowerMeterMqtt.cpp
rename to src/powermeter/mqtt/Provider.cpp
index 99a51a69e..a35178cb3 100644
--- a/src/PowerMeterMqtt.cpp
+++ b/src/powermeter/mqtt/Provider.cpp
@@ -1,18 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-#include "PowerMeterMqtt.h"
-#include "MqttSettings.h"
-#include "MessageOutput.h"
-#include "ArduinoJson.h"
-#include "Utils.h"
+#include <powermeter/mqtt/Provider.h>
+#include <MqttSettings.h>
+#include <MessageOutput.h>
+#include <ArduinoJson.h>
+#include <Utils.h>
 
-bool PowerMeterMqtt::init()
+namespace PowerMeters::Mqtt {
+
+bool Provider::init()
 {
     auto subscribe = [this](PowerMeterMqttValue const& val, float* targetVariable) {
         *targetVariable = 0;
         char const* topic = val.Topic;
         if (strlen(topic) == 0) { return; }
         MqttSettings.subscribe(topic, 0,
-                std::bind(&PowerMeterMqtt::onMessage,
+                std::bind(&Provider::onMessage,
                     this, std::placeholders::_1, std::placeholders::_2,
                     std::placeholders::_3, std::placeholders::_4,
                     std::placeholders::_5, std::placeholders::_6,
@@ -28,17 +30,17 @@ bool PowerMeterMqtt::init()
     return _mqttSubscriptions.size() > 0;
 }
 
-PowerMeterMqtt::~PowerMeterMqtt()
+Provider::~Provider()
 {
     for (auto const& t: _mqttSubscriptions) { MqttSettings.unsubscribe(t); }
     _mqttSubscriptions.clear();
 }
 
-void PowerMeterMqtt::onMessage(PowerMeterMqtt::MsgProperties const& properties,
+void Provider::onMessage(Provider::MsgProperties const& properties,
         char const* topic, uint8_t const* payload, size_t len, size_t index,
         size_t total, float* targetVariable, PowerMeterMqttValue const* cfg)
 {
-    auto extracted = Utils::getNumericValueFromMqttPayload<float>("PowerMeterMqtt",
+    auto extracted = Utils::getNumericValueFromMqttPayload<float>("PowerMeters::Mqtt",
             std::string(reinterpret_cast<const char*>(payload), len), topic,
             cfg->JsonPath);
 
@@ -66,17 +68,19 @@ void PowerMeterMqtt::onMessage(PowerMeterMqtt::MsgProperties const& properties,
     }
 
     if (_verboseLogging) {
-        MessageOutput.printf("[PowerMeterMqtt] Topic '%s': new value: %5.2f, "
+        MessageOutput.printf("[PowerMeters::Mqtt] Topic '%s': new value: %5.2f, "
                 "total: %5.2f\r\n", topic, newValue, getPowerTotal());
     }
 
     gotUpdate();
 }
 
-float PowerMeterMqtt::getPowerTotal() const
+float Provider::getPowerTotal() const
 {
     float sum = 0.0;
     std::unique_lock<std::mutex> lock(_mutex);
     for (auto v: _powerValues) { sum += v; }
     return sum;
 }
+
+} // namespace PowerMeters::Mqtt
diff --git a/src/PowerMeterSerialSdm.cpp b/src/powermeter/sdm/serial/Provider.cpp
similarity index 80%
rename from src/PowerMeterSerialSdm.cpp
rename to src/powermeter/sdm/serial/Provider.cpp
index 4d271b750..0cc438468 100644
--- a/src/PowerMeterSerialSdm.cpp
+++ b/src/powermeter/sdm/serial/Provider.cpp
@@ -1,9 +1,11 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-#include "PowerMeterSerialSdm.h"
-#include "PinMapping.h"
-#include "MessageOutput.h"
+#include <powermeter/sdm/serial/Provider.h>
+#include <PinMapping.h>
+#include <MessageOutput.h>
 
-PowerMeterSerialSdm::~PowerMeterSerialSdm()
+namespace PowerMeters::Sdm::Serial {
+
+Provider::~Provider()
 {
     _taskDone = false;
 
@@ -24,15 +26,15 @@ PowerMeterSerialSdm::~PowerMeterSerialSdm()
     }
 }
 
-bool PowerMeterSerialSdm::init()
+bool Provider::init()
 {
     const PinMapping_t& pin = PinMapping.get();
 
-    MessageOutput.printf("[PowerMeterSerialSdm] rx = %d, tx = %d, dere = %d, rxen = %d, txen = %d \r\n",
+    MessageOutput.printf("[PowerMeters::Sdm::Serial] rx = %d, tx = %d, dere = %d, rxen = %d, txen = %d \r\n",
             pin.powermeter_rx, pin.powermeter_tx, pin.powermeter_dere, pin.powermeter_rxen, pin.powermeter_txen);
 
     if (pin.powermeter_rx < 0 || pin.powermeter_tx < 0) {
-        MessageOutput.println("[PowerMeterSerialSdm] invalid pin config for SDM "
+        MessageOutput.println("[PowerMeters::Sdm::Serial] invalid pin config for SDM "
                 "power meter (RX and TX pins must be defined)");
         return false;
     }
@@ -53,7 +55,7 @@ bool PowerMeterSerialSdm::init()
     return true;
 }
 
-void PowerMeterSerialSdm::loop()
+void Provider::loop()
 {
     if (_taskHandle != nullptr) { return; }
 
@@ -62,23 +64,23 @@ void PowerMeterSerialSdm::loop()
     lock.unlock();
 
     uint32_t constexpr stackSize = 3072;
-    xTaskCreate(PowerMeterSerialSdm::pollingLoopHelper, "PM:SDM",
+    xTaskCreate(Provider::pollingLoopHelper, "PM:SDM",
             stackSize, this, 1/*prio*/, &_taskHandle);
 }
 
-float PowerMeterSerialSdm::getPowerTotal() const
+float Provider::getPowerTotal() const
 {
     std::lock_guard<std::mutex> l(_valueMutex);
     return _phase1Power + _phase2Power + _phase3Power;
 }
 
-bool PowerMeterSerialSdm::isDataValid() const
+bool Provider::isDataValid() const
 {
     uint32_t age = millis() - getLastUpdate();
     return getLastUpdate() > 0 && (age < (3 * _cfg.PollingInterval * 1000));
 }
 
-void PowerMeterSerialSdm::doMqttPublish() const
+void Provider::doMqttPublish() const
 {
     std::lock_guard<std::mutex> l(_valueMutex);
     mqttPublish("power1", _phase1Power);
@@ -94,15 +96,15 @@ void PowerMeterSerialSdm::doMqttPublish() const
     }
 }
 
-void PowerMeterSerialSdm::pollingLoopHelper(void* context)
+void Provider::pollingLoopHelper(void* context)
 {
-    auto pInstance = static_cast<PowerMeterSerialSdm*>(context);
+    auto pInstance = static_cast<Provider*>(context);
     pInstance->pollingLoop();
     pInstance->_taskDone = true;
     vTaskDelete(nullptr);
 }
 
-bool PowerMeterSerialSdm::readValue(std::unique_lock<std::mutex>& lock, uint16_t reg, float& targetVar)
+bool Provider::readValue(std::unique_lock<std::mutex>& lock, uint16_t reg, float& targetVar)
 {
     lock.unlock(); // reading values takes too long to keep holding the lock
     float val = _upSdm->readVal(reg, _cfg.Address);
@@ -118,7 +120,7 @@ bool PowerMeterSerialSdm::readValue(std::unique_lock<std::mutex>& lock, uint16_t
     switch (err) {
         case SDM_ERR_NO_ERROR:
             if (_verboseLogging) {
-                MessageOutput.printf("[PowerMeterSerialSdm]: read register %d "
+                MessageOutput.printf("[PowerMeters::Sdm::Serial]: read register %d "
                         "(0x%04x) successfully\r\n", reg, reg);
             }
 
@@ -126,23 +128,23 @@ bool PowerMeterSerialSdm::readValue(std::unique_lock<std::mutex>& lock, uint16_t
             return true;
             break;
         case SDM_ERR_CRC_ERROR:
-            MessageOutput.printf("[PowerMeterSerialSdm]: CRC error "
+            MessageOutput.printf("[PowerMeters::Sdm::Serial]: CRC error "
                     "while reading register %d (0x%04x)\r\n", reg, reg);
             break;
         case SDM_ERR_WRONG_BYTES:
-            MessageOutput.printf("[PowerMeterSerialSdm]: unexpected data in "
+            MessageOutput.printf("[PowerMeters::Sdm::Serial]: unexpected data in "
                     "message while reading register %d (0x%04x)\r\n", reg, reg);
             break;
         case SDM_ERR_NOT_ENOUGHT_BYTES:
-            MessageOutput.printf("[PowerMeterSerialSdm]: unexpected end of "
+            MessageOutput.printf("[PowerMeters::Sdm::Serial]: unexpected end of "
                     "message while reading register %d (0x%04x)\r\n", reg, reg);
             break;
         case SDM_ERR_TIMEOUT:
-            MessageOutput.printf("[PowerMeterSerialSdm]: timeout occured "
+            MessageOutput.printf("[PowerMeters::Sdm::Serial]: timeout occured "
                     "while reading register %d (0x%04x)\r\n", reg, reg);
             break;
         default:
-            MessageOutput.printf("[PowerMeterSerialSdm]: unknown SDM error "
+            MessageOutput.printf("[PowerMeters::Sdm::Serial]: unknown SDM error "
                     "code after reading register %d (0x%04x)\r\n", reg, reg);
             break;
     }
@@ -150,7 +152,7 @@ bool PowerMeterSerialSdm::readValue(std::unique_lock<std::mutex>& lock, uint16_t
     return false;
 }
 
-void PowerMeterSerialSdm::pollingLoop()
+void Provider::pollingLoop()
 {
     std::unique_lock<std::mutex> lock(_pollingMutex);
 
@@ -204,8 +206,10 @@ void PowerMeterSerialSdm::pollingLoop()
             _energyExport = static_cast<float>(energyExport);
         }
 
-        MessageOutput.printf("[PowerMeterSerialSdm] TotalPower: %5.2f\r\n", getPowerTotal());
+        MessageOutput.printf("[PowerMeters::Sdm::Serial] TotalPower: %5.2f\r\n", getPowerTotal());
 
         gotUpdate();
     }
 }
+
+} // namespace PowerMeters::Sdm::Serial
diff --git a/src/PowerMeterSml.cpp b/src/powermeter/sml/Provider.cpp
similarity index 86%
rename from src/PowerMeterSml.cpp
rename to src/powermeter/sml/Provider.cpp
index c44662104..86cce573a 100644
--- a/src/PowerMeterSml.cpp
+++ b/src/powermeter/sml/Provider.cpp
@@ -1,15 +1,17 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-#include "PowerMeterSml.h"
-#include "MessageOutput.h"
+#include <powermeter/sml/Provider.h>
+#include <MessageOutput.h>
 
-float PowerMeterSml::getPowerTotal() const
+namespace PowerMeters::Sml {
+
+float Provider::getPowerTotal() const
 {
     std::lock_guard<std::mutex> l(_mutex);
     if (_values.activePowerTotal.has_value()) { return *_values.activePowerTotal; }
     return 0;
 }
 
-void PowerMeterSml::doMqttPublish() const
+void Provider::doMqttPublish() const
 {
 #define PUB(t, m) \
     if (_values.m.has_value()) { mqttPublish(t, *_values.m); }
@@ -30,13 +32,13 @@ void PowerMeterSml::doMqttPublish() const
 #undef PUB
 }
 
-void PowerMeterSml::reset()
+void Provider::reset()
 {
     smlReset();
     _cache = { std::nullopt };
 }
 
-void PowerMeterSml::processSmlByte(uint8_t byte)
+void Provider::processSmlByte(uint8_t byte)
 {
     switch (smlState(byte)) {
         case SML_LISTEND:
@@ -71,3 +73,5 @@ void PowerMeterSml::processSmlByte(uint8_t byte)
             break;
     }
 }
+
+} // namespace PowerMeters::Sml
diff --git a/src/PowerMeterHttpSml.cpp b/src/powermeter/sml/http/Provider.cpp
similarity index 74%
rename from src/PowerMeterHttpSml.cpp
rename to src/powermeter/sml/http/Provider.cpp
index 8cfd14806..cf41572b5 100644
--- a/src/PowerMeterHttpSml.cpp
+++ b/src/powermeter/sml/http/Provider.cpp
@@ -1,11 +1,13 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-#include "PowerMeterHttpSml.h"
-#include "MessageOutput.h"
+#include <powermeter/sml/http/Provider.h>
+#include <MessageOutput.h>
 #include <WiFiClientSecure.h>
 #include <base64.h>
 #include <ESPmDNS.h>
 
-PowerMeterHttpSml::~PowerMeterHttpSml()
+namespace PowerMeters::Sml::Http {
+
+Provider::~Provider()
 {
     _taskDone = false;
 
@@ -21,21 +23,21 @@ PowerMeterHttpSml::~PowerMeterHttpSml()
     }
 }
 
-bool PowerMeterHttpSml::init()
+bool Provider::init()
 {
     _upHttpGetter = std::make_unique<HttpGetter>(_cfg.HttpRequest);
 
     if (_upHttpGetter->init()) { return true; }
 
-    MessageOutput.printf("[PowerMeterHttpSml] Initializing HTTP getter failed:\r\n");
-    MessageOutput.printf("[PowerMeterHttpSml] %s\r\n", _upHttpGetter->getErrorText());
+    MessageOutput.printf("[PowerMeters::Sml::Http] Initializing HTTP getter failed:\r\n");
+    MessageOutput.printf("[PowerMeters::Sml::Http] %s\r\n", _upHttpGetter->getErrorText());
 
     _upHttpGetter = nullptr;
 
     return false;
 }
 
-void PowerMeterHttpSml::loop()
+void Provider::loop()
 {
     if (_taskHandle != nullptr) { return; }
 
@@ -44,19 +46,19 @@ void PowerMeterHttpSml::loop()
     lock.unlock();
 
     uint32_t constexpr stackSize = 3072;
-    xTaskCreate(PowerMeterHttpSml::pollingLoopHelper, "PM:HTTP+SML",
+    xTaskCreate(Provider::pollingLoopHelper, "PM:HTTP+SML",
             stackSize, this, 1/*prio*/, &_taskHandle);
 }
 
-void PowerMeterHttpSml::pollingLoopHelper(void* context)
+void Provider::pollingLoopHelper(void* context)
 {
-    auto pInstance = static_cast<PowerMeterHttpSml*>(context);
+    auto pInstance = static_cast<Provider*>(context);
     pInstance->pollingLoop();
     pInstance->_taskDone = true;
     vTaskDelete(nullptr);
 }
 
-void PowerMeterHttpSml::pollingLoop()
+void Provider::pollingLoop()
 {
     std::unique_lock<std::mutex> lock(_pollingMutex);
 
@@ -77,7 +79,7 @@ void PowerMeterHttpSml::pollingLoop()
         lock.lock();
 
         if (!res.isEmpty()) {
-            MessageOutput.printf("[PowerMeterHttpSml] %s\r\n", res.c_str());
+            MessageOutput.printf("[PowerMeters::Sml::Http] %s\r\n", res.c_str());
             continue;
         }
 
@@ -85,13 +87,13 @@ void PowerMeterHttpSml::pollingLoop()
     }
 }
 
-bool PowerMeterHttpSml::isDataValid() const
+bool Provider::isDataValid() const
 {
     uint32_t age = millis() - getLastUpdate();
     return getLastUpdate() > 0 && (age < (3 * _cfg.PollingInterval * 1000));
 }
 
-String PowerMeterHttpSml::poll()
+String Provider::poll()
 {
     if (!_upHttpGetter) {
         return "Initialization of HTTP request failed";
@@ -111,7 +113,9 @@ String PowerMeterHttpSml::poll()
         processSmlByte(pStream->read());
     }
 
-    PowerMeterSml::reset();
+    ::PowerMeters::Sml::Provider::reset();
 
     return "";
 }
+
+} // namespace PowerMeters::Sml::Http
diff --git a/src/PowerMeterSerialSml.cpp b/src/powermeter/sml/serial/Provider.cpp
similarity index 81%
rename from src/PowerMeterSerialSml.cpp
rename to src/powermeter/sml/serial/Provider.cpp
index a6af3ccba..a25df0d2a 100644
--- a/src/PowerMeterSerialSml.cpp
+++ b/src/powermeter/sml/serial/Provider.cpp
@@ -1,16 +1,18 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-#include "PowerMeterSerialSml.h"
-#include "PinMapping.h"
-#include "MessageOutput.h"
+#include <powermeter/sml/serial/Provider.h>
+#include <PinMapping.h>
+#include <MessageOutput.h>
 
-bool PowerMeterSerialSml::init()
+namespace PowerMeters::Sml::Serial {
+
+bool Provider::init()
 {
     const PinMapping_t& pin = PinMapping.get();
 
-    MessageOutput.printf("[PowerMeterSerialSml] rx = %d\r\n", pin.powermeter_rx);
+    MessageOutput.printf("[PowerMeters::Sml::Serial] rx = %d\r\n", pin.powermeter_rx);
 
     if (pin.powermeter_rx < 0) {
-        MessageOutput.println("[PowerMeterSerialSml] invalid pin config "
+        MessageOutput.println("[PowerMeters::Sml::Serial] invalid pin config "
                 "for serial SML power meter (RX pin must be defined)");
         return false;
     }
@@ -26,7 +28,7 @@ bool PowerMeterSerialSml::init()
     return true;
 }
 
-void PowerMeterSerialSml::loop()
+void Provider::loop()
 {
     if (_taskHandle != nullptr) { return; }
 
@@ -35,11 +37,11 @@ void PowerMeterSerialSml::loop()
     lock.unlock();
 
     uint32_t constexpr stackSize = 3072;
-    xTaskCreate(PowerMeterSerialSml::pollingLoopHelper, "PM:SML",
+    xTaskCreate(Provider::pollingLoopHelper, "PM:SML",
             stackSize, this, 1/*prio*/, &_taskHandle);
 }
 
-PowerMeterSerialSml::~PowerMeterSerialSml()
+Provider::~Provider()
 {
     _taskDone = false;
 
@@ -58,15 +60,15 @@ PowerMeterSerialSml::~PowerMeterSerialSml()
     }
 }
 
-void PowerMeterSerialSml::pollingLoopHelper(void* context)
+void Provider::pollingLoopHelper(void* context)
 {
-    auto pInstance = static_cast<PowerMeterSerialSml*>(context);
+    auto pInstance = static_cast<Provider*>(context);
     pInstance->pollingLoop();
     pInstance->_taskDone = true;
     vTaskDelete(nullptr);
 }
 
-void PowerMeterSerialSml::pollingLoop()
+void Provider::pollingLoop()
 {
     int lastAvailable = 0;
     uint32_t gapStartMillis = 0;
@@ -115,8 +117,10 @@ void PowerMeterSerialSml::pollingLoop()
 
         lastAvailable = 0;
 
-        PowerMeterSml::reset();
+        ::PowerMeters::Sml::Provider::reset();
 
         lock.lock();
     }
 }
+
+} // namespace PowerMeters::Sml::Serial
diff --git a/src/PowerMeterUdpSmaHomeManager.cpp b/src/powermeter/udp/smahm/Provider.cpp
similarity index 86%
rename from src/PowerMeterUdpSmaHomeManager.cpp
rename to src/powermeter/udp/smahm/Provider.cpp
index d68275c2c..5df24801b 100644
--- a/src/PowerMeterUdpSmaHomeManager.cpp
+++ b/src/powermeter/udp/smahm/Provider.cpp
@@ -2,46 +2,48 @@
 /*
  * Copyright (C) 2024 Holger-Steffen Stapf
  */
-#include "PowerMeterUdpSmaHomeManager.h"
+#include <powermeter/udp/smahm/Provider.h>
 #include <Arduino.h>
 #include <WiFiUdp.h>
 #include "MessageOutput.h"
 
+namespace PowerMeters::Udp::SmaHM {
+
 static constexpr unsigned int multicastPort = 9522;  // local port to listen on
 static const IPAddress multicastIP(239, 12, 255, 254);
 static WiFiUDP SMAUdp;
 
 constexpr uint32_t interval = 1000;
 
-void PowerMeterUdpSmaHomeManager::Soutput(int kanal, int index, int art, int tarif,
+void Provider::Soutput(int kanal, int index, int art, int tarif,
         char const* name, float value, uint32_t timestamp)
 {
     if (!_verboseLogging) { return; }
 
-    MessageOutput.printf("[PowerMeterUdpSmaHomeManager] %s = %.1f (timestamp %u)\r\n",
+    MessageOutput.printf("[PowerMeters::Udp::SmaHM] %s = %.1f (timestamp %u)\r\n",
             name, value, timestamp);
 }
 
-bool PowerMeterUdpSmaHomeManager::init()
+bool Provider::init()
 {
     SMAUdp.begin(multicastPort);
     SMAUdp.beginMulticast(multicastIP, multicastPort);
     return true;
 }
 
-PowerMeterUdpSmaHomeManager::~PowerMeterUdpSmaHomeManager()
+Provider::~Provider()
 {
     SMAUdp.stop();
 }
 
-void PowerMeterUdpSmaHomeManager::doMqttPublish() const
+void Provider::doMqttPublish() const
 {
     mqttPublish("power1", _powerMeterL1);
     mqttPublish("power2", _powerMeterL2);
     mqttPublish("power3", _powerMeterL3);
 }
 
-uint8_t* PowerMeterUdpSmaHomeManager::decodeGroup(uint8_t* offset, uint16_t grouplen)
+uint8_t* Provider::decodeGroup(uint8_t* offset, uint16_t grouplen)
 {
     float Pbezug = 0;
     float BezugL1 = 0;
@@ -145,7 +147,7 @@ uint8_t* PowerMeterUdpSmaHomeManager::decodeGroup(uint8_t* offset, uint16_t grou
             continue;
         }
 
-        MessageOutput.printf("[PowerMeterUdpSmaHomeManager] Skipped unknown measurement: %d %d %d %d\r\n",
+        MessageOutput.printf("[PowerMeters::Udp::SmaHM] Skipped unknown measurement: %d %d %d %d\r\n",
                 kanal, index, art, tarif);
         offset += art;
     }
@@ -153,7 +155,7 @@ uint8_t* PowerMeterUdpSmaHomeManager::decodeGroup(uint8_t* offset, uint16_t grou
     return offset;
 }
 
-void PowerMeterUdpSmaHomeManager::loop()
+void Provider::loop()
 {
     uint32_t currentMillis = millis();
     if (currentMillis - _previousMillis < interval) { return; }
@@ -166,7 +168,7 @@ void PowerMeterUdpSmaHomeManager::loop()
     uint8_t buffer[1024];
     int rSize = SMAUdp.read(buffer, 1024);
     if (buffer[0] != 'S' || buffer[1] != 'M' || buffer[2] != 'A') {
-        MessageOutput.println("[PowerMeterUdpSmaHomeManager] Not an SMA packet?");
+        MessageOutput.println("[PowerMeters::Udp::SmaHM] Not an SMA packet?");
         return;
     }
 
@@ -197,8 +199,10 @@ void PowerMeterUdpSmaHomeManager::loop()
             continue;
         }
 
-        MessageOutput.printf("[PowerMeterUdpSmaHomeManager] Unhandled group 0x%04x with length %d\r\n",
+        MessageOutput.printf("[PowerMeters::Udp::SmaHM] Unhandled group 0x%04x with length %d\r\n",
                 grouptag, grouplen);
         offset += grouplen;
     } while (grouplen > 0 && offset + 4 < buffer + rSize);
 }
+
+} // namespace PowerMeters::Udp::SmaHM