diff --git a/include/Configuration.h b/include/Configuration.h index daa77916d..3452003d7 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -206,11 +206,11 @@ struct CONFIG_T { bool VerboseLogging; bool SolarPassThroughEnabled; uint8_t SolarPassThroughLosses; - uint8_t BatteryDrainStategy; + bool BatteryAlwaysUseAtNight; uint32_t Interval; bool IsInverterBehindPowerMeter; bool IsInverterSolarPowered; - uint8_t InverterId; + uint64_t InverterId; uint8_t InverterChannelId; int32_t TargetPowerConsumption; int32_t TargetPowerConsumptionHysteresis; diff --git a/include/defaults.h b/include/defaults.h index 818c556b6..940080c5f 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -122,11 +122,11 @@ #define POWERLIMITER_ENABLED false #define POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED true #define POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES 3 -#define POWERLIMITER_BATTERY_DRAIN_STRATEGY 0 +#define POWERLIMITER_BATTERY_ALWAYS_USE_AT_NIGHT false #define POWERLIMITER_INTERVAL 10 #define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true #define POWERLIMITER_IS_INVERTER_SOLAR_POWERED false -#define POWERLIMITER_INVERTER_ID 0 +#define POWERLIMITER_INVERTER_ID 0ULL #define POWERLIMITER_INVERTER_CHANNEL_ID 0 #define POWERLIMITER_TARGET_POWER_CONSUMPTION 0 #define POWERLIMITER_TARGET_POWER_CONSUMPTION_HYSTERESIS 0 diff --git a/src/Configuration.cpp b/src/Configuration.cpp index a1764f1e7..5d9e547e1 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -182,7 +182,7 @@ bool ConfigurationClass::write() powerlimiter["verbose_logging"] = config.PowerLimiter.VerboseLogging; powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled; powerlimiter["solar_passtrough_losses"] = config.PowerLimiter.SolarPassThroughLosses; - powerlimiter["battery_drain_strategy"] = config.PowerLimiter.BatteryDrainStategy; + powerlimiter["battery_always_use_at_night"] = config.PowerLimiter.BatteryAlwaysUseAtNight; powerlimiter["interval"] = config.PowerLimiter.Interval; powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter; powerlimiter["is_inverter_solar_powered"] = config.PowerLimiter.IsInverterSolarPowered; @@ -429,7 +429,8 @@ bool ConfigurationClass::read() config.PowerLimiter.VerboseLogging = powerlimiter["verbose_logging"] | VERBOSE_LOGGING; config.PowerLimiter.SolarPassThroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED; config.PowerLimiter.SolarPassThroughLosses = powerlimiter["solar_passthrough_losses"] | POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES; - config.PowerLimiter.BatteryDrainStategy = powerlimiter["battery_drain_strategy"] | POWERLIMITER_BATTERY_DRAIN_STRATEGY; + config.PowerLimiter.BatteryAlwaysUseAtNight = powerlimiter["battery_always_use_at_night"] | POWERLIMITER_BATTERY_ALWAYS_USE_AT_NIGHT; + if (powerlimiter["battery_drain_strategy"].as() == 1) { config.PowerLimiter.BatteryAlwaysUseAtNight = true; } // convert legacy setting config.PowerLimiter.Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL; config.PowerLimiter.IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER; config.PowerLimiter.IsInverterSolarPowered = powerlimiter["is_inverter_solar_powered"] | POWERLIMITER_IS_INVERTER_SOLAR_POWERED; diff --git a/src/Huawei_can.cpp b/src/Huawei_can.cpp index 87b141aad..df1d5eb24 100644 --- a/src/Huawei_can.cpp +++ b/src/Huawei_can.cpp @@ -325,7 +325,13 @@ void HuaweiCanClass::loop() // Check if inverter used by the power limiter is active std::shared_ptr inverter = - Hoymiles.getInverterByPos(config.PowerLimiter.InverterId); + Hoymiles.getInverterBySerial(config.PowerLimiter.InverterId); + + if (inverter == nullptr && config.PowerLimiter.InverterId < INV_MAX_COUNT) { + // we previously had an index saved as InverterId. fall back to the + // respective positional lookup if InverterId is not a known serial. + inverter = Hoymiles.getInverterByPos(config.PowerLimiter.InverterId); + } if (inverter != nullptr) { if(inverter->isProducing()) { diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index e8c29a05b..094e4a3e4 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -137,7 +137,13 @@ void PowerLimiterClass::loop() } std::shared_ptr currentInverter = - Hoymiles.getInverterByPos(config.PowerLimiter.InverterId); + Hoymiles.getInverterBySerial(config.PowerLimiter.InverterId); + + if (currentInverter == nullptr && config.PowerLimiter.InverterId < INV_MAX_COUNT) { + // we previously had an index saved as InverterId. fall back to the + // respective positional lookup if InverterId is not a known serial. + currentInverter = Hoymiles.getInverterByPos(config.PowerLimiter.InverterId); + } // in case of (newly) broken configuration, shut down // the last inverter we worked with (if any) @@ -242,14 +248,14 @@ void PowerLimiterClass::loop() if (isStartThresholdReached()) { return true; } - // with solar passthrough, and the respective drain strategy, we + // with solar passthrough, and the respective switch enabled, we // may start discharging the battery when it is nighttime. we also // stop the discharge cycle if it becomes daytime again. // TODO(schlimmchen): should be supported by sunrise and sunset, such // that a thunderstorm or other events that drastically lower the solar // power do not cause the start of a discharge cycle during the day. if (config.PowerLimiter.SolarPassThroughEnabled && - config.PowerLimiter.BatteryDrainStategy == EMPTY_AT_NIGHT) { + config.PowerLimiter.BatteryAlwaysUseAtNight) { return getSolarPower() == 0; } @@ -287,10 +293,10 @@ void PowerLimiterClass::loop() (isStopThresholdReached()?"yes":"no"), (_inverter->isProducing()?"is":"is NOT")); - MessageOutput.printf("[DPL::loop] battery discharging %s, SolarPT %s, Drain Strategy: %i\r\n", + MessageOutput.printf("[DPL::loop] battery discharging %s, SolarPT %s, use at night: %i\r\n", (_batteryDischargeEnabled?"allowed":"prevented"), (config.PowerLimiter.SolarPassThroughEnabled?"enabled":"disabled"), - config.PowerLimiter.BatteryDrainStategy); + config.PowerLimiter.BatteryAlwaysUseAtNight); }; if (_verboseLogging) { logging(); } diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index 444c134f2..ecbbcfbfc 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -26,18 +26,24 @@ void WebApiPowerLimiterClass::init(AsyncWebServer& server, Scheduler& scheduler) void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(); + auto const& config = Configuration.get(); + + size_t invAmount = 0; + for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { + if (config.Inverter[i].Serial != 0) { ++invAmount; } + } + + AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024 + 384 * invAmount); auto& root = response->getRoot(); - const CONFIG_T& config = Configuration.get(); root["enabled"] = config.PowerLimiter.Enabled; root["verbose_logging"] = config.PowerLimiter.VerboseLogging; root["solar_passthrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled; root["solar_passthrough_losses"] = config.PowerLimiter.SolarPassThroughLosses; - root["battery_drain_strategy"] = config.PowerLimiter.BatteryDrainStategy; + root["battery_always_use_at_night"] = config.PowerLimiter.BatteryAlwaysUseAtNight; root["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter; root["is_inverter_solar_powered"] = config.PowerLimiter.IsInverterSolarPowered; - root["inverter_id"] = config.PowerLimiter.InverterId; + root["inverter_serial"] = String(config.PowerLimiter.InverterId); root["inverter_channel_id"] = config.PowerLimiter.InverterChannelId; root["target_power_consumption"] = config.PowerLimiter.TargetPowerConsumption; root["target_power_consumption_hysteresis"] = config.PowerLimiter.TargetPowerConsumptionHysteresis; @@ -54,6 +60,37 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) root["full_solar_passthrough_start_voltage"] = static_cast(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0; root["full_solar_passthrough_stop_voltage"] = static_cast(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0; + JsonObject metadata = root.createNestedObject("metadata"); + metadata["power_meter_enabled"] = config.PowerMeter.Enabled; + metadata["battery_enabled"] = config.Battery.Enabled; + metadata["charge_controller_enabled"] = config.Vedirect.Enabled; + + JsonObject inverters = metadata.createNestedObject("inverters"); + for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { + if (config.Inverter[i].Serial == 0) { continue; } + + // we use the integer (base 10) representation of the inverter serial, + // rather than the hex represenation as used when handling the inverter + // serial elsewhere in the web application, because in this case, the + // serial is actually not displayed but only used as a value/index. + JsonObject obj = inverters.createNestedObject(String(config.Inverter[i].Serial)); + obj["pos"] = i; + obj["name"] = String(config.Inverter[i].Name); + obj["poll_enable"] = config.Inverter[i].Poll_Enable; + obj["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night; + obj["command_enable"] = config.Inverter[i].Command_Enable; + obj["command_enable_night"] = config.Inverter[i].Command_Enable_Night; + + obj["type"] = "Unknown"; + obj["channels"] = 1; + auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial); + if (inv != nullptr) { + obj["type"] = inv->typeName(); + auto channels = inv->Statistics()->getChannelsByType(TYPE_DC); + obj["channels"] = channels.size(); + } + } + response->setLength(); request->send(response); } @@ -103,13 +140,14 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("enabled") - && root.containsKey("lower_power_limit") - && root.containsKey("inverter_id") - && root.containsKey("inverter_channel_id") - && root.containsKey("target_power_consumption") - && root.containsKey("target_power_consumption_hysteresis") - )) { + // we were not actually checking for all the keys we (unconditionally) + // access below for a long time, and it is technically not needed if users + // use the web application to submit settings. the web app will always + // submit all keys. users who send HTTP requests manually need to beware + // anyways to always include the keys accessed below. if we wanted to + // support a simpler API, like only sending the "enabled" key which only + // changes that key, we need to refactor all of the code below. + if (!root.containsKey("enabled")) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); @@ -117,34 +155,43 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) return; } - CONFIG_T& config = Configuration.get(); config.PowerLimiter.Enabled = root["enabled"].as(); PowerLimiter.setMode(PowerLimiterClass::Mode::Normal); // User input sets PL to normal operation config.PowerLimiter.VerboseLogging = root["verbose_logging"].as(); - config.PowerLimiter.SolarPassThroughEnabled = root["solar_passthrough_enabled"].as(); - config.PowerLimiter.SolarPassThroughLosses = root["solar_passthrough_losses"].as(); - config.PowerLimiter.BatteryDrainStategy= root["battery_drain_strategy"].as(); + + if (config.Vedirect.Enabled) { + config.PowerLimiter.SolarPassThroughEnabled = root["solar_passthrough_enabled"].as(); + config.PowerLimiter.SolarPassThroughLosses = root["solar_passthrough_losses"].as(); + config.PowerLimiter.BatteryAlwaysUseAtNight= root["battery_always_use_at_night"].as(); + config.PowerLimiter.FullSolarPassThroughStartVoltage = static_cast(root["full_solar_passthrough_start_voltage"].as() * 100) / 100.0; + config.PowerLimiter.FullSolarPassThroughStopVoltage = static_cast(root["full_solar_passthrough_stop_voltage"].as() * 100) / 100.0; + } + config.PowerLimiter.IsInverterBehindPowerMeter = root["is_inverter_behind_powermeter"].as(); config.PowerLimiter.IsInverterSolarPowered = root["is_inverter_solar_powered"].as(); - config.PowerLimiter.InverterId = root["inverter_id"].as(); + config.PowerLimiter.InverterId = root["inverter_serial"].as(); config.PowerLimiter.InverterChannelId = root["inverter_channel_id"].as(); config.PowerLimiter.TargetPowerConsumption = root["target_power_consumption"].as(); config.PowerLimiter.TargetPowerConsumptionHysteresis = root["target_power_consumption_hysteresis"].as(); config.PowerLimiter.LowerPowerLimit = root["lower_power_limit"].as(); config.PowerLimiter.UpperPowerLimit = root["upper_power_limit"].as(); - config.PowerLimiter.IgnoreSoc = root["ignore_soc"].as(); - config.PowerLimiter.BatterySocStartThreshold = root["battery_soc_start_threshold"].as(); - config.PowerLimiter.BatterySocStopThreshold = root["battery_soc_stop_threshold"].as(); + + if (config.Battery.Enabled) { + config.PowerLimiter.IgnoreSoc = root["ignore_soc"].as(); + config.PowerLimiter.BatterySocStartThreshold = root["battery_soc_start_threshold"].as(); + config.PowerLimiter.BatterySocStopThreshold = root["battery_soc_stop_threshold"].as(); + if (config.Vedirect.Enabled) { + config.PowerLimiter.FullSolarPassThroughSoc = root["full_solar_passthrough_soc"].as(); + } + } + config.PowerLimiter.VoltageStartThreshold = root["voltage_start_threshold"].as(); config.PowerLimiter.VoltageStartThreshold = static_cast(config.PowerLimiter.VoltageStartThreshold * 100) / 100.0; config.PowerLimiter.VoltageStopThreshold = root["voltage_stop_threshold"].as(); config.PowerLimiter.VoltageStopThreshold = static_cast(config.PowerLimiter.VoltageStopThreshold * 100) / 100.0; config.PowerLimiter.VoltageLoadCorrectionFactor = root["voltage_load_correction_factor"].as(); config.PowerLimiter.RestartHour = root["inverter_restart_hour"].as(); - config.PowerLimiter.FullSolarPassThroughSoc = root["full_solar_passthrough_soc"].as(); - config.PowerLimiter.FullSolarPassThroughStartVoltage = static_cast(root["full_solar_passthrough_start_voltage"].as() * 100) / 100.0; - config.PowerLimiter.FullSolarPassThroughStopVoltage = static_cast(root["full_solar_passthrough_stop_voltage"].as() * 100) / 100.0; WebApi.writeConfig(retMsg); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 50a5a17bd..e99a865aa 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -10,7 +10,7 @@ "DTUSettings": "DTU", "DeviceManager": "Hardware", "VedirectSettings": "VE.Direct", - "PowerMeterSettings": "Power Meter", + "PowerMeterSettings": "Stromzähler", "BatterySettings": "Batterie", "AcChargerSettings": "AC Ladegerät", "ConfigManagement": "Konfigurationsverwaltung", @@ -574,48 +574,52 @@ "testHttpRequest": "Testen" }, "powerlimiteradmin": { - "PowerLimiterSettings": "Power Limiter Einstellungen", - "PowerLimiterConfiguration": "Power Limiter Konfiguration", + "PowerLimiterSettings": "Dynamic Power Limiter Einstellungen", + "ConfigAlertMessage": "Eine oder mehrere Voraussetzungen zum Betrieb des Dynamic Power Limiter sind nicht erfüllt.", + "ConfigHints": "Konfigurationshinweise", + "ConfigHintRequirement": "Erforderlich", + "ConfigHintOptional": "Optional", + "ConfigHintsIntro": "Folgende Hinweise zur Konfiguration des Dynamic Power Limiter (DPL) sollen beachtet werden:", + "ConfigHintPowerMeterDisabled": "Zum Betrieb des DPL muss der Power Meter konfiguriert sein und Daten liefern.", + "ConfigHintNoInverter": "Vor dem Festlegen von Einstellungen des DPL muss mindestens ein Inverter konfiguriert sein.", + "ConfigHintInverterCommunication": "Das Abrufen von Daten und Senden von Kommandos muss für den zu regelnden Wechselrichter aktiviert sein.", + "ConfigHintNoChargeController": "Die Solar-Passthrough Funktion kann nur mit aktivierter VE.Direct Schnittstelle genutzt werden.", + "ConfigHintNoBatteryInterface": "SoC-basierte Schwellwerte können nur mit konfigurierter Batteriekommunikationsschnittstelle genutzt werden.", "General": "Allgemein", "Enable": "Aktiviert", "VerboseLogging": "@:base.VerboseLogging", + "SolarPassthrough": "Solar-Passthrough", "EnableSolarPassthrough": "Aktiviere Solar-Passthrough", - "SolarPassthroughLosses": "(Full) Solar-Passthrough Verluste:", + "SolarPassthroughLosses": "(Full) Solar-Passthrough Verluste", "SolarPassthroughLossesInfo": "Hinweis: Bei der Übertragung von Energie vom Solarladeregler zum Inverter sind Leitungsverluste zu erwarten. Um eine schleichende Entladung der Batterie im (Full) Solar-Passthrough Modus zu unterbinden, können diese Verluste berücksichtigt werden. Das am Inverter einzustellende Power Limit wird nach Berücksichtigung von dessen Effizienz zusätzlich um diesen Faktor verringert.", - "BatteryDrainStrategy": "Strategie zur Batterieentleerung", - "BatteryDrainWhenFull": "Leeren, wenn voll", - "BatteryDrainAtNight": "Leeren zur Nacht", - "SolarpassthroughInfo": "Diese Einstellung aktiviert die direkte Weitergabe der aktuell vom Laderegler gemeldeten Solarleistung an den Wechselrichter um eine unnötige Speicherung zu vermeiden und die Energieverluste zu minimieren.", - "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.", + "BatteryDischargeAtNight": "Batterie nachts sogar teilweise geladen nutzen", + "SolarpassthroughInfo": "Diese Funktion ermöglicht den unmittelbaren Verbauch der verfügbaren Solarleistung. Dazu wird die aktuell vom Laderegler gemeldete Solarleistung am Wechselrichter als Limit eingestellt, selbst wenn sich die Batterie in einem Ladezyklus befindet. Somit wird eine unnötige Speicherung vermieden, die verlustbehaftet wäre.", + "InverterSettings": "Wechselrichter", + "Inverter": "Zu regelnder Wechselrichter", + "SelectInverter": "Inverter auswählen...", + "InverterChannelId": "Eingang für Spannungsmessungen", "TargetPowerConsumption": "Angestrebter Netzbezug", - "TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch aus dem Netz.", - "TargetPowerConsumptionHysteresis": "Hysterese für das berechnete Limit", + "TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch aus dem Netz. Wert darf negativ sein.", + "TargetPowerConsumptionHysteresis": "Hysterese", "TargetPowerConsumptionHysteresisHint": "Neu berechnetes Limit nur dann an den Inverter senden, wenn es vom zuletzt gesendeten Limit um mindestens diesen Betrag abweicht.", "LowerPowerLimit": "Unteres Leistungslimit", "UpperPowerLimit": "Oberes Leistungslimit", - "PowerMeters": "Leistungsmesser", + "SocThresholds": "Batterie State of Charge (SoC) Schwellwerte", "IgnoreSoc": "Batterie SoC ignorieren", - "BatterySocStartThreshold": "Akku SoC - Start", - "BatterySocStopThreshold": "Akku SoC - Stop", - "BatterySocSolarPassthroughStartThreshold": "Akku SoC - Start solar passthrough", - "BatterySocSolarPassthroughStartThresholdHint": "Wenn der Batterie SoC über diesem Limit ist wird die Inverter Leistung entsprechend der Victron MPPT Leistung gesetzt (abzüglich Effizienzkorrekturfaktor). Kann verwendet werden um überschüssige Solarleistung an das Netz zu liefern wenn die Batterie voll ist.", - "VoltageStartThreshold": "DC Spannung - Start", - "VoltageStopThreshold": "DC Spannung - Stop", - "VoltageSolarPassthroughStartThreshold": "DC Spannung - Start Solar-Passthrough", - "VoltageSolarPassthroughStopThreshold": "DC Spannung - Stop Solar-Passthrough", - "VoltageSolarPassthroughStartThresholdHint": "Wenn der Batteriespannung über diesem Limit ist wird die Inverter Leistung entsprechend der Victron MPPT Leistung gesetzt (abzüglich Effizienzkorrekturfaktor). Kann verwendet werden um überschüssige Solarleistung an das Netz zu liefern wenn die Batterie voll ist. Dieser Mode wird aktiv wenn das Start Spannungslimit überschritten wird und inaktiv wenn das Stop Spannungslimit unterschritten wird.", - "VoltageLoadCorrectionFactor": "DC Spannung - Lastkorrekturfaktor", + "StartThreshold": "Batterienutzung Start-Schwellwert", + "StopThreshold": "Batterienutzung Stop-Schwellwert", + "FullSolarPassthroughStartThreshold": "Full-Solar-Passthrough Start-Schwellwert", + "FullSolarPassthroughStartThresholdHint": "Oberhalb dieses Schwellwertes wird die Inverterleistung der Victron-MPPT-Leistung gleichgesetzt (abzüglich Effizienzkorrekturfaktor). Kann verwendet werden um überschüssige Solarleistung an das Netz zu liefern wenn die Batterie voll ist.", + "VoltageSolarPassthroughStopThreshold": "Full-Solar-Passthrough Stop-Schwellwert", + "VoltageLoadCorrectionFactor": "Lastkorrekturfaktor", "BatterySocInfo": "Hinweis: Die Akku SoC (State of Charge) Werte werden nur benutzt, wenn die Batterie-Kommunikationsschnittstelle innerhalb der letzten Minute gültige Werte geschickt hat. Andernfalls werden als Fallback-Option die Spannungseinstellungen verwendet.", - "InverterIsBehindPowerMeter": "Welchselrichter ist hinter Leistungsmesser", + "InverterIsBehindPowerMeter": "Stromzählermessung beinhaltet Wechselrichterleistung", "InverterIsSolarPowered": "Wechselrichter wird von Solarmodulen gespeist", - "Battery": "DC / Akku", - "VoltageLoadCorrectionInfo": "Hinweis: Wenn Leistung von der Batterie abgegeben wird, bricht normalerweise die Spannung etwas ein. Damit nicht vorzeitig der Wechelrichter ausgeschaltet wird sobald der \"Stop\"-Schwellenwert erreicht wird, wird der hier angegebene Korrekturfaktor mit einberechnet. Korrigierte Spannung = DC Spannung + (Aktuelle Leistung (W) * Korrekturfaktor).", - "InverterRestart": "Wechselrichter Neustart", - "InverterRestartHour": "Stunde für Neustart", - "InverterRestartHint": "Neustart des Wechselrichter einmal täglich um die \"Tagesertrag\" Werte wieder auf Null zu setzen." + "VoltageThresholds": "Batterie Spannungs-Schwellwerte ", + "VoltageLoadCorrectionInfo": "Hinweis: Wenn Leistung von der Batterie abgegeben wird, bricht ihre Spannung etwas ein. Der Spannungseinbruch skaliert mit dem Entladestrom. Damit nicht vorzeitig der Wechselrichter ausgeschaltet wird sobald der Stop-Schwellenwert unterschritten wurde, wird der hier angegebene Korrekturfaktor mit einberechnet um die Spannung zu errechnen die der Akku in Ruhe hätte. Korrigierte Spannung = DC Spannung + (Aktuelle Leistung (W) * Korrekturfaktor).", + "InverterRestartHour": "Uhrzeit für geplanten Neustart", + "InverterRestartDisabled": "Keinen automatischen Neustart planen", + "InverterRestartHint": "Der Tagesertrag des Wechselrichters wird normalerweise nachts zurückgesetzt, wenn sich der Wechselrichter mangels Licht abschaltet. Um den Tageserstrag zurückzusetzen obwohl der Wechselrichter dauerhaft von der Batterie gespeist wird, kann der Inverter täglich zur gewünschten Uhrzeit automatisch neu gestartet werden." }, "batteryadmin": { "BatterySettings": "Batterie Einstellungen", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 864789da3..532ac8b48 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -580,48 +580,52 @@ "milliSeconds": "ms" }, "powerlimiteradmin": { - "PowerLimiterSettings": "Power Limiter Settings", - "PowerLimiterConfiguration": "Power Limiter Configuration", + "PowerLimiterSettings": "Dynamic Power Limiter Settings", + "ConfigAlertMessage": "One or more prerequisites for operating the Dynamic Power Limiter are not met.", + "ConfigHints": "Configuration Notes", + "ConfigHintRequirement": "Required", + "ConfigHintOptional": "Optional", + "ConfigHintsIntro": "The following notes regarding the Dynamic Power Limiter (DPL) configuration shall be considered:", + "ConfigHintPowerMeterDisabled": "Operating the DPL requires the Power Meter being configured and delivering data.", + "ConfigHintNoInverter": "At least one inverter must be configured prior to setting up the DPL.", + "ConfigHintInverterCommunication": "Polling data from and sending commands to the target inverter must be enabled.", + "ConfigHintNoChargeController": "The solar-passthrough feature can only be used if the VE.Direct interface is configured.", + "ConfigHintNoBatteryInterface": "SoC-based thresholds can only be used if a battery communication interface is configured.", "General": "General", "Enable": "Enable", "VerboseLogging": "@:base.VerboseLogging", + "SolarPassthrough": "Solar-Passthrough", "EnableSolarPassthrough": "Enable Solar-Passthrough", - "SolarPassthroughLosses": "(Full) Solar Passthrough Losses:", - "SolarPassthroughLossesInfo": "Hint: Line losses are to be expected when transferring energy from the solar charge controller to the inverter. These losses can be taken into account to prevent the battery from gradually discharging in (full) solar passthrough mode. The power limit to be set on the inverter is additionally reduced by this factor after taking its efficiency into account.", - "BatteryDrainStrategy": "Battery drain strategy", - "BatteryDrainWhenFull": "Empty when full", - "BatteryDrainAtNight": "Empty at night", - "SolarpassthroughInfo": "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.", - "TargetPowerConsumption": "Target power consumption from grid", - "TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.", - "TargetPowerConsumptionHysteresis": "Hysteresis for calculated power limit", + "SolarPassthroughLosses": "(Full) Solar-Passthrough Losses", + "SolarPassthroughLossesInfo": "Hint: Line losses are to be expected when transferring energy from the solar charge controller to the inverter. These losses can be taken into account to prevent the battery from gradually discharging in (full) solar-passthrough mode. The power limit to be set on the inverter is additionally reduced by this factor after taking its efficiency into account.", + "BatteryDischargeAtNight": "Use battery at night even if only partially charged", + "SolarpassthroughInfo": "This feature allows to use the available current solar power directly. The solar power, as reported by the MPPT charge controller, is set as the inverter's limit, even if the battery is currently charging. This avoids storing energy unnecessarily, which would be lossy.", + "InverterSettings": "Inverter", + "Inverter": "Target Inverter", + "SelectInverter": "Select an inverter...", + "InverterChannelId": "Input used for voltage measurements", + "TargetPowerConsumption": "Target Grid Consumption", + "TargetPowerConsumptionHint": "Grid power consumption the limiter tries to achieve. Value may be negative.", + "TargetPowerConsumptionHysteresis": "Hysteresis", "TargetPowerConsumptionHysteresisHint": "Only send a newly calculated power limit to the inverter if the absolute difference to the last sent power limit matches or exceeds this amount.", - "LowerPowerLimit": "Lower power limit", - "UpperPowerLimit": "Upper power limit", - "PowerMeters": "Power meter", + "LowerPowerLimit": "Lower Power Limit", + "UpperPowerLimit": "Upper Power Limit", + "SocThresholds": "Battery State of Charge (SoC) Thresholds", "IgnoreSoc": "Ignore Battery SoC", - "BatterySocStartThreshold": "Battery SoC - Start threshold", - "BatterySocStopThreshold": "Battery SoC - Stop threshold", - "BatterySocSolarPassthroughStartThreshold": "Battery SoC - Start threshold for full solar passthrough", - "BatterySocSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) if battery SoC is over this limit. Use this if you like to supply excess power to the grid when battery is full", - "VoltageStartThreshold": "DC Voltage - Start threshold", - "VoltageStopThreshold": "DC Voltage - Stop threshold", - "VoltageSolarPassthroughStartThreshold": "DC Voltage - Start threshold for full solar passthrough", - "VoltageSolarPassthroughStopThreshold": "DC Voltage - Stop threshold for full solar passthrough", - "VoltageSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) when full solar passthrough is active. Use this if you like to supply excess power to the grid when battery is full. This is started when battery voltage goes over this limit and stopped if voltage drops below stop limit.", - "VoltageLoadCorrectionFactor": "DC Voltage - Load correction factor", + "StartThreshold": "Start Threshold for Battery Discharging", + "StopThreshold": "Stop Threshold for Battery Discharging", + "FullSolarPassthroughStartThreshold": "Full Solar-Passthrough Start Threshold", + "FullSolarPassthroughStartThresholdHint": "Inverter power is set equal to Victron MPPT power (minus efficiency factors) while above this threshold. Use this if you want to supply excess power to the grid when the battery is full.", + "VoltageSolarPassthroughStopThreshold": "Full Solar-Passthrough Stop Threshold", + "VoltageLoadCorrectionFactor": "Load correction factor", "BatterySocInfo": "Hint: The battery SoC (State of Charge) values are only used if the battery communication interface reported SoC updates in the last minute. Otherwise the voltage thresholds will be used as fallback.", - "InverterIsBehindPowerMeter": "Inverter is behind Power meter", + "InverterIsBehindPowerMeter": "PowerMeter reading includes inverter output", "InverterIsSolarPowered": "Inverter is powered by solar modules", - "Battery": "DC / Battery", - "VoltageLoadCorrectionInfo": "Hint: When the power output is higher, the voltage is usually decreasing. In order to not stop the inverter too early (Stop treshold), a power factor can be specified here to correct this. Corrected voltage = DC Voltage + (Current power * correction factor).", - "InverterRestart": "Inverter Restart", - "InverterRestartHour": "Restart Hour", - "InverterRestartHint": "Restart the Inverter once a day to reset the \"YieldDay\" values." + "VoltageThresholds": "Battery Voltage Thresholds", + "VoltageLoadCorrectionInfo": "Hint: When the battery is discharged, its voltage drops. The voltage drop scales with the discharge current. In order to not stop the inverter too early (stop threshold), this load correction factor can be specified to calculate the battery voltage if it was idle. Corrected voltage = DC Voltage + (Current power * correction factor).", + "InverterRestartHour": "Automatic Restart Time", + "InverterRestartDisabled": "Do not execute automatic restart", + "InverterRestartHint": "The daily yield of the inverter is usually reset at night when the inverter turns off due to lack of light. To reset the daily yield even though the inverter is continuously powered by the battery, the inverter can be automatically restarted daily at the desired time." }, "batteryadmin": { "BatterySettings": "Battery Settings", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index a16a7fba3..cf8e3a8df 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -663,45 +663,49 @@ "Cancel": "@:base.Cancel" }, "powerlimiteradmin": { - "PowerLimiterSettings": "Power Limiter Settings", - "PowerLimiterConfiguration": "Power Limiter Configuration", + "PowerLimiterSettings": "Dynamic Power Limiter Settings", + "ConfigAlertMessage": "One or more prerequisites for operating the Dynamic Power Limiter are not met.", + "ConfigHints": "Configuration Notes", + "ConfigHintRequirement": "Required", + "ConfigHintOptional": "Optional", + "ConfigHintsIntro": "The following notes regarding the Dynamic Power Limiter (DPL) configuration shall be considered:", + "ConfigHintPowerMeterDisabled": "Operating the DPL requires the Power Meter being configured and delivering data.", + "ConfigHintNoInverter": "At least one inverter must be configured prior to setting up the DPL.", + "ConfigHintInverterCommunication": "Polling data from and sending commands to the target inverter must be enabled.", + "ConfigHintNoChargeController": "The solar-passthrough feature can only be used if the VE.Direct interface is configured.", + "ConfigHintNoBatteryInterface": "SoC-based thresholds can only be used if a battery communication interface is configured.", "General": "General", "Enable": "Enable", "VerboseLogging": "@:base.VerboseLogging", + "SolarPassthrough": "Solar-Passthrough", "EnableSolarPassthrough": "Enable Solar-Passthrough", - "SolarPassthroughLosses": "(Full) Solar Passthrough Losses:", - "SolarPassthroughLossesInfo": "Hint: Line losses are to be expected when transferring energy from the solar charge controller to the inverter. These losses can be taken into account to prevent the battery from gradually discharging in (full) solar passthrough mode. The power limit to be set on the inverter is additionally reduced by this factor after taking its efficiency into account.", - "BatteryDrainStrategy": "Battery drain strategy", - "BatteryDrainWhenFull": "Empty when full", - "BatteryDrainAtNight": "Empty at night", - "SolarpassthroughInfo": "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.", - "TargetPowerConsumption": "Target power consumption from grid", - "TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.", - "TargetPowerConsumptionHysteresis": "Hysteresis for calculated power limit", + "SolarPassthroughLosses": "(Full) Solar-Passthrough Losses", + "SolarPassthroughLossesInfo": "Hint: Line losses are to be expected when transferring energy from the solar charge controller to the inverter. These losses can be taken into account to prevent the battery from gradually discharging in (full) solar-passthrough mode. The power limit to be set on the inverter is additionally reduced by this factor after taking its efficiency into account.", + "BatteryDischargeAtNight": "Use battery at night even if only partially charged", + "SolarpassthroughInfo": "This feature allows to use the available current solar power directly. The solar power, as reported by the MPPT charge controller, is set as the inverter's limit, even if the battery is currently charging. This avoids storing energy unnecessarily, which would be lossy.", + "InverterSettings": "Inverter", + "Inverter": "Target Inverter", + "SelectInverter": "Select an inverter...", + "InverterChannelId": "Input used for voltage measurements", + "TargetPowerConsumption": "Target Grid Consumption", + "TargetPowerConsumptionHint": "Grid power consumption the limiter tries to achieve. Value may be negative.", + "TargetPowerConsumptionHysteresis": "Hysteresis", "TargetPowerConsumptionHysteresisHint": "Only send a newly calculated power limit to the inverter if the absolute difference to the last sent power limit matches or exceeds this amount.", - "LowerPowerLimit": "Lower power limit", - "UpperPowerLimit": "Upper power limit", - "PowerMeters": "Power meter", + "LowerPowerLimit": "Lower Power Limit", + "UpperPowerLimit": "Upper Power Limit", + "SocThresholds": "Battery State of Charge (SoC) Thresholds", "IgnoreSoc": "Ignore Battery SoC", - "BatterySocStartThreshold": "Battery SoC - Start threshold", - "BatterySocStopThreshold": "Battery SoC - Stop threshold", - "BatterySocSolarPassthroughStartThreshold": "Battery SoC - Start threshold for full solar passthrough", - "BatterySocSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) if battery SOC is over this limit. Use this if you like to supply excess power to the grid when battery is full", - "VoltageStartThreshold": "DC Voltage - Start threshold", - "VoltageStopThreshold": "DC Voltage - Stop threshold", - "VoltageSolarPassthroughStartThreshold": "DC Voltage - Start threshold for full solar passthrough", - "VoltageSolarPassthroughStopThreshold": "DC Voltage - Stop threshold for full solar passthrough", - "VoltageSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) when full solar passthrough is active. Use this if you like to supply excess power to the grid when battery is full. This is started when battery voltage goes over this limit and stopped if voltage drops below stop limit.", - "VoltageLoadCorrectionFactor": "DC Voltage - Load correction factor", + "StartThreshold": "Start Threshold for Battery Discharging", + "StopThreshold": "Stop Threshold for Battery Discharging", + "FullSolarPassthroughStartThreshold": "Full Solar-Passthrough Start Threshold", + "FullSolarPassthroughStartThresholdHint": "Inverter power is set equal to Victron MPPT power (minus efficiency factors) while above this threshold. Use this if you want to supply excess power to the grid when the battery is full.", + "VoltageSolarPassthroughStopThreshold": "Full Solar-Passthrough Stop Threshold", + "VoltageLoadCorrectionFactor": "Load correction factor", "BatterySocInfo": "Hint: The battery SoC (State of Charge) values are only used if the battery communication interface reported SoC updates in the last minute. Otherwise the voltage thresholds will be used as fallback.", - "InverterIsBehindPowerMeter": "Inverter is behind Power meter", + "InverterIsBehindPowerMeter": "PowerMeter reading includes inverter output", "InverterIsSolarPowered": "Inverter is powered by solar modules", - "Battery": "DC / Battery", - "VoltageLoadCorrectionInfo": "Hint: When the power output is higher, the voltage is usually decreasing. In order to not stop the inverter too early (Stop treshold), a power factor can be specified here to correct this. Corrected voltage = DC Voltage + (Current power * correction factor)." + "VoltageThresholds": "Battery Voltage Thresholds", + "VoltageLoadCorrectionInfo": "Hint: When the battery is discharged, its voltage drops. The voltage drop scales with the discharge current. In order to not stop the inverter too early (stop threshold), this load correction factor can be specified to calculate the battery voltage if it was idle. Corrected voltage = DC Voltage + (Current power * correction factor)." }, "login": { "Login": "Connexion", diff --git a/webapp/src/types/PowerLimiterConfig.ts b/webapp/src/types/PowerLimiterConfig.ts index 70fe6084a..efd0451fd 100644 --- a/webapp/src/types/PowerLimiterConfig.ts +++ b/webapp/src/types/PowerLimiterConfig.ts @@ -1,12 +1,32 @@ +export interface PowerLimiterInverterInfo { + pos: number; + name: string; + poll_enable: boolean; + poll_enable_night: boolean; + command_enable: boolean; + command_enable_night: boolean; + type: string; + channels: number; +} + +// meta-data not directly part of the DPL settings, +// to control visibility of DPL settings +export interface PowerLimiterMetaData { + power_meter_enabled: boolean; + battery_enabled: boolean; + charge_controller_enabled: boolean; + inverters: { [key: string]: PowerLimiterInverterInfo }; +} + export interface PowerLimiterConfig { enabled: boolean; verbose_logging: boolean; solar_passthrough_enabled: boolean; solar_passthrough_losses: number; - battery_drain_strategy: number; + battery_always_use_at_night: boolean; is_inverter_behind_powermeter: boolean; is_inverter_solar_powered: boolean; - inverter_id: number; + inverter_serial: string; inverter_channel_id: number; target_power_consumption: number; target_power_consumption_hysteresis: number; @@ -22,4 +42,5 @@ export interface PowerLimiterConfig { full_solar_passthrough_soc: number; full_solar_passthrough_start_voltage: number; full_solar_passthrough_stop_voltage: number; + metadata: PowerLimiterMetaData; } diff --git a/webapp/src/views/PowerLimiterAdminView.vue b/webapp/src/views/PowerLimiterAdminView.vue index 54cc5adb0..10be443c0 100644 --- a/webapp/src/views/PowerLimiterAdminView.vue +++ b/webapp/src/views/PowerLimiterAdminView.vue @@ -1,278 +1,197 @@