Fix: inverter power limits precision
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 %
This commit is contained in:
parent
86a49a781a
commit
6ee6eaf0b2
@ -63,7 +63,7 @@ private:
|
|||||||
void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = "");
|
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 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 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);
|
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);
|
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
||||||
|
|||||||
@ -85,7 +85,7 @@ bool ActivePowerControlCommand::handleResponse(const fragment_t fragment[], cons
|
|||||||
|
|
||||||
float ActivePowerControlCommand::getLimit() const
|
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;
|
return l / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,8 +73,8 @@ void MqttHandleHassClass::publishConfig()
|
|||||||
publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1");
|
publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1");
|
||||||
publishInverterButton(inv, "Restart Inverter", "", "config", "restart", "cmd/restart", "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 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);
|
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 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);
|
publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
|
||||||
@ -215,7 +215,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
|
|||||||
void MqttHandleHassClass::publishInverterNumber(
|
void MqttHandleHassClass::publishInverterNumber(
|
||||||
std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category,
|
std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category,
|
||||||
const char* commandTopic, const char* stateTopic, const char* unitOfMeasure,
|
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();
|
const String serial = inv->serialString();
|
||||||
|
|
||||||
@ -243,6 +243,7 @@ void MqttHandleHassClass::publishInverterNumber(
|
|||||||
root["unit_of_meas"] = unitOfMeasure;
|
root["unit_of_meas"] = unitOfMeasure;
|
||||||
root["min"] = min;
|
root["min"] = min;
|
||||||
root["max"] = max;
|
root["max"] = max;
|
||||||
|
root["step"] = step;
|
||||||
|
|
||||||
createInverterInfo(root, inv);
|
createInverterInfo(root, inv);
|
||||||
|
|
||||||
|
|||||||
@ -183,7 +183,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
char* strlimit = new char[len + 1];
|
char* strlimit = new char[len + 1];
|
||||||
memcpy(strlimit, payload, len);
|
memcpy(strlimit, payload, len);
|
||||||
strlimit[len] = '\0';
|
strlimit[len] = '\0';
|
||||||
const int32_t payload_val = strtol(strlimit, NULL, 10);
|
const float payload_val = strtof(strlimit, NULL);
|
||||||
delete[] strlimit;
|
delete[] strlimit;
|
||||||
|
|
||||||
if (payload_val < 0) {
|
if (payload_val < 0) {
|
||||||
@ -193,17 +193,17 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
||||||
// Set inverter limit relative persistent
|
// 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);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativPersistent);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
||||||
// Set inverter limit absolute persistent
|
// 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);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutPersistent);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
||||||
// Set inverter limit relative non persistent
|
// 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) {
|
if (!properties.retain) {
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativNonPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativNonPersistent);
|
||||||
} else {
|
} else {
|
||||||
@ -212,7 +212,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
||||||
// Set inverter limit absolute non persistent
|
// 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) {
|
if (!properties.retain) {
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
||||||
} else {
|
} else {
|
||||||
@ -221,8 +221,8 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
||||||
// Turn inverter on or off
|
// Turn inverter on or off
|
||||||
MessageOutput.printf("Set inverter power to: %d\r\n", payload_val);
|
MessageOutput.printf("Set inverter power to: %d\r\n", static_cast<int32_t>(payload_val));
|
||||||
inv->sendPowerControlRequest(payload_val > 0);
|
inv->sendPowerControlRequest(static_cast<int32_t>(payload_val) > 0);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
||||||
// Restart inverter
|
// Restart inverter
|
||||||
@ -230,7 +230,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
if (!properties.retain && payload_val == 1) {
|
if (!properties.retain && payload_val == 1) {
|
||||||
inv->sendRestartControlRequest();
|
inv->sendRestartControlRequest();
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.println("Ignored because retained");
|
MessageOutput.println("Ignored because retained or numeric value not '1'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,7 +83,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
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["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
||||||
retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
|
retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
|
||||||
@ -102,7 +102,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t limit = root["limit_value"].as<uint16_t>();
|
float limit = root["limit_value"].as<float>();
|
||||||
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
||||||
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|||||||
@ -35,12 +35,18 @@ LOCALES.forEach((locale) => {
|
|||||||
decimalNoDigits: {
|
decimalNoDigits: {
|
||||||
style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0
|
style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0
|
||||||
},
|
},
|
||||||
|
decimalOneDigit: {
|
||||||
|
style: 'decimal', minimumFractionDigits: 1, maximumFractionDigits: 1
|
||||||
|
},
|
||||||
decimalTwoDigits: {
|
decimalTwoDigits: {
|
||||||
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||||
},
|
},
|
||||||
percent: {
|
percent: {
|
||||||
style: 'percent',
|
style: 'percent',
|
||||||
},
|
},
|
||||||
|
percentOneDigit: {
|
||||||
|
style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1
|
||||||
|
},
|
||||||
byte: {
|
byte: {
|
||||||
style: 'unit', unit: 'byte',
|
style: 'unit', unit: 'byte',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('home.CurrentLimit') }}<template v-if="inverter.limit_absolute > -1"> {{
|
{{ $t('home.CurrentLimit') }}<template v-if="inverter.limit_absolute > -1"> {{
|
||||||
$n(inverter.limit_absolute, 'decimalNoDigits')
|
$n(inverter.limit_absolute, 'decimalNoDigits')
|
||||||
}} W | </template>{{ $n(inverter.limit_relative / 100, 'percent') }}
|
}} W | </template>{{ $n(inverter.limit_relative / 100, 'percentOneDigit') }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('home.DataAge') }} {{ $t('home.Seconds', {'val': $n(inverter.data_age) }) }}
|
{{ $t('home.DataAge') }} {{ $t('home.Seconds', {'val': $n(inverter.data_age) }) }}
|
||||||
@ -416,13 +416,13 @@ export default defineComponent({
|
|||||||
currentLimitAbsolute(): string {
|
currentLimitAbsolute(): string {
|
||||||
if (this.currentLimitList.max_power > 0) {
|
if (this.currentLimitList.max_power > 0) {
|
||||||
return this.$n(this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100,
|
return this.$n(this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100,
|
||||||
'decimalTwoDigits');
|
'decimalNoDigits');
|
||||||
}
|
}
|
||||||
return "0";
|
return "0";
|
||||||
},
|
},
|
||||||
currentLimitRelative(): string {
|
currentLimitRelative(): string {
|
||||||
return this.$n(this.currentLimitList.limit_relative,
|
return this.$n(this.currentLimitList.limit_relative,
|
||||||
'decimalTwoDigits');
|
'decimalOneDigit');
|
||||||
},
|
},
|
||||||
inverterData(): Inverter[] {
|
inverterData(): Inverter[] {
|
||||||
return this.liveData.inverters.slice().sort((a: Inverter, b: Inverter) => {
|
return this.liveData.inverters.slice().sort((a: Inverter, b: Inverter) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user