Skip to content

Commit

Permalink
Feature: SDM power meter: poll asynchronously
Browse files Browse the repository at this point in the history
  • Loading branch information
schlimmchen committed Jun 27, 2024
1 parent 8a46ba9 commit 347dd67
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 48 deletions.
17 changes: 14 additions & 3 deletions include/PowerMeterSerialSdm.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <atomic>
#include <mutex>
#include <condition_variable>
#include "Configuration.h"
#include "PowerMeterProvider.h"
#include "SDM.h"
Expand All @@ -20,16 +22,20 @@ class PowerMeterSerialSdm : public PowerMeterProvider {
~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<bool> _taskDone;
void pollingLoop();

Phases _phases;
PowerMeterSerialSdmConfig const _cfg;

uint32_t _lastPoll;
uint32_t _lastPoll = 0;

float _phase1Power = 0.0;
float _phase2Power = 0.0;
Expand All @@ -40,9 +46,14 @@ class PowerMeterSerialSdm : public PowerMeterProvider {
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<HardwareSerial> _upSdmSerial = nullptr;
std::unique_ptr<SDM> _upSdm = nullptr;

TaskHandle_t _taskHandle = nullptr;
bool _stopPolling;
mutable std::mutex _pollingMutex;
std::condition_variable _cv;
};
126 changes: 81 additions & 45 deletions src/PowerMeterSerialSdm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@

PowerMeterSerialSdm::~PowerMeterSerialSdm()
{
_taskDone = false;

std::unique_lock<std::mutex> lock(_pollingMutex);
_stopPolling = true;
lock.unlock();

_cv.notify_all();

if (_taskHandle != nullptr) {
while (!_taskDone) { delay(10); }
_taskHandle = nullptr;
}

if (_upSdmSerial) {
_upSdmSerial->end();
_upSdmSerial = nullptr;
Expand Down Expand Up @@ -34,12 +47,20 @@ bool PowerMeterSerialSdm::init()
SERIAL_8N1, pin.powermeter_rx, pin.powermeter_tx);
_upSdm->begin();

std::unique_lock<std::mutex> 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<std::mutex> l(_mutex);
std::lock_guard<std::mutex> l(_valueMutex);
return _phase1Power + _phase2Power + _phase3Power;
}

Expand All @@ -51,7 +72,7 @@ bool PowerMeterSerialSdm::isDataValid() const

void PowerMeterSerialSdm::doMqttPublish() const
{
std::lock_guard<std::mutex> l(_mutex);
std::lock_guard<std::mutex> l(_valueMutex);
mqttPublish("power1", _phase1Power);
mqttPublish("power2", _phase2Power);
mqttPublish("power3", _phase3Power);
Expand All @@ -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);
}
auto pInstance = static_cast<PowerMeterSerialSdm*>(context);
pInstance->pollingLoop();
pInstance->_taskDone = true;
vTaskDelete(nullptr);
}

{
std::lock_guard<std::mutex> l(_mutex);
_phase1Power = static_cast<float>(phase1Power);
_phase2Power = static_cast<float>(phase2Power);
_phase3Power = static_cast<float>(phase3Power);
_phase1Voltage = static_cast<float>(phase1Voltage);
_phase2Voltage = static_cast<float>(phase2Voltage);
_phase3Voltage = static_cast<float>(phase3Voltage);
_energyImport = static_cast<float>(energyImport);
_energyExport = static_cast<float>(energyExport);
void PowerMeterSerialSdm::pollingLoop()
{
std::unique_lock<std::mutex> 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<std::mutex> l(_valueMutex);
_phase1Power = static_cast<float>(phase1Power);
_phase2Power = static_cast<float>(phase2Power);
_phase3Power = static_cast<float>(phase3Power);
_phase1Voltage = static_cast<float>(phase1Voltage);
_phase2Voltage = static_cast<float>(phase2Voltage);
_phase3Voltage = static_cast<float>(phase3Voltage);
_energyImport = static_cast<float>(energyImport);
_energyExport = static_cast<float>(energyExport);
}

MessageOutput.printf("[PowerMeterSerialSdm] TotalPower: %5.2f\r\n", getPowerTotal());

gotUpdate();
}

gotUpdate();

MessageOutput.printf("[PowerMeterSerialSdm] TotalPower: %5.2f\r\n", getPowerTotal());

_lastPoll = millis();
}

0 comments on commit 347dd67

Please sign in to comment.