when performing a test request using the web UI, we need to init() the respective power meter, but we do not want to start the polling task. hence we move initialization of the polling task to the poll() function. it will return if the task is setup already, otherwise setup the task.
123 lines
3.4 KiB
C++
123 lines
3.4 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
#include "PowerMeterSerialSml.h"
|
|
#include "PinMapping.h"
|
|
#include "MessageOutput.h"
|
|
|
|
bool PowerMeterSerialSml::init()
|
|
{
|
|
const PinMapping_t& pin = PinMapping.get();
|
|
|
|
MessageOutput.printf("[PowerMeterSerialSml] rx = %d\r\n", pin.powermeter_rx);
|
|
|
|
if (pin.powermeter_rx < 0) {
|
|
MessageOutput.println("[PowerMeterSerialSml] invalid pin config "
|
|
"for serial SML power meter (RX pin must be defined)");
|
|
return false;
|
|
}
|
|
|
|
pinMode(pin.powermeter_rx, INPUT);
|
|
_upSmlSerial = std::make_unique<SoftwareSerial>();
|
|
_upSmlSerial->begin(_baud, SWSERIAL_8N1, pin.powermeter_rx, -1/*tx pin*/,
|
|
false/*invert*/, _bufCapacity, _isrCapacity);
|
|
_upSmlSerial->enableRx(true);
|
|
_upSmlSerial->enableTx(false);
|
|
_upSmlSerial->flush();
|
|
|
|
return true;
|
|
}
|
|
|
|
void PowerMeterSerialSml::loop()
|
|
{
|
|
if (_taskHandle != nullptr) { return; }
|
|
|
|
std::unique_lock<std::mutex> lock(_pollingMutex);
|
|
_stopPolling = false;
|
|
lock.unlock();
|
|
|
|
uint32_t constexpr stackSize = 3072;
|
|
xTaskCreate(PowerMeterSerialSml::pollingLoopHelper, "PM:SML",
|
|
stackSize, this, 1/*prio*/, &_taskHandle);
|
|
}
|
|
|
|
PowerMeterSerialSml::~PowerMeterSerialSml()
|
|
{
|
|
_taskDone = false;
|
|
|
|
std::unique_lock<std::mutex> lock(_pollingMutex);
|
|
_stopPolling = true;
|
|
lock.unlock();
|
|
|
|
if (_taskHandle != nullptr) {
|
|
while (!_taskDone) { delay(10); }
|
|
_taskHandle = nullptr;
|
|
}
|
|
|
|
if (_upSmlSerial) {
|
|
_upSmlSerial->end();
|
|
_upSmlSerial = nullptr;
|
|
}
|
|
}
|
|
|
|
void PowerMeterSerialSml::pollingLoopHelper(void* context)
|
|
{
|
|
auto pInstance = static_cast<PowerMeterSerialSml*>(context);
|
|
pInstance->pollingLoop();
|
|
pInstance->_taskDone = true;
|
|
vTaskDelete(nullptr);
|
|
}
|
|
|
|
void PowerMeterSerialSml::pollingLoop()
|
|
{
|
|
int lastAvailable = 0;
|
|
uint32_t gapStartMillis = 0;
|
|
std::unique_lock<std::mutex> lock(_pollingMutex);
|
|
|
|
while (!_stopPolling) {
|
|
lock.unlock();
|
|
|
|
// calling available() will decode bytes into the receive buffer and
|
|
// hence free data from the ISR buffer, so we need to call this rather
|
|
// frequenly.
|
|
int nowAvailable = _upSmlSerial->available();
|
|
|
|
if (nowAvailable <= 0) {
|
|
// sleep, but at most until the software serial ISR
|
|
// buffer is potentially half full with transitions.
|
|
uint32_t constexpr delayMs = _isrCapacity * 1000 / _baud / 2;
|
|
|
|
delay(delayMs); // this yields so other tasks are scheduled
|
|
|
|
lock.lock();
|
|
continue;
|
|
}
|
|
|
|
// sleep more if new data arrived in the meantime. process data only
|
|
// once a SML datagram seems to be complete (no new data arrived while
|
|
// we slept). this seems to be important as using read() while more
|
|
// data arrives causes trouble (we are missing bytes).
|
|
if (nowAvailable > lastAvailable) {
|
|
lastAvailable = nowAvailable;
|
|
delay(10);
|
|
gapStartMillis = millis();
|
|
lock.lock();
|
|
continue;
|
|
}
|
|
|
|
if ((millis() - gapStartMillis) < _datagramGapMillis) {
|
|
delay(10);
|
|
lock.lock();
|
|
continue;
|
|
}
|
|
|
|
while (_upSmlSerial->available() > 0) {
|
|
processSmlByte(_upSmlSerial->read());
|
|
}
|
|
|
|
lastAvailable = 0;
|
|
|
|
PowerMeterSml::reset();
|
|
|
|
lock.lock();
|
|
}
|
|
}
|