diff --git a/include/PinMapping.h b/include/PinMapping.h index 6197b5a45..f6db7a2da 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 1ac6d65a1..f2b2042c6 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 @@ class PowerMeterClass { 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 @@ class PowerMeterClass { 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 39eeca13b..7c8bec186 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 bd1f3b607..72526b7d7 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 7340df464..137168baf 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();