From cd339a3a147c054d40c471b9052058be5ea63ffa Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Mon, 18 Mar 2024 21:40:33 +0100 Subject: [PATCH] Feature: implement PowerMeter pin config for serial interfaces in your pin_mapping.json, add a powermeter object like this: [ { "name": "My Board", ... "powermeter": { "rx": , "tx": , "dere": }, ... } ] the SML power meter requires the rx pin to be set. the SDM power meter requires the rx and tx pins are set. the "dere" pin pin is optional and if set, this pin controls the driver enable and receiver enable pins of the RS485 transceiver. the SDM library handles this pin. closes #771. --- include/PinMapping.h | 7 ++- include/PowerMeter.h | 32 +++++------- src/PinMapping.cpp | 31 ++++++++++-- src/PowerMeter.cpp | 101 ++++++++++++++++++++++---------------- src/WebApi_powermeter.cpp | 2 +- 5 files changed, 104 insertions(+), 69 deletions(-) diff --git a/include/PinMapping.h b/include/PinMapping.h index 6197b5a4..f6db7a2d 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -38,6 +38,9 @@ struct PinMapping_t { uint8_t display_clk; uint8_t display_cs; uint8_t display_reset; + int8_t led[PINMAPPING_LED_COUNT]; + + // OpenDTU-OnBattery-specific pins below int8_t victron_tx; int8_t victron_rx; int8_t victron_tx2; @@ -52,7 +55,9 @@ struct PinMapping_t { int8_t huawei_irq; int8_t huawei_cs; int8_t huawei_power; - int8_t led[PINMAPPING_LED_COUNT]; + int8_t powermeter_rx; + int8_t powermeter_tx; + int8_t powermeter_dere; }; class PinMappingClass { diff --git a/include/PowerMeter.h b/include/PowerMeter.h index 1ac6d65a..f2b2042c 100644 --- a/include/PowerMeter.h +++ b/include/PowerMeter.h @@ -10,18 +10,7 @@ #include "SDM.h" #include "sml.h" #include - -#ifndef SDM_RX_PIN -#define SDM_RX_PIN 13 -#endif - -#ifndef SDM_TX_PIN -#define SDM_TX_PIN 32 -#endif - -#ifndef SML_RX_PIN -#define SML_RX_PIN 35 -#endif +#include typedef struct { const unsigned char OBIS[6]; @@ -31,13 +20,13 @@ typedef struct { class PowerMeterClass { public: - enum SOURCE { - SOURCE_MQTT = 0, - SOURCE_SDM1PH = 1, - SOURCE_SDM3PH = 2, - SOURCE_HTTP = 3, - SOURCE_SML = 4, - SOURCE_SMAHM2 = 5 + enum class Source : unsigned { + MQTT = 0, + SDM1PH = 1, + SDM3PH = 2, + HTTP = 3, + SML = 4, + SMAHM2 = 5 }; void init(Scheduler& scheduler); float getPowerTotal(bool forceUpdate = true); @@ -50,7 +39,7 @@ private: void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); - Task _loopTask; + Task _loopTask; bool _verboseLogging = true; uint32_t _lastPowerMeterCheck; @@ -70,6 +59,9 @@ private: mutable std::mutex _mutex; + std::unique_ptr _upSdm = nullptr; + std::unique_ptr _upSmlSerial = nullptr; + void readPowerMeter(); bool smlReadLoop(); diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 39eeca13..7c8bec18 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -144,6 +144,18 @@ #define HUAWEI_PIN_POWER -1 #endif +#ifndef POWERMETER_PIN_RX +#define POWERMETER_PIN_RX -1 +#endif + +#ifndef POWERMETER_PIN_TX +#define POWERMETER_PIN_TX -1 +#endif + +#ifndef POWERMETER_PIN_DERE +#define POWERMETER_PIN_DERE -1 +#endif + PinMappingClass PinMapping; PinMappingClass::PinMappingClass() @@ -182,6 +194,10 @@ PinMappingClass::PinMappingClass() _pinMapping.display_cs = DISPLAY_CS; _pinMapping.display_reset = DISPLAY_RESET; + _pinMapping.led[0] = LED0; + _pinMapping.led[1] = LED1; + + // OpenDTU-OnBattery-specific pins below _pinMapping.victron_rx = VICTRON_PIN_RX; _pinMapping.victron_tx = VICTRON_PIN_TX; @@ -199,8 +215,10 @@ PinMappingClass::PinMappingClass() _pinMapping.huawei_cs = HUAWEI_PIN_CS; _pinMapping.huawei_irq = HUAWEI_PIN_IRQ; _pinMapping.huawei_power = HUAWEI_PIN_POWER; - _pinMapping.led[0] = LED0; - _pinMapping.led[1] = LED1; + + _pinMapping.powermeter_rx = POWERMETER_PIN_RX; + _pinMapping.powermeter_tx = POWERMETER_PIN_TX; + _pinMapping.powermeter_dere = POWERMETER_PIN_DERE; } PinMapping_t& PinMappingClass::get() @@ -260,6 +278,10 @@ bool PinMappingClass::init(const String& deviceMapping) _pinMapping.display_cs = doc[i]["display"]["cs"] | DISPLAY_CS; _pinMapping.display_reset = doc[i]["display"]["reset"] | DISPLAY_RESET; + _pinMapping.led[0] = doc[i]["led"]["led0"] | LED0; + _pinMapping.led[1] = doc[i]["led"]["led1"] | LED1; + + // OpenDTU-OnBattery-specific pins below _pinMapping.victron_rx = doc[i]["victron"]["rx"] | VICTRON_PIN_RX; _pinMapping.victron_tx = doc[i]["victron"]["tx"] | VICTRON_PIN_TX; _pinMapping.victron_rx2 = doc[i]["victron"]["rx2"] | VICTRON_PIN_RX; @@ -277,8 +299,9 @@ bool PinMappingClass::init(const String& deviceMapping) _pinMapping.huawei_cs = doc[i]["huawei"]["cs"] | HUAWEI_PIN_CS; _pinMapping.huawei_power = doc[i]["huawei"]["power"] | HUAWEI_PIN_POWER; - _pinMapping.led[0] = doc[i]["led"]["led0"] | LED0; - _pinMapping.led[1] = doc[i]["led"]["led1"] | LED1; + _pinMapping.powermeter_rx = doc[i]["powermeter"]["rx"] | POWERMETER_PIN_RX; + _pinMapping.powermeter_tx = doc[i]["powermeter"]["tx"] | POWERMETER_PIN_TX; + _pinMapping.powermeter_dere = doc[i]["powermeter"]["dere"] | POWERMETER_PIN_DERE; return true; } diff --git a/src/PowerMeter.cpp b/src/PowerMeter.cpp index bd1f3b60..72526b7d 100644 --- a/src/PowerMeter.cpp +++ b/src/PowerMeter.cpp @@ -4,21 +4,16 @@ */ #include "PowerMeter.h" #include "Configuration.h" +#include "PinMapping.h" #include "HttpPowerMeter.h" #include "MqttSettings.h" #include "NetworkSettings.h" -#include "SDM.h" #include "MessageOutput.h" #include -#include #include PowerMeterClass PowerMeter; -SDM sdm(Serial2, 9600, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN); - -SoftwareSerial inputSerial; - void PowerMeterClass::init(Scheduler& scheduler) { scheduler.addTask(_loopTask); @@ -38,8 +33,12 @@ void PowerMeterClass::init(Scheduler& scheduler) return; } - switch(config.PowerMeter.Source) { - case SOURCE_MQTT: { + const PinMapping_t& pin = PinMapping.get(); + MessageOutput.printf("[PowerMeter] rx = %d, tx = %d, dere = %d\r\n", + pin.powermeter_rx, pin.powermeter_tx, pin.powermeter_dere); + + switch(static_cast(config.PowerMeter.Source)) { + case Source::MQTT: { auto subscribe = [this](char const* topic, float* target) { if (strlen(topic) == 0) { return; } MqttSettings.subscribe(topic, 0, @@ -57,24 +56,37 @@ void PowerMeterClass::init(Scheduler& scheduler) break; } - case SOURCE_SDM1PH: - case SOURCE_SDM3PH: - sdm.begin(); + case Source::SDM1PH: + case Source::SDM3PH: + if (pin.powermeter_rx < 0 || pin.powermeter_tx < 0) { + MessageOutput.println("[PowerMeter] invalid pin config for SDM power meter (RX and TX pins must be defined)"); + return; + } + + _upSdm = std::make_unique(Serial2, 9600, pin.powermeter_dere, + SERIAL_8N1, pin.powermeter_rx, pin.powermeter_tx); + _upSdm->begin(); break; - case SOURCE_HTTP: + case Source::HTTP: HttpPowerMeter.init(); break; - case SOURCE_SML: - pinMode(SML_RX_PIN, INPUT); - inputSerial.begin(9600, SWSERIAL_8N1, SML_RX_PIN, -1, false, 128, 95); - inputSerial.enableRx(true); - inputSerial.enableTx(false); - inputSerial.flush(); + case Source::SML: + if (pin.powermeter_rx < 0) { + MessageOutput.println("[PowerMeter] invalid pin config for SML power meter (RX pin must be defined)"); + return; + } + + pinMode(pin.powermeter_rx, INPUT); + _upSmlSerial = std::make_unique(); + _upSmlSerial->begin(9600, SWSERIAL_8N1, pin.powermeter_rx, -1, false, 128, 95); + _upSmlSerial->enableRx(true); + _upSmlSerial->enableTx(false); + _upSmlSerial->flush(); break; - case SOURCE_SMAHM2: + case Source::SMAHM2: SMA_HM.init(scheduler, config.PowerMeter.VerboseLogging); break; } @@ -150,12 +162,10 @@ void PowerMeterClass::loop() if (!config.PowerMeter.Enabled) { return; } - if (config.PowerMeter.Source == SOURCE_SML) { - if (!smlReadLoop()) { - return; - } else { - _lastPowerMeterUpdate = millis(); - } + if (static_cast(config.PowerMeter.Source) == Source::SML && + nullptr != _upSmlSerial) { + if (!smlReadLoop()) { return; } + _lastPowerMeterUpdate = millis(); } if ((millis() - _lastPowerMeterCheck) < (config.PowerMeter.Interval * 1000)) { @@ -176,14 +186,17 @@ void PowerMeterClass::readPowerMeter() CONFIG_T& config = Configuration.get(); uint8_t _address = config.PowerMeter.SdmAddress; + Source configuredSource = static_cast(config.PowerMeter.Source); + + if (configuredSource == Source::SDM1PH) { + if (!_upSdm) { return; } - if (config.PowerMeter.Source == SOURCE_SDM1PH) { // this takes a "very long" time as each readVal() is a synchronous // exchange of serial messages. cache the values and write later. - auto phase1Power = sdm.readVal(SDM_PHASE_1_POWER, _address); - auto phase1Voltage = sdm.readVal(SDM_PHASE_1_VOLTAGE, _address); - auto energyImport = sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address); - auto energyExport = sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address); + auto phase1Power = _upSdm->readVal(SDM_PHASE_1_POWER, _address); + auto phase1Voltage = _upSdm->readVal(SDM_PHASE_1_VOLTAGE, _address); + auto energyImport = _upSdm->readVal(SDM_IMPORT_ACTIVE_ENERGY, _address); + auto energyExport = _upSdm->readVal(SDM_EXPORT_ACTIVE_ENERGY, _address); std::lock_guard l(_mutex); _powerMeter1Power = static_cast(phase1Power); @@ -196,17 +209,19 @@ void PowerMeterClass::readPowerMeter() _powerMeterExport = static_cast(energyExport); _lastPowerMeterUpdate = millis(); } - else if (config.PowerMeter.Source == SOURCE_SDM3PH) { + else if (configuredSource == Source::SDM3PH) { + if (!_upSdm) { return; } + // this takes a "very long" time as each readVal() is a synchronous // exchange of serial messages. cache the values and write later. - auto phase1Power = sdm.readVal(SDM_PHASE_1_POWER, _address); - auto phase2Power = sdm.readVal(SDM_PHASE_2_POWER, _address); - auto phase3Power = sdm.readVal(SDM_PHASE_3_POWER, _address); - auto phase1Voltage = sdm.readVal(SDM_PHASE_1_VOLTAGE, _address); - auto phase2Voltage = sdm.readVal(SDM_PHASE_2_VOLTAGE, _address); - auto phase3Voltage = sdm.readVal(SDM_PHASE_3_VOLTAGE, _address); - auto energyImport = sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address); - auto energyExport = sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address); + auto phase1Power = _upSdm->readVal(SDM_PHASE_1_POWER, _address); + auto phase2Power = _upSdm->readVal(SDM_PHASE_2_POWER, _address); + auto phase3Power = _upSdm->readVal(SDM_PHASE_3_POWER, _address); + auto phase1Voltage = _upSdm->readVal(SDM_PHASE_1_VOLTAGE, _address); + auto phase2Voltage = _upSdm->readVal(SDM_PHASE_2_VOLTAGE, _address); + auto phase3Voltage = _upSdm->readVal(SDM_PHASE_3_VOLTAGE, _address); + auto energyImport = _upSdm->readVal(SDM_IMPORT_ACTIVE_ENERGY, _address); + auto energyExport = _upSdm->readVal(SDM_EXPORT_ACTIVE_ENERGY, _address); std::lock_guard l(_mutex); _powerMeter1Power = static_cast(phase1Power); @@ -219,7 +234,7 @@ void PowerMeterClass::readPowerMeter() _powerMeterExport = static_cast(energyExport); _lastPowerMeterUpdate = millis(); } - else if (config.PowerMeter.Source == SOURCE_HTTP) { + else if (configuredSource == Source::HTTP) { if (HttpPowerMeter.updateValues()) { std::lock_guard l(_mutex); _powerMeter1Power = HttpPowerMeter.getPower(1); @@ -228,7 +243,7 @@ void PowerMeterClass::readPowerMeter() _lastPowerMeterUpdate = millis(); } } - else if (config.PowerMeter.Source == SOURCE_SMAHM2) { + else if (configuredSource == Source::SMAHM2) { std::lock_guard l(_mutex); _powerMeter1Power = SMA_HM.getPowerL1(); _powerMeter2Power = SMA_HM.getPowerL2(); @@ -239,9 +254,9 @@ void PowerMeterClass::readPowerMeter() bool PowerMeterClass::smlReadLoop() { - while (inputSerial.available()) { + while (_upSmlSerial->available()) { double readVal = 0; - unsigned char smlCurrentChar = inputSerial.read(); + unsigned char smlCurrentChar = _upSmlSerial->read(); sml_states_t smlCurrentState = smlState(smlCurrentChar); if (smlCurrentState == SML_LISTEND) { for (auto& handler: smlHandlerList) { diff --git a/src/WebApi_powermeter.cpp b/src/WebApi_powermeter.cpp index 7340df46..137168ba 100644 --- a/src/WebApi_powermeter.cpp +++ b/src/WebApi_powermeter.cpp @@ -118,7 +118,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) return; } - if (root["source"].as() == PowerMeter.SOURCE_HTTP) { + if (static_cast(root["source"].as()) == PowerMeterClass::Source::HTTP) { JsonArray http_phases = root["http_phases"]; for (uint8_t i = 0; i < http_phases.size(); i++) { JsonObject phase = http_phases[i].as();