Skip to content

Commit

Permalink
Fix: inverter power limits precision
Browse files Browse the repository at this point in the history
Hoymiles inverters allow setting relative limits with a precision of 0.1 %.
this changeset allows to utilize this precision.

* preserve accuracy when decoding power limit
* Web API: process floating point limits
* MQTT: process floating point limits
* use appropriate accuracy for limits in web UI
* HASS: step for relative inverter limit is 0.1 %
  • Loading branch information
schlimmchen authored Jul 23, 2024
1 parent 86a49a7 commit 6ee6eaf
Show file tree
Hide file tree
Showing 7 changed files with 25 additions and 18 deletions.
2 changes: 1 addition & 1 deletion include/MqttHandleHass.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class MqttHandleHassClass {
void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = "");
void publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100);
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100, float step = 1.0);
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);

static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
Expand Down
2 changes: 1 addition & 1 deletion lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ bool ActivePowerControlCommand::handleResponse(const fragment_t fragment[], cons

float ActivePowerControlCommand::getLimit() const
{
const uint16_t l = (((uint16_t)_payload[12] << 8) | _payload[13]);
const float l = (((uint16_t)_payload[12] << 8) | _payload[13]);
return l / 10;
}

Expand Down
7 changes: 4 additions & 3 deletions src/MqttHandleHass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ void MqttHandleHassClass::publishConfig()
publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1");
publishInverterButton(inv, "Restart Inverter", "", "config", "restart", "cmd/restart", "1");

publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%", 0, 100);
publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%", 0, 100);
publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%", 0, 100, 0.1);
publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%", 0, 100, 0.1);

publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
Expand Down Expand Up @@ -215,7 +215,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
void MqttHandleHassClass::publishInverterNumber(
std::shared_ptr<InverterAbstract> inv, 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)
const int16_t min, const int16_t max, float step)
{
const String serial = inv->serialString();

Expand Down Expand Up @@ -243,6 +243,7 @@ void MqttHandleHassClass::publishInverterNumber(
root["unit_of_meas"] = unitOfMeasure;
root["min"] = min;
root["max"] = max;
root["step"] = step;

createInverterInfo(root, inv);

Expand Down
16 changes: 8 additions & 8 deletions src/MqttHandleInverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
char* strlimit = new char[len + 1];
memcpy(strlimit, payload, len);
strlimit[len] = '\0';
const int32_t payload_val = strtol(strlimit, NULL, 10);
const float payload_val = strtof(strlimit, NULL);
delete[] strlimit;

if (payload_val < 0) {
Expand All @@ -193,17 +193,17 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro

if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
// Set inverter limit relative persistent
MessageOutput.printf("Limit Persistent: %d %%\r\n", payload_val);
MessageOutput.printf("Limit Persistent: %.1f %%\r\n", payload_val);
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativPersistent);

} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
// Set inverter limit absolute persistent
MessageOutput.printf("Limit Persistent: %d W\r\n", payload_val);
MessageOutput.printf("Limit Persistent: %.1f W\r\n", payload_val);
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutPersistent);

} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
// Set inverter limit relative non persistent
MessageOutput.printf("Limit Non-Persistent: %d %%\r\n", payload_val);
MessageOutput.printf("Limit Non-Persistent: %.1f %%\r\n", payload_val);
if (!properties.retain) {
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativNonPersistent);
} else {
Expand All @@ -212,7 +212,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro

} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
// Set inverter limit absolute non persistent
MessageOutput.printf("Limit Non-Persistent: %d W\r\n", payload_val);
MessageOutput.printf("Limit Non-Persistent: %.1f W\r\n", payload_val);
if (!properties.retain) {
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutNonPersistent);
} else {
Expand All @@ -221,16 +221,16 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro

} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
// Turn inverter on or off
MessageOutput.printf("Set inverter power to: %d\r\n", payload_val);
inv->sendPowerControlRequest(payload_val > 0);
MessageOutput.printf("Set inverter power to: %d\r\n", static_cast<int32_t>(payload_val));
inv->sendPowerControlRequest(static_cast<int32_t>(payload_val) > 0);

} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
// Restart inverter
MessageOutput.printf("Restart inverter\r\n");
if (!properties.retain && payload_val == 1) {
inv->sendRestartControlRequest();
} else {
MessageOutput.println("Ignored because retained");
MessageOutput.println("Ignored because retained or numeric value not '1'");
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/WebApi_limit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
return;
}

if (root["limit_value"].as<uint16_t>() > MAX_INVERTER_LIMIT) {
if (root["limit_value"].as<float>() > MAX_INVERTER_LIMIT) {
retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!";
retMsg["code"] = WebApiError::LimitInvalidLimit;
retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
Expand All @@ -102,7 +102,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
return;
}

uint16_t limit = root["limit_value"].as<uint16_t>();
float limit = root["limit_value"].as<float>();
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();

auto inv = Hoymiles.getInverterBySerial(serial);
Expand Down
6 changes: 6 additions & 0 deletions webapp/src/locales/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ LOCALES.forEach((locale) => {
decimalNoDigits: {
style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0
},
decimalOneDigit: {
style: 'decimal', minimumFractionDigits: 1, maximumFractionDigits: 1
},
decimalTwoDigits: {
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
},
percent: {
style: 'percent',
},
percentOneDigit: {
style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1
},
byte: {
style: 'unit', unit: 'byte',
},
Expand Down
6 changes: 3 additions & 3 deletions webapp/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<div style="padding-right: 2em;">
{{ $t('home.CurrentLimit') }}<template v-if="inverter.limit_absolute > -1"> {{
$n(inverter.limit_absolute, 'decimalNoDigits')
}} W | </template>{{ $n(inverter.limit_relative / 100, 'percent') }}
}} W | </template>{{ $n(inverter.limit_relative / 100, 'percentOneDigit') }}
</div>
<div style="padding-right: 2em;">
{{ $t('home.DataAge') }} {{ $t('home.Seconds', {'val': $n(inverter.data_age) }) }}
Expand Down Expand Up @@ -416,13 +416,13 @@ export default defineComponent({
currentLimitAbsolute(): string {
if (this.currentLimitList.max_power > 0) {
return this.$n(this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100,
'decimalTwoDigits');
'decimalNoDigits');
}
return "0";
},
currentLimitRelative(): string {
return this.$n(this.currentLimitList.limit_relative,
'decimalTwoDigits');
'decimalOneDigit');
},
inverterData(): Inverter[] {
return this.liveData.inverters.slice().sort((a: Inverter, b: Inverter) => {
Expand Down

0 comments on commit 6ee6eaf

Please sign in to comment.