mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[sonnen] Update to API V2 of vendor and add PowerMeter (#14589)
* Implementing sonnen APi V2 * Fixed issues with powermeter and added two more channels from consumption. Signed-off-by: chingon007 <tron81@gmx.de>
This commit is contained in:
parent
6ab8111f9e
commit
4d811691e9
@ -2,6 +2,8 @@
|
||||
|
||||
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
|
||||
|
||||
@ -12,6 +14,7 @@ More information about the sonnen battery can be found [here](https://sonnen.de/
|
||||
## 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
|
||||
|
||||
@ -35,7 +38,10 @@ The following channels are yet supported:
|
||||
| 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
|
||||
|
||||
|
@ -45,4 +45,10 @@ public class SonnenBindingConstants {
|
||||
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";
|
||||
}
|
||||
|
@ -25,4 +25,5 @@ public class SonnenConfiguration {
|
||||
|
||||
public @Nullable String hostIP = null;
|
||||
public int refreshInterval = 30;
|
||||
public String authToken = "";
|
||||
}
|
||||
|
@ -20,12 +20,14 @@ import java.util.concurrent.ScheduledFuture;
|
||||
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;
|
||||
@ -61,6 +63,10 @@ public class SonnenHandler extends BaseThingHandler {
|
||||
|
||||
private boolean automaticRefreshing = false;
|
||||
|
||||
private boolean sonnenAPIV2 = false;
|
||||
|
||||
private int disconnectionCounter = 0;
|
||||
|
||||
private Map<String, Boolean> linkedChannels = new HashMap<>();
|
||||
|
||||
public SonnenHandler(Thing thing) {
|
||||
@ -82,6 +88,10 @@ public class SonnenHandler extends BaseThingHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.authToken.isEmpty()) {
|
||||
sonnenAPIV2 = true;
|
||||
}
|
||||
|
||||
serviceCommunication.setConfig(config);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
scheduler.submit(() -> {
|
||||
@ -101,13 +111,23 @@ public class SonnenHandler extends BaseThingHandler {
|
||||
* @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();
|
||||
}
|
||||
@ -134,7 +154,7 @@ public class SonnenHandler extends BaseThingHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job refreshing the oven status
|
||||
* Start the job refreshing the battery status
|
||||
*/
|
||||
private void startAutomaticRefresh() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
@ -176,6 +196,35 @@ public class SonnenHandler extends BaseThingHandler {
|
||||
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:
|
||||
@ -234,9 +283,23 @@ public class SonnenHandler extends BaseThingHandler {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
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;
|
||||
@ -38,6 +39,7 @@ public class SonnenJSONCommunication {
|
||||
|
||||
private Gson gson;
|
||||
private @Nullable SonnenJsonDataDTO batteryData;
|
||||
private SonnenJsonPowerMeterDataDTO @Nullable [] powerMeterData;
|
||||
|
||||
public SonnenJSONCommunication() {
|
||||
gson = new Gson();
|
||||
@ -45,14 +47,56 @@ public class SonnenJSONCommunication {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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/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);
|
||||
@ -85,4 +129,28 @@ public class SonnenJSONCommunication {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@ thing-type.config.sonnen.sonnenbattery.hostIP.label = IP Address
|
||||
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
|
||||
|
||||
@ -45,3 +47,11 @@ 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
|
||||
|
@ -25,7 +25,15 @@
|
||||
<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">
|
||||
@ -33,6 +41,12 @@
|
||||
<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.
|
||||
@ -128,4 +142,28 @@
|
||||
<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>
|
||||
|
@ -0,0 +1,23 @@
|
||||
<?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>
|
Loading…
Reference in New Issue
Block a user