make inverter and channel selectable

This commit is contained in:
helgeerbe 2023-02-23 21:46:59 +01:00
parent 73fe3bfb96
commit 56151b0d12
12 changed files with 221 additions and 159 deletions

View File

@ -106,6 +106,8 @@ struct CONFIG_T {
char PowerLimiter_MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1]; char PowerLimiter_MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1];
char PowerLimiter_MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1]; char PowerLimiter_MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1];
bool PowerLimiter_IsInverterBehindPowerMeter; bool PowerLimiter_IsInverterBehindPowerMeter;
uint8_t PowerLimiter_InverterId;
uint8_t PowerLimiter_InverterChannelId;
uint32_t PowerLimiter_LowerPowerLimit; uint32_t PowerLimiter_LowerPowerLimit;
uint32_t PowerLimiter_UpperPowerLimit; uint32_t PowerLimiter_UpperPowerLimit;
uint32_t PowerLimiter_BatterySocStartThreshold; uint32_t PowerLimiter_BatterySocStartThreshold;

View File

@ -96,6 +96,8 @@
#define POWERLIMITER_SOLAR_PASSTROUGH_ENABLED true #define POWERLIMITER_SOLAR_PASSTROUGH_ENABLED true
#define POWERLIMITER_INTERVAL 10 #define POWERLIMITER_INTERVAL 10
#define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true #define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true
#define POWERLIMITER_INVERTER_ID 0
#define POWERLIMITER_INVERTER_CHANNEL_ID 0
#define POWERLIMITER_LOWER_POWER_LIMIT 10 #define POWERLIMITER_LOWER_POWER_LIMIT 10
#define POWERLIMITER_UPPER_POWER_LIMIT 800 #define POWERLIMITER_UPPER_POWER_LIMIT 800
#define POWERLIMITER_BATTERY_SOC_START_THRESHOLD 80 #define POWERLIMITER_BATTERY_SOC_START_THRESHOLD 80

View File

@ -123,6 +123,8 @@ bool ConfigurationClass::write()
powerlimiter["mqtt_topic_powermeter_2"] = config.PowerLimiter_MqttTopicPowerMeter2; powerlimiter["mqtt_topic_powermeter_2"] = config.PowerLimiter_MqttTopicPowerMeter2;
powerlimiter["mqtt_topic_powermeter_3"] = config.PowerLimiter_MqttTopicPowerMeter3; powerlimiter["mqtt_topic_powermeter_3"] = config.PowerLimiter_MqttTopicPowerMeter3;
powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter; powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter;
powerlimiter["inverter_id"] = config.PowerLimiter_InverterId;
powerlimiter["inverter_channel_id"] = config.PowerLimiter_InverterChannelId;
powerlimiter["lower_power_limit"] = config.PowerLimiter_LowerPowerLimit; powerlimiter["lower_power_limit"] = config.PowerLimiter_LowerPowerLimit;
powerlimiter["upper_power_limit"] = config.PowerLimiter_UpperPowerLimit; powerlimiter["upper_power_limit"] = config.PowerLimiter_UpperPowerLimit;
powerlimiter["battery_soc_start_threshold"] = config.PowerLimiter_BatterySocStartThreshold; powerlimiter["battery_soc_start_threshold"] = config.PowerLimiter_BatterySocStartThreshold;
@ -284,6 +286,8 @@ bool ConfigurationClass::read()
strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, powerlimiter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter2)); strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, powerlimiter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter2));
strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, powerlimiter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter3)); strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, powerlimiter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter3));
config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER; config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER;
config.PowerLimiter_InverterId = powerlimiter["inverter_id"] | POWERLIMITER_INVERTER_ID;
config.PowerLimiter_InverterChannelId = powerlimiter["inverter_channel_id"] | POWERLIMITER_INVERTER_CHANNEL_ID;
config.PowerLimiter_LowerPowerLimit = powerlimiter["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT; config.PowerLimiter_LowerPowerLimit = powerlimiter["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT;
config.PowerLimiter_UpperPowerLimit = powerlimiter["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT; config.PowerLimiter_UpperPowerLimit = powerlimiter["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT;
config.PowerLimiter_BatterySocStartThreshold = powerlimiter["battery_soc_start_threshold"] | POWERLIMITER_BATTERY_SOC_START_THRESHOLD; config.PowerLimiter_BatterySocStartThreshold = powerlimiter["battery_soc_start_threshold"] | POWERLIMITER_BATTERY_SOC_START_THRESHOLD;

View File

@ -78,13 +78,13 @@ void PowerLimiterClass::loop()
_lastLoop = millis(); _lastLoop = millis();
std::shared_ptr<InverterAbstract> inverter = Hoymiles.getInverterByPos(1); // TODO(helgeerbe) make Inverter selectable std::shared_ptr<InverterAbstract> inverter = Hoymiles.getInverterByPos(config.PowerLimiter_InverterId);
if (inverter == nullptr || !inverter->isReachable()) { if (inverter == nullptr || !inverter->isReachable()) {
return; return;
} }
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, CH0, FLD_UDC); // TODO(helgeerbe) make channel selectable float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC);
if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000) { if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000) {
return; return;

View File

@ -40,6 +40,8 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
root[F("mqtt_topic_powermeter_2")] = config.PowerLimiter_MqttTopicPowerMeter2; root[F("mqtt_topic_powermeter_2")] = config.PowerLimiter_MqttTopicPowerMeter2;
root[F("mqtt_topic_powermeter_3")] = config.PowerLimiter_MqttTopicPowerMeter3; root[F("mqtt_topic_powermeter_3")] = config.PowerLimiter_MqttTopicPowerMeter3;
root[F("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter; root[F("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter;
root[F("inverter_id")] = config.PowerLimiter_InverterId;
root[F("inverter_channel_id")] = config.PowerLimiter_InverterChannelId;
root[F("lower_power_limit")] = config.PowerLimiter_LowerPowerLimit; root[F("lower_power_limit")] = config.PowerLimiter_LowerPowerLimit;
root[F("upper_power_limit")] = config.PowerLimiter_UpperPowerLimit; root[F("upper_power_limit")] = config.PowerLimiter_UpperPowerLimit;
root[F("battery_soc_start_threshold")] = config.PowerLimiter_BatterySocStartThreshold; root[F("battery_soc_start_threshold")] = config.PowerLimiter_BatterySocStartThreshold;
@ -97,7 +99,10 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
return; return;
} }
if (!(root.containsKey("enabled") && root.containsKey("lower_power_limit"))) { if (!(root.containsKey("enabled")
&& root.containsKey("lower_power_limit")
&& root.containsKey("inverter_id")
&& root.containsKey("inverter_channel_id"))) {
retMsg[F("message")] = F("Values are missing!"); retMsg[F("message")] = F("Values are missing!");
response->setLength(); response->setLength();
request->send(response); request->send(response);
@ -111,6 +116,8 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter2)); strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter2));
strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter3)); strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter3));
config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>(); config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>();
config.PowerLimiter_InverterId = root[F("inverter_id")].as<uint8_t>();
config.PowerLimiter_InverterChannelId = root[F("inverter_channel_id")].as<uint8_t>();
config.PowerLimiter_LowerPowerLimit = root[F("lower_power_limit")].as<uint32_t>(); config.PowerLimiter_LowerPowerLimit = root[F("lower_power_limit")].as<uint32_t>();
config.PowerLimiter_UpperPowerLimit = root[F("upper_power_limit")].as<uint32_t>(); config.PowerLimiter_UpperPowerLimit = root[F("upper_power_limit")].as<uint32_t>();
config.PowerLimiter_BatterySocStartThreshold = root[F("battery_soc_start_threshold")].as<float>(); config.PowerLimiter_BatterySocStartThreshold = root[F("battery_soc_start_threshold")].as<float>();

View File

@ -443,6 +443,12 @@
"UpdatesOnly": "Nur Änderungen senden:", "UpdatesOnly": "Nur Änderungen senden:",
"Save": "@:dtuadmin.Save" "Save": "@:dtuadmin.Save"
}, },
"powerlimiteradmin": {
"InverterId": "Wechselrichter ID",
"InverterIdHint": "Wähle den Wechselrichter an dem die Batterie hängt.",
"InverterChannelId": "Kanal ID",
"InverterChannelIdHint": "Wähle den Kanal an dem die Batterie hängt."
},
"batteryadmin": { "batteryadmin": {
"BatterySettings": "Batterie Einstellungen", "BatterySettings": "Batterie Einstellungen",
"BatteryConfiguration": "Batterie Konfiguration", "BatteryConfiguration": "Batterie Konfiguration",

View File

@ -450,6 +450,10 @@
"Enable": "Enable", "Enable": "Enable",
"EnableSolarPasstrough": "Enable Solar Passtrough", "EnableSolarPasstrough": "Enable Solar Passtrough",
"SolarpasstroughInfo": "When the sun is shining, this setting enables the sychronization of the inverter limit with the current solar power of the Victron MPPT charger. This optimizes battery degradation and loses.", "SolarpasstroughInfo": "When the sun is shining, this setting enables the sychronization of the inverter limit with the current solar power of the Victron MPPT charger. This optimizes battery degradation and loses.",
"InverterId": "Inverter ID",
"InverterIdHint": "Select proper inverter ID where battery is connected to.",
"InverterChannelId": "Channel ID",
"InverterChannelIdHint": "Select proper channel where battery is connected to.",
"LowerPowerLimit": "Lower power limit / continuous feed", "LowerPowerLimit": "Lower power limit / continuous feed",
"UpperPowerLimit": "Upper power limit", "UpperPowerLimit": "Upper power limit",
"PowerMeters": "Power meters - MQTT", "PowerMeters": "Power meters - MQTT",

View File

@ -5,6 +5,8 @@ export interface PowerLimiterConfig {
mqtt_topic_powermeter_2: string; mqtt_topic_powermeter_2: string;
mqtt_topic_powermeter_3: string; mqtt_topic_powermeter_3: string;
is_inverter_behind_powermeter: boolean; is_inverter_behind_powermeter: boolean;
inverterId: number;
inverterChannelId: number;
lower_power_limit: number; lower_power_limit: number;
upper_power_limit: number; upper_power_limit: number;
battery_soc_start_threshold: number; battery_soc_start_threshold: number;

View File

@ -5,33 +5,47 @@
</BootstrapAlert> </BootstrapAlert>
<form @submit="savePowerLimiterConfig"> <form @submit="savePowerLimiterConfig">
<div class="card"> <CardElement :text="$t('powerlimiteradmin.General')" textVariant="text-bg-primary">
<div class="card-header text-bg-primary">{{ $t('powerlimiteradmin.General') }}</div> <InputElement :label="$t('powerlimiteradmin.Enable')"
<div class="card-body"> v-model="powerLimiterConfigList.enabled"
type="checkbox" wide/>
<InputElement v-show="powerLimiterConfigList.enabled"
:label="$t('powerlimiteradmin.EnableSolarPasstrough')"
v-model="powerLimiterConfigList.solar_passtrough_enabled"
type="checkbox" wide/>
<div class="alert alert-secondary" v-show="powerLimiterConfigList.enabled" role="alert" v-html="$t('powerlimiteradmin.SolarpasstroughInfo')"></div>
<div class="row mb-3"> <div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputPowerlimiter">{{ $t('powerlimiteradmin.Enable') }}</label> <label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.InverterId') }}
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.InverterIdHint')" />
</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="form-check form-switch"> <select class="form-select" v-model="powerLimiterConfigList.inverterId">
<input class="form-check-input" type="checkbox" id="inputPowerlimiter" <option v-for="inverter in inverterList" :key="inverter.key" :value="inverter.key">
v-model="powerLimiterConfigList.enabled" /> {{ inverter.value }}
</div> </option>
</select>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<label class="col-sm-2 form-check-label" for="solarPasstroughEnabled">{{ $t('powerlimiteradmin.EnableSolarPasstrough') }}</label> <label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.InverterChannelId') }}
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.InverterChannelIdHint')" />
</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="form-check form-switch"> <select class="form-select" v-model="powerLimiterConfigList.inverterChannelId">
<input class="form-check-input" type="checkbox" id="solarPasstroughEnabled" <option v-for="inverterChannel in inverterChannelList" :key="inverterChannel.key" :value="inverterChannel.key">
v-model="powerLimiterConfigList.solar_passtrough_enabled" /> {{ inverterChannel.value }}
</div> </option>
</select>
</div> </div>
</div> </div>
<div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.SolarpasstroughInfo')"></div> <div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<div class="row mb-3">
<label for="inputLowerPowerLimit" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.LowerPowerLimit') }}:</label> <label for="inputLowerPowerLimit" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.LowerPowerLimit') }}:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group"> <div class="input-group">
@ -43,7 +57,7 @@
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="inputUpperPowerLimit" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.UpperPowerLimit') }}:</label> <label for="inputUpperPowerLimit" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.UpperPowerLimit') }}:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group"> <div class="input-group">
@ -54,12 +68,11 @@
</div> </div>
</div> </div>
</div> </div>
</div> </CardElement>
</div>
<div class="card mt-5"> <CardElement :text="$t('powerlimiteradmin.PowerMeters')" textVariant="text-bg-primary" add-space
<div class="card-header text-bg-primary">{{ $t('powerlimiteradmin.PowerMeters') }}</div> v-show="powerLimiterConfigList.enabled"
<div class="card-body"> >
<div class="row mb-3"> <div class="row mb-3">
<label for="inputMqttTopicPowerMeter1" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter1') }}:</label> <label for="inputMqttTopicPowerMeter1" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter1') }}:</label>
<div class="col-sm-10"> <div class="col-sm-10">
@ -99,13 +112,11 @@
</div> </div>
</div> </div>
</div> </div>
</div> </CardElement>
</div>
<div class="card mt-5">
<div class="card-header text-bg-primary">{{ $t('powerlimiteradmin.Battery') }}</div>
<div class="card-body">
<CardElement :text="$t('powerlimiteradmin.Battery')" textVariant="text-bg-primary" add-space
v-show="powerLimiterConfigList.enabled"
>
<div class="row mb-3"> <div class="row mb-3">
<label for="batterySocStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocStartThreshold') }}:</label> <label for="batterySocStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocStartThreshold') }}:</label>
<div class="col-sm-10"> <div class="col-sm-10">
@ -169,8 +180,7 @@
</div> </div>
<div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.VoltageLoadCorrectionInfo')"></div> <div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.VoltageLoadCorrectionInfo')"></div>
</div> </CardElement>
</div>
<button type="submit" class="btn btn-primary mb-3">{{ $t('powerlimiteradmin.Save') }}</button> <button type="submit" class="btn btn-primary mb-3">{{ $t('powerlimiteradmin.Save') }}</button>
</form> </form>
@ -182,17 +192,42 @@ import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue'; import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import { handleResponse, authHeader } from '@/utils/authentication'; import { handleResponse, authHeader } from '@/utils/authentication';
import CardElement from '@/components/CardElement.vue';
import InputElement from '@/components/InputElement.vue';
import { BIconInfoCircle } from 'bootstrap-icons-vue';
import type { PowerLimiterConfig } from "@/types/PowerLimiterConfig"; import type { PowerLimiterConfig } from "@/types/PowerLimiterConfig";
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage, BasePage,
BootstrapAlert, BootstrapAlert,
CardElement,
InputElement,
BIconInfoCircle,
}, },
data() { data() {
return { return {
dataLoading: true, dataLoading: true,
powerLimiterConfigList: {} as PowerLimiterConfig, powerLimiterConfigList: {} as PowerLimiterConfig,
inverterList: [
{ key: 0, value: "ID 00" },
{ key: 1, value: "ID 01" },
{ key: 2, value: "ID 02" },
{ key: 3, value: "ID 03" },
{ key: 4, value: "ID 04" },
{ key: 5, value: "ID 05" },
{ key: 6, value: "ID 06" },
{ key: 7, value: "ID 07" },
{ key: 8, value: "ID 08" },
{ key: 9, value: "ID 09" },
{ key: 10, value: "ID 10" },
],
inverterChannelList: [
{ key: 0, value: "CH 0" },
{ key: 1, value: "CH 1" },
{ key: 2, value: "CH 2" },
{ key: 3, value: "CH 3" },
],
alertMessage: "", alertMessage: "",
alertType: "info", alertType: "info",
showAlert: false, showAlert: false,

Binary file not shown.

Binary file not shown.

Binary file not shown.