The binding for sonnen communicates with a sonnen battery.
More information about the sonnen battery can be found [here](https://sonnen.de/).
+The binding supports the old deprecated V1 from sonnen as well as V2 which requires an authentication token.
+More information about the V2 API can be found at `http://LOCAL-SONNENBATTERY-SYSTEM-IP/api/doc.html`
## Supported Things
## Thing Configuration
Only the parameter `hostIP` is required; this is the IP address of the sonnen battery in your local network.
+If you want to use the V2 API, which supports more channels, you need to provide the `authToken`.
## Channels
| 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 |
-| flowProductionGridState | Switch | read | Indicates if there is a current flow from Production towards Grid |
+| 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 |
## Full Example
public static final String CHANNELFLOWGRIDBATTERYSTATE = "flowGridBatteryState";
public static final String CHANNELFLOWPRODUCTIONBATTERYSTATE = "flowProductionBatteryState";
public static final String CHANNELFLOWPRODUCTIONGRIDSTATE = "flowProductionGridState";
+
+ // List of new Channel ids for PowerMeter API
+ public static final String CHANNELENERGYIMPORTEDSTATEPRODUCTION = "energyImportedStateProduction";
+ public static final String CHANNELENERGYEXPORTEDSTATEPRODUCTION = "energyExportedStateProduction";
+ public static final String CHANNELENERGYIMPORTEDSTATECONSUMPTION = "energyImportedStateConsumption";
+ public static final String CHANNELENERGYEXPORTEDSTATECONSUMPTION = "energyExportedStateConsumption";
}
public @Nullable String hostIP = null;
public int refreshInterval = 30;
+ public String authToken = "";
}
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.Energy;
import javax.measure.quantity.Power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sonnen.internal.communication.SonnenJSONCommunication;
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.unit.Units;
private boolean automaticRefreshing = false;
+ private boolean sonnenAPIV2 = false;
+
+ private int disconnectionCounter = 0;
+
private Map<String, Boolean> linkedChannels = new HashMap<>();
public SonnenHandler(Thing thing) {
return;
}
+ if (!config.authToken.isEmpty()) {
+ sonnenAPIV2 = true;
+ }
+
serviceCommunication.setConfig(config);
updateStatus(ThingStatus.UNKNOWN);
scheduler.submit(() -> {
* @return true if the update succeeded, false otherwise
*/
private boolean updateBatteryData() {
- String error = serviceCommunication.refreshBatteryConnection();
+ String error = "";
+ if (sonnenAPIV2) {
+ error = serviceCommunication.refreshBatteryConnectionAPICALLV2(arePowerMeterChannelsLinked());
+ } else {
+ error = serviceCommunication.refreshBatteryConnectionAPICALLV1();
+ }
if (error.isEmpty()) {
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 error.isEmpty();
}
}
/**
- * Start the job refreshing the oven status
+ * Start the job refreshing the battery status
*/
private void startAutomaticRefresh() {
ScheduledFuture<?> job = refreshJob;
if (isLinked(channelId)) {
State state = null;
SonnenJsonDataDTO data = serviceCommunication.getBatteryData();
+ // The sonnen API has two sub-channels, e.g. 4_1 and 4_2, one representing consumption and the
+ // other production. E.g. 4_1.kwh_imported represents the total production since the
+ // battery was installed.
+ SonnenJsonPowerMeterDataDTO[] dataPM = null;
+ if (arePowerMeterChannelsLinked()) {
+ dataPM = serviceCommunication.getPowerMeterData();
+ }
+
+ if (dataPM != null && dataPM.length >= 2) {
+ switch (channelId) {
+ case CHANNELENERGYIMPORTEDSTATEPRODUCTION:
+ state = new QuantityType<Energy>(dataPM[0].getKwhImported(), Units.KILOWATT_HOUR);
+ update(state, channelId);
+ break;
+ case CHANNELENERGYEXPORTEDSTATEPRODUCTION:
+ state = new QuantityType<Energy>(dataPM[0].getKwhExported(), Units.KILOWATT_HOUR);
+ update(state, channelId);
+ break;
+ case CHANNELENERGYIMPORTEDSTATECONSUMPTION:
+ state = new QuantityType<Energy>(dataPM[1].getKwhImported(), Units.KILOWATT_HOUR);
+ update(state, channelId);
+ break;
+ case CHANNELENERGYEXPORTEDSTATECONSUMPTION:
+ state = new QuantityType<Energy>(dataPM[1].getKwhExported(), Units.KILOWATT_HOUR);
+ update(state, channelId);
+ break;
+ }
+ }
+
if (data != null) {
switch (channelId) {
case CHANNELBATTERYDISCHARGINGSTATE:
update(OnOffType.from(data.isFlowProductionGrid()), channelId);
break;
}
- } else {
- update(null, channelId);
}
+ } else {
+ update(null, channelId);
+ }
+ }
+
+ private boolean arePowerMeterChannelsLinked() {
+ if (isLinked(CHANNELENERGYIMPORTEDSTATEPRODUCTION)) {
+ return true;
+ } else if (isLinked(CHANNELENERGYEXPORTEDSTATEPRODUCTION)) {
+ return true;
+ } else if (isLinked(CHANNELENERGYIMPORTEDSTATECONSUMPTION)) {
+ return true;
+ } else if (isLinked(CHANNELENERGYEXPORTEDSTATECONSUMPTION)) {
+ return true;
+ } else {
+ return false;
}
}
package org.openhab.binding.sonnen.internal.communication;
import java.io.IOException;
+import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
private Gson gson;
private @Nullable SonnenJsonDataDTO batteryData;
+ private SonnenJsonPowerMeterDataDTO @Nullable [] powerMeterData;
public SonnenJSONCommunication() {
gson = new Gson();
}
/**
- * Refreshes the battery connection.
+ * Refreshes the battery connection with the new API Call V2.
*
* @return an empty string if no error occurred, the error message otherwise.
*/
- public String refreshBatteryConnection() {
+ public String refreshBatteryConnectionAPICALLV2(boolean powerMeter) {
String result = "";
- String urlStr = "http://" + config.hostIP + "/api/v1/status";
+ String urlStr = "http://" + config.hostIP + "/api/v2/status";
+ Properties httpHeader = new Properties();
+ httpHeader = createHeader(config.authToken);
+ try {
+ String response = HttpUtil.executeUrl("GET", urlStr, httpHeader, null, "application/json", 10000);
+ logger.debug("BatteryData = {}", response);
+ if (response == null) {
+ throw new IOException("HttpUtil.executeUrl returned null");
+ }
+ batteryData = gson.fromJson(response, SonnenJsonDataDTO.class);
+
+ if (powerMeter) {
+ response = HttpUtil.executeUrl("GET", "http://" + config.hostIP + "/api/v2/powermeter", httpHeader,
+ null, "application/json", 10000);
+ logger.debug("PowerMeterData = {}", response);
+ if (response == null) {
+ throw new IOException("HttpUtil.executeUrl returned null");
+ }
+
+ powerMeterData = gson.fromJson(response, SonnenJsonPowerMeterDataDTO[].class);
+ }
+ } catch (IOException | JsonSyntaxException e) {
+ logger.debug("Error processiong Get 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 {
+ result = "Cannot find service on given IP " + config.hostIP + ". Please verify the IP address!";
+ logger.debug("Error in establishing connection: {}", e.getMessage());
+ }
+ batteryData = null;
+ powerMeterData = new SonnenJsonPowerMeterDataDTO[] {};
+ }
+ return result;
+ }
+ /**
+ * Refreshes the battery connection with the Old API Call.
+ *
+ * @return an empty string if no error occurred, the error message otherwise.
+ */
+ public String refreshBatteryConnectionAPICALLV1() {
+ String result = "";
+ String urlStr = "http://" + config.hostIP + "/api/v1/status";
try {
String response = HttpUtil.executeUrl("GET", urlStr, 10000);
logger.debug("BatteryData = {}", response);
public @Nullable SonnenJsonDataDTO getBatteryData() {
return this.batteryData;
}
+
+ /**
+ * Returns the actual stored Power Meter Data Array
+ *
+ * @return JSON Data from the Power Meter or null if request failed
+ */
+ public SonnenJsonPowerMeterDataDTO @Nullable [] getPowerMeterData() {
+ return this.powerMeterData;
+ }
+
+ /**
+ * Creates the header for the Get Request
+ *
+ * @return The created Header Properties
+ */
+ private Properties createHeader(String authToken) {
+ Properties httpHeader = new Properties();
+ 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;
+ }
}
/**
* The {@link SonnenJsonDataDTO} is the Java class used to map the JSON
- * response to an Oven request.
+ * response to an Object.
*
* @author Christian Feininger - Initial contribution
*/
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sonnen.internal.communication;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link SonnenJsonPowerMeterDataDTO} is the Java class used to map the JSON
+ * response from the API to a PowerMeter Object.
+ *
+ * @author Christian Feininger - Initial contribution
+ */
+@NonNullByDefault
+public class SonnenJsonPowerMeterDataDTO {
+
+ @SerializedName("kwh_exported")
+ private float kwhExported;
+ @SerializedName("kwh_imported")
+ private float kwhImported;
+
+ /**
+ * @return the kwh_exported
+ */
+ public float getKwhExported() {
+ return kwhExported;
+ }
+
+ /**
+ * @return the kwh_imported
+ */
+ public float getKwhImported() {
+ return kwhImported;
+ }
+}
thing-type.config.sonnen.sonnenbattery.hostIP.description = Please add the IP Address of your sonnen battery.
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.
# channel types
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 id="flowGridBatteryState" typeId="flowGridBatteryState"/>
<channel id="flowProductionBatteryState" typeId="flowProductionBatteryState"/>
<channel id="flowProductionGridState" typeId="flowProductionGridState"/>
+ <channel id="energyImportedStateProduction" typeId="energyImportedStateProduction"/>
+ <channel id="energyExportedStateProduction" typeId="energyExportedStateProduction"/>
+ <channel id="energyImportedStateConsumption" typeId="energyImportedStateConsumption"/>
+ <channel id="energyExportedStateConsumption" typeId="energyExportedStateConsumption"/>
</channels>
+ <properties>
+ <property name="vendor">sonnen</property>
+ <property name="thingTypeVersion">1</property>
+ </properties>
<config-description>
<parameter name="hostIP" type="text" required="true">
<label>IP Address</label>
<description>Please add the IP Address of your sonnen battery.</description>
</parameter>
+ <parameter name="authToken" type="text">
+ <context>service</context>
+ <label>sonnen Authentication Token</label>
+ <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.</description>
+ </parameter>
<parameter name="refreshInterval" type="integer" unit="s" min="0" max="1000">
<label>Refresh Interval</label>
<description>How often in seconds the sonnen battery should schedule a refresh after a channel is linked to an item.
<description>Indicates if there is a current flow from production towards grid.</description>
<state readOnly="true"/>
</channel-type>
+ <channel-type id="energyImportedStateProduction">
+ <item-type>Number:Energy</item-type>
+ <label>kWh Imported Production</label>
+ <description>Indicates the imported kWh Production.</description>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
+ <channel-type id="energyExportedStateProduction">
+ <item-type>Number:Energy</item-type>
+ <label>kWh Exported Production</label>
+ <description>Indicates the exported kWh Production.</description>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
+ <channel-type id="energyImportedStateConsumption">
+ <item-type>Number:Energy</item-type>
+ <label>kWh Imported Consumption</label>
+ <description>Indicates the imported kWh Consumption.</description>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
+ <channel-type id="energyExportedStateConsumption">
+ <item-type>Number:Energy</item-type>
+ <label>kWh Exported Consumption</label>
+ <description>Indicates the exported kWh Consumption.</description>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
+
+ <thing-type uid="sonnen:sonnenbattery">
+
+ <instruction-set targetVersion="1">
+ <add-channel id="energyImportedStateProduction">
+ <type>sonnen:energyImportedStateProduction</type>
+ </add-channel>
+ <add-channel id="energyExportedStateProduction">
+ <type>sonnen:energyExportedStateProduction</type>
+ </add-channel>
+ <add-channel id="energyImportedStateConsumption">
+ <type>sonnen:energyImportedStateConsumption</type>
+ </add-channel>
+ <add-channel id="energyExportedStateConsumption">
+ <type>sonnen:energyExportedStateConsumption</type>
+ </add-channel>
+ </instruction-set>
+ </thing-type>
+</update:update-descriptions>