diff --git a/include/PowerMeterSerialSdm.h b/include/PowerMeterSerialSdm.h index 56a52e6e..5e91bd4d 100644 --- a/include/PowerMeterSerialSdm.h +++ b/include/PowerMeterSerialSdm.h @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include +#include #include "Configuration.h" #include "PowerMeterProvider.h" #include "SDM.h" @@ -20,16 +22,20 @@ public: ~PowerMeterSerialSdm(); bool init() final; - void loop() final; + void loop() final { } // polling is performed asynchronously float getPowerTotal() const final; bool isDataValid() const final; void doMqttPublish() const final; private: + static void pollingLoopHelper(void* context); + std::atomic _taskDone; + void pollingLoop(); + Phases _phases; PowerMeterSerialSdmConfig const _cfg; - uint32_t _lastPoll; + uint32_t _lastPoll = 0; float _phase1Power = 0.0; float _phase2Power = 0.0; @@ -40,9 +46,14 @@ private: float _energyImport = 0.0; float _energyExport = 0.0; - mutable std::mutex _mutex; + mutable std::mutex _valueMutex; static char constexpr _sdmSerialPortOwner[] = "SDM power meter"; std::unique_ptr _upSdmSerial = nullptr; std::unique_ptr _upSdm = nullptr; + + TaskHandle_t _taskHandle = nullptr; + bool _stopPolling; + mutable std::mutex _pollingMutex; + std::condition_variable _cv; }; diff --git a/src/PowerMeterSerialSdm.cpp b/src/PowerMeterSerialSdm.cpp index 3c9941f6..6901e079 100644 --- a/src/PowerMeterSerialSdm.cpp +++ b/src/PowerMeterSerialSdm.cpp @@ -6,6 +6,19 @@ PowerMeterSerialSdm::~PowerMeterSerialSdm() { + _taskDone = false; + + std::unique_lock lock(_pollingMutex); + _stopPolling = true; + lock.unlock(); + + _cv.notify_all(); + + if (_taskHandle != nullptr) { + while (!_taskDone) { delay(10); } + _taskHandle = nullptr; + } + if (_upSdmSerial) { _upSdmSerial->end(); _upSdmSerial = nullptr; @@ -34,12 +47,20 @@ bool PowerMeterSerialSdm::init() SERIAL_8N1, pin.powermeter_rx, pin.powermeter_tx); _upSdm->begin(); + std::unique_lock lock(_pollingMutex); + _stopPolling = false; + lock.unlock(); + + uint32_t constexpr stackSize = 3072; + xTaskCreate(PowerMeterSerialSdm::pollingLoopHelper, "PM:SDM", + stackSize, this, 1/*prio*/, &_taskHandle); + return true; } float PowerMeterSerialSdm::getPowerTotal() const { - std::lock_guard l(_mutex); + std::lock_guard l(_valueMutex); return _phase1Power + _phase2Power + _phase3Power; } @@ -51,7 +72,7 @@ bool PowerMeterSerialSdm::isDataValid() const void PowerMeterSerialSdm::doMqttPublish() const { - std::lock_guard l(_mutex); + std::lock_guard l(_valueMutex); mqttPublish("power1", _phase1Power); mqttPublish("power2", _phase2Power); mqttPublish("power3", _phase3Power); @@ -62,50 +83,65 @@ void PowerMeterSerialSdm::doMqttPublish() const mqttPublish("export", _energyExport); } -void PowerMeterSerialSdm::loop() +void PowerMeterSerialSdm::pollingLoopHelper(void* context) { - if (!_upSdm) { return; } - - if ((millis() - _lastPoll) < (_cfg.PollingInterval * 1000)) { - return; - } - - uint8_t addr = _cfg.Address; - - // reading takes a "very long" time as each readVal() is a synchronous - // exchange of serial messages. cache the values and write later to - // enforce consistent values. - float phase1Power = _upSdm->readVal(SDM_PHASE_1_POWER, addr); - float phase2Power = 0.0; - float phase3Power = 0.0; - float phase1Voltage = _upSdm->readVal(SDM_PHASE_1_VOLTAGE, addr); - float phase2Voltage = 0.0; - float phase3Voltage = 0.0; - float energyImport = _upSdm->readVal(SDM_IMPORT_ACTIVE_ENERGY, addr); - float energyExport = _upSdm->readVal(SDM_EXPORT_ACTIVE_ENERGY, addr); - - if (_phases == Phases::Three) { - phase2Power = _upSdm->readVal(SDM_PHASE_2_POWER, addr); - phase3Power = _upSdm->readVal(SDM_PHASE_3_POWER, addr); - phase2Voltage = _upSdm->readVal(SDM_PHASE_2_VOLTAGE, addr); - phase3Voltage = _upSdm->readVal(SDM_PHASE_3_VOLTAGE, addr); - } - - { - std::lock_guard l(_mutex); - _phase1Power = static_cast(phase1Power); - _phase2Power = static_cast(phase2Power); - _phase3Power = static_cast(phase3Power); - _phase1Voltage = static_cast(phase1Voltage); - _phase2Voltage = static_cast(phase2Voltage); - _phase3Voltage = static_cast(phase3Voltage); - _energyImport = static_cast(energyImport); - _energyExport = static_cast(energyExport); - } - - gotUpdate(); - - MessageOutput.printf("[PowerMeterSerialSdm] TotalPower: %5.2f\r\n", getPowerTotal()); - - _lastPoll = millis(); + auto pInstance = static_cast(context); + pInstance->pollingLoop(); + pInstance->_taskDone = true; + vTaskDelete(nullptr); +} + +void PowerMeterSerialSdm::pollingLoop() +{ + std::unique_lock lock(_pollingMutex); + + while (!_stopPolling) { + auto elapsedMillis = millis() - _lastPoll; + auto intervalMillis = _cfg.PollingInterval * 1000; + if (_lastPoll > 0 && elapsedMillis < intervalMillis) { + auto sleepMs = intervalMillis - elapsedMillis; + _cv.wait_for(lock, std::chrono::milliseconds(sleepMs), + [this] { return _stopPolling; }); // releases the mutex + continue; + } + + _lastPoll = millis(); + + uint8_t addr = _cfg.Address; + + // reading takes a "very long" time as each readVal() is a synchronous + // exchange of serial messages. cache the values and write later to + // enforce consistent values. + float phase1Power = _upSdm->readVal(SDM_PHASE_1_POWER, addr); + float phase2Power = 0.0; + float phase3Power = 0.0; + float phase1Voltage = _upSdm->readVal(SDM_PHASE_1_VOLTAGE, addr); + float phase2Voltage = 0.0; + float phase3Voltage = 0.0; + float energyImport = _upSdm->readVal(SDM_IMPORT_ACTIVE_ENERGY, addr); + float energyExport = _upSdm->readVal(SDM_EXPORT_ACTIVE_ENERGY, addr); + + if (_phases == Phases::Three) { + phase2Power = _upSdm->readVal(SDM_PHASE_2_POWER, addr); + phase3Power = _upSdm->readVal(SDM_PHASE_3_POWER, addr); + phase2Voltage = _upSdm->readVal(SDM_PHASE_2_VOLTAGE, addr); + phase3Voltage = _upSdm->readVal(SDM_PHASE_3_VOLTAGE, addr); + } + + { + std::lock_guard l(_valueMutex); + _phase1Power = static_cast(phase1Power); + _phase2Power = static_cast(phase2Power); + _phase3Power = static_cast(phase3Power); + _phase1Voltage = static_cast(phase1Voltage); + _phase2Voltage = static_cast(phase2Voltage); + _phase3Voltage = static_cast(phase3Voltage); + _energyImport = static_cast(energyImport); + _energyExport = static_cast(energyExport); + } + + MessageOutput.printf("[PowerMeterSerialSdm] TotalPower: %5.2f\r\n", getPowerTotal()); + + gotUpdate(); + } }