Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set powerlimiter thresholds via MQTT #677

Merged
merged 2 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions include/MqttHandlePowerLimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@ class MqttHandlePowerLimiterClass {

private:
void loop();
void onCmdMode(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);

enum class MqttPowerLimiterCommand : unsigned {
Mode,
BatterySoCStartThreshold,
BatterySoCStopThreshold,
FullSolarPassthroughSoC,
VoltageStartThreshold,
VoltageStopThreshold,
FullSolarPassThroughStartVoltage,
FullSolarPassThroughStopVoltage
};

void onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);

Task _loopTask;

Expand All @@ -28,4 +40,4 @@ class MqttHandlePowerLimiterClass {
std::deque<std::function<void()>> _mqttCallbacks;
};

extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
26 changes: 26 additions & 0 deletions include/MqttHandlePowerLimiterHass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ArduinoJson.h>
#include <TaskSchedulerDeclarations.h>

class MqttHandlePowerLimiterHassClass {
public:
void init(Scheduler& scheduler);
void publishConfig();
void forceUpdate();

private:
void loop();
void publish(const String& subtopic, const String& payload);
void publishNumber(const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min, const int16_t max);
void publishSelect(const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic);
void createDeviceInfo(JsonObject& object);

Task _loopTask;

bool _wasConnected = false;
bool _updateForced = false;
};

extern MqttHandlePowerLimiterHassClass MqttHandlePowerLimiterHass;
139 changes: 109 additions & 30 deletions src/MqttHandlePowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,25 @@ void MqttHandlePowerLimiterClass::init(Scheduler& scheduler)
using std::placeholders::_5;
using std::placeholders::_6;

String topic = MqttSettings.getPrefix() + "powerlimiter/cmd/mode";
MqttSettings.subscribe(topic.c_str(), 0, std::bind(&MqttHandlePowerLimiterClass::onCmdMode, this, _1, _2, _3, _4, _5, _6));
String const& prefix = MqttSettings.getPrefix();

auto subscribe = [&prefix, this](char const* subTopic, MqttPowerLimiterCommand command) {
String fullTopic(prefix + "powerlimiter/cmd/" + subTopic);
MqttSettings.subscribe(fullTopic.c_str(), 0,
std::bind(&MqttHandlePowerLimiterClass::onMqttCmd, this, command,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5, std::placeholders::_6));
};

subscribe("threshold/soc/start", MqttPowerLimiterCommand::BatterySoCStartThreshold);
subscribe("threshold/soc/stop", MqttPowerLimiterCommand::BatterySoCStopThreshold);
subscribe("threshold/soc/full_solar_passthrough", MqttPowerLimiterCommand::FullSolarPassthroughSoC);
subscribe("threshold/voltage/start", MqttPowerLimiterCommand::VoltageStartThreshold);
subscribe("threshold/voltage/stop", MqttPowerLimiterCommand::VoltageStopThreshold);
subscribe("threshold/voltage/full_solar_passthrough_start", MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage);
subscribe("threshold/voltage/full_solar_passthrough_stop", MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage);
subscribe("mode", MqttPowerLimiterCommand::Mode);

_lastPublish = millis();
}
Expand All @@ -50,51 +67,113 @@ void MqttHandlePowerLimiterClass::loop()

if (!MqttSettings.getConnected() ) { return; }

if ((millis() - _lastPublish) > (config.Mqtt.PublishInterval * 1000) ) {
auto val = static_cast<unsigned>(PowerLimiter.getMode());
MqttSettings.publish("powerlimiter/status/mode", String(val));
if ((millis() - _lastPublish) < (config.Mqtt.PublishInterval * 1000)) {
return;
}

_lastPublish = millis();

auto val = static_cast<unsigned>(PowerLimiter.getMode());
MqttSettings.publish("powerlimiter/status/mode", String(val));

yield();
_lastPublish = millis();
// no thresholds are relevant for setups without a battery
if (config.PowerLimiter.IsInverterSolarPowered) { return; }

MqttSettings.publish("powerlimiter/status/threshold/voltage/start", String(config.PowerLimiter.VoltageStartThreshold));
MqttSettings.publish("powerlimiter/status/threshold/voltage/stop", String(config.PowerLimiter.VoltageStopThreshold));

if (config.Vedirect.Enabled) {
MqttSettings.publish("powerlimiter/status/threshold/voltage/full_solar_passthrough_start", String(config.PowerLimiter.FullSolarPassThroughStartVoltage));
MqttSettings.publish("powerlimiter/status/threshold/voltage/full_solar_passthrough_stop", String(config.PowerLimiter.FullSolarPassThroughStopVoltage));
}
}

if (!config.Battery.Enabled || config.PowerLimiter.IgnoreSoc) { return; }

MqttSettings.publish("powerlimiter/status/threshold/soc/start", String(config.PowerLimiter.BatterySocStartThreshold));
MqttSettings.publish("powerlimiter/status/threshold/soc/stop", String(config.PowerLimiter.BatterySocStopThreshold));

void MqttHandlePowerLimiterClass::onCmdMode(const espMqttClientTypes::MessageProperties& properties,
const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
if (config.Vedirect.Enabled) {
MqttSettings.publish("powerlimiter/status/threshold/soc/full_solar_passthrough", String(config.PowerLimiter.FullSolarPassThroughSoc));
}
}

void MqttHandlePowerLimiterClass::onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
{
CONFIG_T& config = Configuration.get();

std::string strValue(reinterpret_cast<const char*>(payload), len);
int intValue = -1;
float payload_val = -1;
try {
intValue = std::stoi(strValue);
payload_val = std::stof(strValue);
}
catch (std::invalid_argument const& e) {
MessageOutput.printf("PowerLimiter MQTT handler: cannot parse payload of topic '%s' as int: %s\r\n",
MessageOutput.printf("PowerLimiter MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
topic, strValue.c_str());
return;
}
const int intValue = static_cast<int>(payload_val);

std::lock_guard<std::mutex> mqttLock(_mqttMutex);

using Mode = PowerLimiterClass::Mode;
switch (static_cast<Mode>(intValue)) {
case Mode::UnconditionalFullSolarPassthrough:
MessageOutput.println("Power limiter unconditional full solar PT");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::UnconditionalFullSolarPassthrough));
switch (command) {
case MqttPowerLimiterCommand::Mode:
{
using Mode = PowerLimiterClass::Mode;
Mode mode = static_cast<Mode>(intValue);
if (mode == Mode::UnconditionalFullSolarPassthrough) {
MessageOutput.println("Power limiter unconditional full solar PT");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::UnconditionalFullSolarPassthrough));
} else if (mode == Mode::Disabled) {
MessageOutput.println("Power limiter disabled (override)");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Disabled));
} else if (mode == Mode::Normal) {
MessageOutput.println("Power limiter normal operation");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Normal));
} else {
MessageOutput.printf("PowerLimiter - unknown mode %d\r\n", intValue);
}
return;
}
case MqttPowerLimiterCommand::BatterySoCStartThreshold:
if (config.PowerLimiter.BatterySocStartThreshold == intValue) { return; }
MessageOutput.printf("Setting battery SoC start threshold to: %d %%\r\n", intValue);
config.PowerLimiter.BatterySocStartThreshold = intValue;
break;
case MqttPowerLimiterCommand::BatterySoCStopThreshold:
if (config.PowerLimiter.BatterySocStopThreshold == intValue) { return; }
MessageOutput.printf("Setting battery SoC stop threshold to: %d %%\r\n", intValue);
config.PowerLimiter.BatterySocStopThreshold = intValue;
break;
case Mode::Disabled:
MessageOutput.println("Power limiter disabled (override)");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Disabled));
case MqttPowerLimiterCommand::FullSolarPassthroughSoC:
if (config.PowerLimiter.FullSolarPassThroughSoc == intValue) { return; }
MessageOutput.printf("Setting full solar passthrough SoC to: %d %%\r\n", intValue);
config.PowerLimiter.FullSolarPassThroughSoc = intValue;
break;
case Mode::Normal:
MessageOutput.println("Power limiter normal operation");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Normal));
case MqttPowerLimiterCommand::VoltageStartThreshold:
if (config.PowerLimiter.VoltageStartThreshold == payload_val) { return; }
MessageOutput.printf("Setting voltage start threshold to: %.2f V\r\n", payload_val);
config.PowerLimiter.VoltageStartThreshold = payload_val;
break;
default:
MessageOutput.printf("PowerLimiter - unknown mode %d\r\n", intValue);
case MqttPowerLimiterCommand::VoltageStopThreshold:
if (config.PowerLimiter.VoltageStopThreshold == payload_val) { return; }
MessageOutput.printf("Setting voltage stop threshold to: %.2f V\r\n", payload_val);
config.PowerLimiter.VoltageStopThreshold = payload_val;
break;
case MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage:
if (config.PowerLimiter.FullSolarPassThroughStartVoltage == payload_val) { return; }
MessageOutput.printf("Setting full solar passthrough start voltage to: %.2f V\r\n", payload_val);
config.PowerLimiter.FullSolarPassThroughStartVoltage = payload_val;
break;
case MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage:
if (config.PowerLimiter.FullSolarPassThroughStopVoltage == payload_val) { return; }
MessageOutput.printf("Setting full solar passthrough stop voltage to: %.2f V\r\n", payload_val);
config.PowerLimiter.FullSolarPassThroughStopVoltage = payload_val;
break;
}
}

// not reached if the value did not change
Configuration.write();
}
Loading
Loading