From e32f71780eca7f991797323489b4bcaed912c015 Mon Sep 17 00:00:00 2001 From: chingon007 <76529461+chingon007@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:46:29 +0100 Subject: [PATCH] [sonnen] Add support to actively charge the battery from grid (#18213) * Initial commit for V3 sonnen binding. Added functionality for start/stop charging battery from grid. Signed-off-by: chingon007 --- bundles/org.openhab.binding.sonnen/README.md | 42 ++++++------ .../internal/SonnenBindingConstants.java | 4 ++ .../sonnen/internal/SonnenConfiguration.java | 1 + .../sonnen/internal/SonnenHandler.java | 66 ++++++++++++++----- .../SonnenJSONCommunication.java | 60 ++++++++++++++++- .../communication/SonnenJsonDataDTO.java | 27 ++++++++ .../resources/OH-INF/i18n/sonnen.properties | 24 ++++--- .../resources/OH-INF/thing/thing-types.xml | 27 +++++++- .../resources/OH-INF/update/instructions.xml | 8 +++ 9 files changed, 212 insertions(+), 47 deletions(-) diff --git a/bundles/org.openhab.binding.sonnen/README.md b/bundles/org.openhab.binding.sonnen/README.md index 0388a5cf1a5..54be5c24b80 100644 --- a/bundles/org.openhab.binding.sonnen/README.md +++ b/bundles/org.openhab.binding.sonnen/README.md @@ -20,26 +20,28 @@ If you want to use the V2 API, which supports more channels, you need to provide The following channels are yet supported: -| Channel | Type | Access | Description | -| ------------------------------ | ------------- | ------ | --------------------------------------------------------------------------------------- | -| batteryChargingState | Switch | read | Indicates if the Battery is charging at that moment | -| batteryCharging | Number:Power | read | Indicates the actual current charging the Battery. Otherwise 0. | -| batteryDischargingState | Switch | read | Indicates if the Battery is discharging at that moment | -| batteryDischarging | Number:Power | read | Indicates the actual current discharging the Battery. Otherwise 0. | -| consumption | Number:Power | read | Indicates the actual consumption of the consumer in watt | -| gridFeedIn | Number:Power | read | Indicates the actual current feeding to the Grid in watt.0 if nothing is feeded | -| gridConsumption | Number:Power | read | Indicates the actual current consumption from the Grid in watt.0 if nothing is received | -| solarProduction | Number:Power | read | Indicates the actual production of the Solar system in watt | -| batteryLevel | Number | read | Indicates the actual Battery Level in % from 0 - 100 | -| flowConsumptionBatteryState | Switch | read | Indicates if there is a current flow from Battery towards Consumption | -| flowConsumptionGridState | Switch | read | Indicates if there is a current flow from Grid towards Consumption | -| flowConsumptionProductionState | Switch | read | Indicates if there is a current flow from Solar Production towards Consumption | -| flowGridBatteryState | Switch | read | Indicates if there is a current flow from Grid towards Battery | -| flowProductionBatteryState | Switch | read | Indicates if there is a current flow from Production towards Battery | -| energyImportedStateProduction | Number:Energy | read | Indicates the imported kWh Production | -| energyExportedStateProduction | Number:Energy | read | Indicates the exported kWh Production | -| energyImportedStateConsumption | Number:Energy | read | Indicates the imported kWh Consumption | -| energyExportedStateConsumption | Number:Energy | read | Indicates the exported kWh Consumption | +| Channel | Type | Access | Description | +|--------------------------------|---------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| batteryChargingState | Switch | read | Indicates if the Battery is charging at that moment | +| batteryCharging | Number:Power | read | Indicates the actual current charging the Battery. Otherwise 0. | +| batteryDischargingState | Switch | read | Indicates if the Battery is discharging at that moment | +| batteryDischarging | Number:Power | read | Indicates the actual current discharging the Battery. Otherwise 0. | +| consumption | Number:Power | read | Indicates the actual consumption of the consumer in watt | +| gridFeedIn | Number:Power | read | Indicates the actual current feeding to the Grid in watt.0 if nothing is feeded | +| gridConsumption | Number:Power | read | Indicates the actual current consumption from the Grid in watt.0 if nothing is received | +| solarProduction | Number:Power | read | Indicates the actual production of the Solar system in watt | +| batteryLevel | Number | read | Indicates the actual Battery Level in % from 0 - 100 | +| flowConsumptionBatteryState | Switch | read | Indicates if there is a current flow from Battery towards Consumption | +| flowConsumptionGridState | Switch | read | Indicates if there is a current flow from Grid towards Consumption | +| flowConsumptionProductionState | Switch | read | Indicates if there is a current flow from Solar Production towards Consumption | +| flowGridBatteryState | Switch | read | Indicates if there is a current flow from Grid towards Battery | +| flowProductionBatteryState | Switch | read | Indicates if there is a current flow from Production towards Battery | +| energyImportedStateProduction | Number:Energy | read | Indicates the imported kWh Production | +| energyExportedStateProduction | Number:Energy | read | Indicates the exported kWh Production | +| energyImportedStateConsumption | Number:Energy | read | Indicates the imported kWh Consumption | +| energyExportedStateConsumption | Number:Energy | read | Indicates the exported kWh Consumption | +| batteryChargingFromGrid | Switch | read/write | Starts and stops the active battery charging from Grid. Note: "Write-API" in Software-Integration page of the local web interface from the sonnen battery must be activated and the given token must be entered in authentication token field of the binding | +| batteryOperationMode | String | read | Indicates if the Battery is operating in automatic or manual mode. Manual mode is required for active charging the battery. Assure that the battery is in automatic mode if you don't actively charge the battery.Changing the operation mode is happening with the channel "batteryChargingFromGrid" | ## Full Example diff --git a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenBindingConstants.java b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenBindingConstants.java index 4447395ae83..9160a3ccff2 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenBindingConstants.java +++ b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenBindingConstants.java @@ -51,4 +51,8 @@ public class SonnenBindingConstants { public static final String CHANNELENERGYEXPORTEDSTATEPRODUCTION = "energyExportedStateProduction"; public static final String CHANNELENERGYIMPORTEDSTATECONSUMPTION = "energyImportedStateConsumption"; public static final String CHANNELENERGYEXPORTEDSTATECONSUMPTION = "energyExportedStateConsumption"; + + // List of new Channel ids for battery charging from Grid + public static final String CHANNELBATTERYCHARGINGGRID = "batteryChargingFromGrid"; + public static final String CHANNELBATTERYOPERATIONMODE = "batteryOperationMode"; } diff --git a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenConfiguration.java b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenConfiguration.java index b21d6f6d94f..a91bb5cba19 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenConfiguration.java +++ b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenConfiguration.java @@ -25,4 +25,5 @@ public class SonnenConfiguration { public String hostIP = ""; public int refreshInterval = 30; public String authToken = ""; + public int chargingPower = -1; } diff --git a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenHandler.java b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenHandler.java index 3e3e5284f08..d2dea9ec72d 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenHandler.java +++ b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/SonnenHandler.java @@ -26,6 +26,7 @@ import org.openhab.binding.sonnen.internal.communication.SonnenJsonDataDTO; import org.openhab.binding.sonnen.internal.communication.SonnenJsonPowerMeterDataDTO; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; @@ -61,8 +62,6 @@ public class SonnenHandler extends BaseThingHandler { private boolean sonnenAPIV2 = false; - private int disconnectionCounter = 0; - private Map linkedChannels = new HashMap<>(); public SonnenHandler(Thing thing) { @@ -76,7 +75,7 @@ public class SonnenHandler extends BaseThingHandler { config = getConfigAs(SonnenConfiguration.class); if (config.refreshInterval < 0 || config.refreshInterval > 1000) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Parameter 'refresh Rate' msut be in the range 0-1000!"); + "Parameter 'refresh Rate' must be in the range 0-1000."); return; } if (config.hostIP.isBlank()) { @@ -114,16 +113,15 @@ public class SonnenHandler extends BaseThingHandler { error = serviceCommunication.refreshBatteryConnectionAPICALLV1(); } if (error.isEmpty()) { + if (ThingStatus.OFFLINE.equals(getThing().getStatus())) { + updateStatus(ThingStatus.UNKNOWN); + } if (!ThingStatus.ONLINE.equals(getThing().getStatus())) { updateStatus(ThingStatus.ONLINE); - disconnectionCounter = 0; } } else { - disconnectionCounter++; updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error); - if (disconnectionCounter < 60) { - return true; - } + return false; } return error.isEmpty(); } @@ -161,9 +159,10 @@ public class SonnenHandler extends BaseThingHandler { } private void refreshChannels() { - updateBatteryData(); - for (Channel channel : getThing().getChannels()) { - updateChannel(channel.getUID().getId()); + if (updateBatteryData()) { + for (Channel channel : getThing().getChannels()) { + updateChannel(channel.getUID().getId(), null); + } } } @@ -175,7 +174,7 @@ public class SonnenHandler extends BaseThingHandler { automaticRefreshing = true; } verifyLinkedChannel(channelUID.getId()); - updateChannel(channelUID.getId()); + updateChannel(channelUID.getId(), null); } @Override @@ -188,7 +187,7 @@ public class SonnenHandler extends BaseThingHandler { } } - private void updateChannel(String channelId) { + private void updateChannel(String channelId, @Nullable String putData) { if (isLinked(channelId)) { State state = null; SonnenJsonDataDTO data = serviceCommunication.getBatteryData(); @@ -278,6 +277,28 @@ public class SonnenHandler extends BaseThingHandler { case CHANNELFLOWPRODUCTIONGRIDSTATE: update(OnOffType.from(data.isFlowProductionGrid()), channelId); break; + case CHANNELBATTERYCHARGINGGRID: + if (putData != null) { + serviceCommunication.startStopBatteryCharging(putData); + // put it to true as switch was turned on if it goes into manual mode + if (putData.contains("1")) { + update(OnOffType.from(true), channelId); + } else if (putData.contains("2")) { + update(OnOffType.from(false), channelId); + } + } else { + // Reflect the status of operation mode in the switch + update(OnOffType.from(!data.isInAutomaticMode()), channelId); + } + break; + case CHANNELBATTERYOPERATIONMODE: + if (!data.isInAutomaticMode()) { + state = new StringType("Manual"); + } else if (data.isInAutomaticMode()) { + state = new StringType("Automatic"); + } + update(state, channelId); + break; } } } else { @@ -314,8 +335,23 @@ public class SonnenHandler extends BaseThingHandler { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command == RefreshType.REFRESH) { - updateBatteryData(); - updateChannel(channelUID.getId()); + if (updateBatteryData()) { + updateChannel(channelUID.getId(), null); + } + } + if (channelUID.getId().equals(CHANNELBATTERYCHARGINGGRID)) { + String putData = null; + if (command.equals(OnOffType.ON)) { + // Set battery to manual mode with 1 + putData = "EM_OperatingMode=1"; + } else if (command.equals(OnOffType.OFF)) { + // set battery to automatic mode with 2 + putData = "EM_OperatingMode=2"; + } + if (putData != null) { + logger.debug("Executing {} command", CHANNELBATTERYCHARGINGGRID); + updateChannel(channelUID.getId(), putData); + } } } } diff --git a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJSONCommunication.java b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJSONCommunication.java index 4a028db6036..db03faeb000 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJSONCommunication.java +++ b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJSONCommunication.java @@ -12,7 +12,10 @@ */ package org.openhab.binding.sonnen.internal.communication; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Properties; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -89,6 +92,61 @@ public class SonnenJSONCommunication { return result; } + /** + * Start and Stops the charging of the battery from the grid + * + * @return an empty string if no error occurred, the error message otherwise. + */ + public String startStopBatteryCharging(String putData) { + String result = ""; + String urlStr = "http://" + config.hostIP + "/api/v2/configurations"; + String urlStr2 = "http://" + config.hostIP + "/api/v2/setpoint/charge/" + + Integer.toString(config.chargingPower); + Properties header = createHeader(config.authToken); + try { + // in putData there is 1 or 2 inside to turn on or off the manual mode of the battery + // it will be executed by a change of the switch an either turn on or off the manual mode of the battery + InputStream targetStream = new ByteArrayInputStream(putData.getBytes(StandardCharsets.UTF_8)); + String response = HttpUtil.executeUrl("PUT", urlStr, header, targetStream, + "application/x-www-form-urlencoded", 10000); + logger.debug("ChargingOperationMode = {}", response); + if (response == null) { + throw new IOException("HttpUtil.executeUrl returned null"); + } + batteryData = gson.fromJson(response, SonnenJsonDataDTO.class); + // if battery is put to manual mode + if (config.chargingPower > 10000) { + throw new IllegalArgumentException( + "Max battery charging power in watt needs to be in the range of greater 0 and smaller 10000."); + } + SonnenJsonDataDTO batteryData2 = getBatteryData(); + if (batteryData2.emgetOperationMode() != null && Integer.parseInt(batteryData2.emgetOperationMode()) == 1 + && config.chargingPower > 0 && config.chargingPower <= 10000) { + // start charging + String response2 = HttpUtil.executeUrl("POST", urlStr2, header, null, "application/json", 10000); + logger.debug("ChargingOperationMode = {}", response2); + if (response2 == null) { + throw new IOException("HttpUtil.executeUrl returned null"); + } + } + } catch (IOException | JsonSyntaxException | IllegalArgumentException e) { + logger.debug("Error processiong Put request {}: {}", urlStr, e.getMessage()); + String message = e.getMessage(); + if (message != null && message.contains("WWW-Authenticate header")) { + result = "Given token: " + config.authToken + " is not valid."; + } else if (e.getCause() instanceof IllegalArgumentException) { + result = "Max battery charging power needs to be in the range of greater 0 and smaller 10000. It cannot be: " + + config.chargingPower; + logger.debug("Error in value for battery capacity: {}", e.getMessage()); + } else { + result = "Cannot find service on given IP " + config.hostIP + ". Please verify the IP address!"; + logger.debug("Error in establishing connection: {}", e.getMessage()); + } + batteryData = null; + } + return result; + } + /** * Refreshes the battery connection with the Old API Call. * @@ -146,11 +204,11 @@ public class SonnenJSONCommunication { */ private Properties createHeader(String authToken) { Properties httpHeader = new Properties(); + httpHeader.setProperty("Accept-Encoding", "gzip;q=1.0, compress;q=0.5"); httpHeader.setProperty("Host", config.hostIP); httpHeader.setProperty("Accept", "*/*"); httpHeader.setProperty("Proxy-Connection", "keep-alive"); httpHeader.setProperty("Auth-Token", authToken); - httpHeader.setProperty("Accept-Encoding", "gzip;q=1.0, compress;q=0.5"); return httpHeader; } } diff --git a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJsonDataDTO.java b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJsonDataDTO.java index 2cbc606f897..7615c47cbef 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJsonDataDTO.java +++ b/bundles/org.openhab.binding.sonnen/src/main/java/org/openhab/binding/sonnen/internal/communication/SonnenJsonDataDTO.java @@ -47,6 +47,10 @@ public class SonnenJsonDataDTO { boolean flowProductionGrid; @SerializedName("Pac_total_W") int batteryCurrent; + @SerializedName("EM_OperatingMode") + String emoperatingMode; + @SerializedName("OperatingMode") + private int operatingMode; /** * @return the batteryCurrent @@ -138,4 +142,27 @@ public class SonnenJsonDataDTO { public boolean isFlowProductionGrid() { return flowProductionGrid; } + + /** + * @return the em_operatingMode + */ + public String emgetOperationMode() { + return emoperatingMode; + } + + /** + * @return the operatingMode + */ + public boolean isInAutomaticMode() { + // 2 for automatic mode + if (operatingMode == 2) { + return true; + // 1 for MANUAL mode + } else if (operatingMode == 1) { + return false; + } + // in case of Serialization problem return true, as automatic is the normal state of the battery on most + // cases. + return true; + } } diff --git a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/i18n/sonnen.properties b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/i18n/sonnen.properties index 743806fd67c..a35fa696dd9 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/i18n/sonnen.properties +++ b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/i18n/sonnen.properties @@ -1,5 +1,3 @@ -# add-on - addon.sonnen.name = Sonnen Binding addon.sonnen.description = This binding communicates with a solar battery from sonnen. @@ -16,6 +14,8 @@ thing-type.config.sonnen.sonnenbattery.refreshInterval.label = Refresh Interval thing-type.config.sonnen.sonnenbattery.refreshInterval.description = How often in seconds the sonnen battery should schedule a refresh after a channel is linked to an item. Valid input is 0 - 1000. thing-type.config.sonnen.sonnenbattery.authToken.label = Authentication Token thing-type.config.sonnen.sonnenbattery.authToken.description = Authentication Token which can be found under "Software Integration" if you connect locally to your sonnen battery. If empty the old deprecated API will be used. +thing-type.config.sonnen.sonnenbattery.chargingPower.label=Maximum Charging Power +thing-type.config.sonnen.sonnenbattery.chargingPower.description = Maximum charging power in watt which is supported by sonnenbattery type. Be cautious to not enter a higher value then supported by the battery. Details can be found on the datasheet of your battery. # channel types @@ -47,11 +47,15 @@ channel-type.sonnen.gridFeedIn.label = Grid Feed In channel-type.sonnen.gridFeedIn.description = Indicates the actual current feeding to the Grid. Otherwise 0. channel-type.sonnen.solarProduction.label = Solar Production channel-type.sonnen.solarProduction.description = Indicates the actual production of the Solar system. -channel-type.sonnen.energyImportedStateProduction.label = Imported kWh Production. -channel-type.sonnen.energyImportedStateProduction.description = Indicates the imported kWh Production -channel-type.sonnen.energyExportedStateProduction.label= Exported kWh Production. -channel-type.sonnen.energyExportedStateProduction.description = Indicates the exported kWh Production -channel-type.sonnen.energyImportedStateConsumption.label = Imported kWh Consumption. -channel-type.sonnen.energyImportedStateConsupmtion.description = Indicates the imported kWh Consumption -channel-type.sonnen.energyExportedStateConsumption.label= Exported kWh Consumption. -channel-type.sonnen.energyExportedStateConsumption.description = Indicates the exported kWh Consumption +channel-type.sonnen.energyImportedStateProduction.label = Imported kWh Production +channel-type.sonnen.energyImportedStateProduction.description = Indicates the imported kWh Production. +channel-type.sonnen.energyExportedStateProduction.label= Exported kWh Production +channel-type.sonnen.energyExportedStateProduction.description = Indicates the exported kWh Production. +channel-type.sonnen.energyImportedStateConsumption.label = Imported kWh Consumption +channel-type.sonnen.energyImportedStateConsupmtion.description = Indicates the imported kWh Consumption. +channel-type.sonnen.energyExportedStateConsumption.label= Exported kWh Consumption +channel-type.sonnen.energyExportedStateConsumption.description = Indicates the exported kWh Consumption. +channel-type.sonnen.batteryChargingFromGrid.label=Charging from Grid +channel-type.sonnen.batteryChargingFromGrid.description = Starts and stops the active battery charging from Grid. Note: "Write-API" in Software-Integration page of the local web interface from the sonnen battery must be activated and the given token must be entered in authentication token field of the binding. +channel-type.sonnen.batteryOperationMode.label = Battery Operation Mode +channel-type.sonnen.batteryOperationMode.description = Indicates if the Battery is operating in automatic or manual mode. Manual mode is required for active charging the battery. Assure that the battery is in automatic mode if you don't actively charge the battery.Changing the operation mode is happening with the channel "Start/Stop Active Battery Charging from Grid". diff --git a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml index e4568359198..58177cf9be6 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml @@ -14,6 +14,8 @@ + + @@ -32,7 +34,7 @@ sonnen - 2 + 3 @@ -47,6 +49,14 @@ Authentication Token which can be found under "Software Integration" if you connect locally to your sonnen battery. If empty the old deprecated API will be used. + + + Maximum charging power in watt which is supported by sonnen battery type. Be cautious to not enter a + higher value then supported by the battery. Details can be found on the datasheet of your battery. + + true + 0 + How often in seconds the sonnen battery should schedule a refresh after a channel is linked to an item. @@ -166,4 +176,19 @@ Indicates the exported kWh Consumption. + + Switch + + Starts and stops the active battery charging from Grid. Note: "Write-API" in Software-Integration page of + the local web interface from the sonnen battery must be activated and the given token must be entered in + authentication token field of the binding. + + + String + + Indicates if the Battery is operating in automatic or manual mode. Manual mode is required for active + charging the battery. Assure that the battery is in automatic mode if you don't actively charge the battery.Changing + the operation mode is happening with the channel "Start/Stop Active Battery Charging from Grid" + + diff --git a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml index 2412b9a02bb..b7170488f7d 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml +++ b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml @@ -40,5 +40,13 @@ sonnen:solarProduction + + + sonnen:batteryChargingFromGrid + + + sonnen:batteryOperationMode + +