diff --git a/README.md b/README.md index 887e5ab37..eae353357 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,12 @@ Topics for 3 phases of a power meter is configurable. Given is an example for th | huawei/output_temp | R | Output air temperature | °C | | huawei/efficiency | R | Efficiency | Percentage | +## Power Limiter topics +| Topic | R / W | Description | Value / Unit | +| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- | +| powerlimiter/cmd/disable | W | Power Limiter disable override for external PL control | 0 / 1 | +| powerlimiter/status/disabled | R | Power Limiter disable override status | 0 / 1 | + ## Currently supported Inverters * Hoymiles HM-300 * Hoymiles HM-350 diff --git a/include/MqttHandlePowerLimiter.h b/include/MqttHandlePowerLimiter.h new file mode 100644 index 000000000..82d736ea6 --- /dev/null +++ b/include/MqttHandlePowerLimiter.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "Configuration.h" +#include + +class MqttHandlePowerLimiterClass { +public: + void init(); + void loop(); + +private: + void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); + + uint32_t _lastPublishStats; + uint32_t _lastPublish; + +}; + +extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter; \ No newline at end of file diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index 96baaf7c1..6e9357a1f 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -26,6 +26,8 @@ class PowerLimiterClass { void loop(); plStates getPowerLimiterState(); int32_t getLastRequestedPowewrLimit(); + void setDisable(bool disable); + bool getDisable(); private: uint32_t _lastCommandSent = 0; @@ -33,6 +35,7 @@ class PowerLimiterClass { int32_t _lastRequestedPowerLimit = 0; uint32_t _lastLimitSetTime = 0; plStates _plState = STATE_DISCOVER; + bool _disabled = false; float _powerMeter1Power; float _powerMeter2Power; diff --git a/src/MqttHandlePowerLimiter.cpp b/src/MqttHandlePowerLimiter.cpp new file mode 100644 index 000000000..2830d420d --- /dev/null +++ b/src/MqttHandlePowerLimiter.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Thomas Basler, Malte Schmidt and others + */ +#include "MessageOutput.h" +#include "MqttSettings.h" +#include "MqttHandlePowerLimiter.h" +#include "PowerLimiter.h" +#include + +#define TOPIC_SUB_POWER_LIMITER "disable" + +MqttHandlePowerLimiterClass MqttHandlePowerLimiter; + +void MqttHandlePowerLimiterClass::init() +{ + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + using std::placeholders::_4; + using std::placeholders::_5; + using std::placeholders::_6; + + String topic = MqttSettings.getPrefix(); + MqttSettings.subscribe(String(topic + "powerlimiter/cmd/" + TOPIC_SUB_POWER_LIMITER).c_str(), 0, std::bind(&MqttHandlePowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); + + _lastPublish = millis(); + +} + + +void MqttHandlePowerLimiterClass::loop() +{ + if (!MqttSettings.getConnected() ) { + return; + } + + const CONFIG_T& config = Configuration.get(); + + if ((millis() - _lastPublish) > (config.Mqtt_PublishInterval * 1000) ) { + MqttSettings.publish("powerlimiter/status/disabled", String(PowerLimiter.getDisable())); + + yield(); + _lastPublish = millis(); + } +} + + +void MqttHandlePowerLimiterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) +{ + const CONFIG_T& config = Configuration.get(); + + char token_topic[MQTT_MAX_TOPIC_STRLEN + 40]; // respect all subtopics + strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char* + + char* setting; + char* rest = &token_topic[strlen(config.Mqtt_Topic)]; + + strtok_r(rest, "/", &rest); // Remove "powerlimiter" + strtok_r(rest, "/", &rest); // Remove "cmd" + + setting = strtok_r(rest, "/", &rest); + + if (setting == NULL) { + return; + } + + char* strlimit = new char[len + 1]; + memcpy(strlimit, payload, len); + strlimit[len] = '\0'; + float payload_val = strtof(strlimit, NULL); + delete[] strlimit; + + if (!strcmp(setting, TOPIC_SUB_POWER_LIMITER)) { + MessageOutput.printf("Disable power limter: %f A\r\n", payload_val); + if(payload_val == 1) { + PowerLimiter.setDisable(true); + } + if(payload_val == 0) { + PowerLimiter.setDisable(false); + } + } +} \ No newline at end of file diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 214df5df9..9bd91c4d4 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -24,11 +24,13 @@ void PowerLimiterClass::loop() CONFIG_T& config = Configuration.get(); if (!config.PowerLimiter_Enabled + || _disabled || !config.PowerMeter_Enabled || !Hoymiles.getRadio()->isIdle() || (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000) || (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) { - if (!config.PowerLimiter_Enabled) + if (!config.PowerLimiter_Enabled + || _disabled) _plState = STATE_DISCOVER; // ensure STATE_DISCOVER is set, if PowerLimiter will be enabled. return; } @@ -150,6 +152,14 @@ int32_t PowerLimiterClass::getLastRequestedPowewrLimit() { return _lastRequestedPowerLimit; } +bool PowerLimiterClass::getDisable() { + return _disabled; +} + +void PowerLimiterClass::setDisable(bool disable) { + _disabled = disable; +} + bool PowerLimiterClass::canUseDirectSolarPower() { CONFIG_T& config = Configuration.get();