added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.senechome</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,82 @@
# Senec Home Binding
Binding to request status information from Senec Home Batteries to allow home automation decisions based on your current energy management.
If your power feed is limited by regulations for example, you can switch on devices if a limited rate was applied by your Senec Home device.
In addition you can switch off devices if the power consumption is getting higher.
Examples: Lights, pool filters, wash machines, ...
## Supported Things
| Thing type id | Name |
|----------------------|-----------------------------------------------|
| senechome | Senec Home Lithium Battery, V2.0, V2.1 and V3 |
## Thing Configuration
demo.things
```
Thing senechome:senechome:pvbattery [ hostname="192.168.0.128", refreshInterval=60, limitationTresholdValue=70, limitationDuration=60 ]
```
If the thing goes online then the connection to the web interface is successful.
In case it is offline you should see an error message.
The refresh interval is 15 seconds by default, but you can optionally provide your own value adding the `refreshInterval` key.
The property `limitationTresholdValue` is used as threshold for channel `powerLimitationState`. It is combined with property `limitationDuration` (seconds) to define a stable status for power limitation. Therefor use `powerLimitationState` to trigger Events (switches) based on your current power limitation.
## Channels
| Channel | Type | Description |
|----------------------|---------|--------------------------------------------------------------------------|
| powerLimitation | percent | How much is your pv generator limited (0% if not limited anyway) |
| powerLimitationState | ON/OFF | Power limitation active (based on configuration) |
| houseConsumption | watt | Current power consumption of your house/living |
| energyProduction | watt | Energy generated by your pv / inverter |
| batteryPower | watt | Energy processed by batterie itself, for example while charging |
| batteryFuelCharge | percent | Fuel charge of your battery (0 - 100%) |
| batteryState | | text describing current action of battery (e.g. CHARGING) |
| gridPower | watt | Grid power level, negative for supply, positive values for drawing power |
| gridPowerDraw | watt | absolute power level of power draw, zero while supplying |
| gridPowerSupply | watt | absolute power level of power supply, zero while drawing |
## Items
Sample:
```
Number SenecPowerLimitation "pv generator limit [%d %%]" <heating> { channel="senechome:senechome:pvbattery:powerLimitation" }
Switch SenecPowerLimitationState "Power limitation active" <switch> { channel="senechome:senechome:pvbattery:powerLimitationState" }
Number SenecHouseConsumption "Current power consumption [%d W]" <energy> { channel="senechome:senechome:pvbattery:houseConsumption" }
Number SenecEnergyProduction "Energy generated by pv [%d W]" <energy> { channel="senechome:senechome:pvbattery:energyProduction" }
Number SenecBatteryPower "Energy processed by battery [%d W]" <energy> { channel="senechome:senechome:pvbattery:batteryPower" }
Number SenecBatteryFuelCharge "State of Charge [%d %%]" <batterylevel> { channel="senechome:senechome:pvbattery:batteryFuelCharge" }
String SenecBatteryState "Current action [%s]" <text> { channel="senechome:senechome:pvbattery:batteryState" }
Number SenecGridPower "Grid power level [%d W]" <energy> { channel="senechome:senechome:pvbattery:gridPower" }
Number SenecGridPowerDraw "Power draw from grid [%d W]" <energy> { channel="senechome:senechome:pvbattery:gridPowerDraw" }
Number SenecGridPowerSupply "Power supply to grid [%d W]" <energy> { channel="senechome:senechome:pvbattery:gridPowerSupply" }
```
## Sitemap
Sample:
```
Text label="Power Grid"{
Frame label="Photovoltaik" {
Default item=SenecPowerLimitation
Default item=SenecPowerLimitationState
Default item=SenecHouseConsumption
Default item=SenecEnergyProduction
Default item=SenecBatteryPower
Default item=SenecBatteryFuelCharge
Default item=SenecBatteryState
Default item=SenecGridPower
Default item=SenecGridPowerDraw
Default item=SenecGridPowerSupply
}
}
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.senechome</artifactId>
<name>openHAB Add-ons :: Bundles :: SenecHome Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.senechome-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-senechome" description="SenecHome Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.senechome/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal;
import java.util.Date;
/**
* The {@link PowerLimitationStatusDTO} class is used as a POJO to
* in-memory persist the last limitation state change.
*
* @author Steven Schwarznau - Initial contribution
*
*/
public class PowerLimitationStatusDTO {
public boolean state = false;
public long time = new Date().getTime();
}

View File

@@ -0,0 +1,151 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal;
/**
* The {@link SenecBatteryStatus} class defines available Senec specific
* battery states.
*
* @author Steven Schwarznau - Initial contribution
*
*/
public enum SenecBatteryStatus {
INITIALSTATE(0, "INITIAL STATE"),
ERROR_INVERTER_COMMUNICATION(1, "ERROR INVERTER COMMUNICATION"),
ERROR_ELECTRICY_METER(2, "ERROR ELECTRICY METER"),
RIPPLE_CONTROL_RECEIVER(3, "RIPPLE CONTROL RECEIVER"),
INITIAL_CHARGE(4, "INITIAL CHARGE"),
MAINTENANCE_CHARGE(5, "MAINTENANCE CHARGE"),
MAINTENANCE_READY(6, "MAINTENANCE READY"),
MAINTENANCE_REQUIRED(7, "MAINTENANCE REQUIRED"),
MAN_SAFETY_CHARGE(8, "MAN. SAFETY CHARGE"),
SAFETY_CHARGE_READY(9, "SAFETY CHARGE READY"),
FULL_CHARGE(10, "FULL CHARGE"),
EQUALIZATION_CHARGE(11, "EQUALIZATION: CHARGE"),
DESULFATATION_CHARGE(12, "DESULFATATION: CHARGE"),
BATTERY_FULL(13, "BATTERY FULL"),
CHARGE(14, "CHARGE"),
BATTERY_EMPTY(15, "BATTERY EMPTY"),
DISCHARGE(16, "DISCHARGE"),
PV_AND_DISCHARGE(17, "PV + DISCHARGE"),
GRID_AND_DISCHARGE(18, "GRID + DISCHARGE"),
PASSIVE(19, "PASSIVE"),
OFF(20, "OFF"),
OWN_CONSUMPTION(21, "OWN CONSUMPTION"),
RESTART(22, "RESTART"),
MAN_EQUALIZATION_CHARGE(23, "MAN. EQUALIZATION: CHARGE"),
MAN_DESULFATATION_CHARGE(24, "MAN. DESULFATATION: CHARGE"),
SAFETY_CHARGE(25, "SAFETY CHARGE"),
BATTERY_PROTECTION_MODE(26, "BATTERY PROTECTION MODE"),
EG_ERROR(27, "EG ERROR"),
EG_CHARGE(28, "EG CHARGE"),
EG_DISCHARGE(29, "EG DISCHARGE"),
EG_PASSIVE(30, "EG PASSIVE"),
EG_PROHIBIT_CHARGE(31, "EG PROHIBIT CHARGE"),
EG_PROHIBIT_DISCHARGE(32, "EG PROHIBIT DISCHARGE"),
EMERGANCY_CHARGE(33, "EMERGANCY CHARGE"),
SOFTWARE_UPDATE(34, "SOFTWARE UPDATE"),
NSP_ERROR(35, "NSP ERROR"),
NSP_ERROR_GRID(36, "NSP ERROR: GRID"),
NSP_ERROR_HARDWRE(37, "NSP ERROR: HARDWRE"),
NO_SERVER_CONNECTION(38, "NO SERVER CONNECTION"),
BMS_ERROR(39, "BMS ERROR"),
MAINTENANCE_FILTER(40, "MAINTENANCE: FILTER"),
SLEEPING_MODE(41, "SLEEPING MODE"),
WAITING_EXCESS(42, "WAITING EXCESS"),
CAPACITY_TEST_CHARGE(43, "CAPACITY TEST: CHARGE"),
CAPACITY_TEST_DISCHARGE(44, "CAPACITY TEST: DISCHARGE"),
MAN_DESULFATATION_WAIT(45, "MAN. DESULFATATION: WAIT"),
MAN_DESULFATATION_READY(46, "MAN. DESULFATATION: READY"),
MAN_DESULFATATION_ERROR(47, "MAN. DESULFATATION: ERROR"),
EQUALIZATION_WAIT(48, "EQUALIZATION: WAIT"),
EMERGANCY_CHARGE_ERROR(49, "EMERGANCY CHARGE: ERROR"),
MAN_EQUALIZATION_WAIT(50, "MAN. EQUALIZATION: WAIT"),
MAN_EQUALIZATION_ERROR(51, "MAN. EQUALIZATION: ERROR"),
MAN_EQUALIZATION_READY(52, "MAN: EQUALIZATION: READY"),
AUTO_DESULFATATION_WAIT(53, "AUTO. DESULFATATION: WAIT"),
ABSORPTION_PHASE(54, "ABSORPTION PHASE"),
DCSWITCH_OFF(55, "DC-SWITCH OFF"),
PEAKSHAVING_WAIT(56, "PEAK-SHAVING: WAIT"),
ERROR_BATTERY_INVERTER(57, "ERROR BATTERY INVERTER"),
NPUERROR(58, "NPU-ERROR"),
BMS_OFFLINE(59, "BMS OFFLINE"),
MAINTENANCE_CHARGE_ERROR(60, "MAINTENANCE CHARGE ERROR"),
MAN_SAFETY_CHARGE_ERROR(61, "MAN. SAFETY CHARGE ERROR"),
SAFETY_CHARGE_ERROR(62, "SAFETY CHARGE ERROR"),
NO_CONNECTION_TO_MASTER(63, "NO CONNECTION TO MASTER"),
LITHIUM_SAFE_MODE_ACTIVE(64, "LITHIUM SAFE MODE ACTIVE"),
LITHIUM_SAFE_MODE_DONE(65, "LITHIUM SAFE MODE DONE"),
BATTERY_VOLTAGE_ERROR(66, "BATTERY VOLTAGE ERROR"),
BMS_DC_SWITCHED_OFF(67, "BMS DC SWITCHED OFF"),
GRID_INITIALIZATION(68, "GRID INITIALIZATION"),
GRID_STABILIZATION(69, "GRID STABILIZATION"),
REMOTE_SHUTDOWN(70, "REMOTE SHUTDOWN"),
OFFPEAKCHARGE(71, "OFFPEAK-CHARGE"),
ERROR_HALFBRIDGE(72, "ERROR HALFBRIDGE"),
BMS_ERROR_OPERATING_TEMPERATURE(73, "BMS: ERROR OPERATING TEMPERATURE"),
FACOTRY_SETTINGS_NOT_FOUND(74, "FACOTRY SETTINGS NOT FOUND"),
BACKUP_POWER_MODE_ACTIVE(75, "BACKUP POWER MODE - ACTIVE"),
BACKUP_POWER_MODE_BATTERY_EMPTY(76, "BACKUP POWER MODE - BATTERY EMPTY"),
BACKUP_POWER_MODE_ERROR(77, "BACKUP POWER MODE ERROR"),
INITIALISING(78, "INITIALISING"),
INSTALLATION_MODE(79, "INSTALLATION MODE"),
GRID_OFFLINE(80, "GRID OFFLINE"),
BMS_UPDATE_NEEDED(81, "BMS UPDATE NEEDED"),
BMS_CONFIGURATION_NEEDED(82, "BMS CONFIGURATION NEEDED"),
INSULATION_TEST(83, "INSULATION TEST"),
SELFTEST(84, "SELFTEST"),
EXTERNAL_CONTROL(85, "EXTERNAL CONTROL"),
ERROR_TEMPERATURESENSOR(86, "ERROR: TEMPERATURESENSOR"),
GRID_OPERATOR_CHARGE_PROHIBITED(87, "GRID OPERATOR: CHARGE PROHIBITED"),
GRID_OPERATOR_DISCHARGE_PROHIBITED(88, "GRID OPERATOR: DISCHARGE PROHIBITED"),
SPARE_CAPACITY(89, "SPARE CAPACITY"),
SELFTEST_ERROR(90, "SELFTEST ERROR"),
EARTH_FAULT(91, "EARTH FAULT"),
UNKNOWN(-1, "UNKNOWN");
private int code;
private String description;
SenecBatteryStatus(int index, String description) {
this.code = index;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
public static SenecBatteryStatus fromCode(int code) {
for (SenecBatteryStatus state : SenecBatteryStatus.values()) {
if (state.code == code) {
return state;
}
}
return SenecBatteryStatus.UNKNOWN;
}
public static String descriptionFromCode(int code) {
for (SenecBatteryStatus state : SenecBatteryStatus.values()) {
if (state.code == code) {
return state.description;
}
}
return SenecBatteryStatus.UNKNOWN.description;
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.openhab.binding.senechome.internal.json.SenecHomeResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link SenecHomeApi} class configures http client and
* performs status requests
*
* @author Steven Schwarznau - Initial contribution
*
*/
@NonNullByDefault
public class SenecHomeApi {
private static final String HTTP_PROTO_PREFIX = "http://";
private final Logger logger = LoggerFactory.getLogger(SenecHomeApi.class);
private final HttpClient httpClient;
private final Gson gson = new Gson();
private String hostname = "";
public SenecHomeApi(HttpClient httpClient) {
this.httpClient = httpClient;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* POST json with empty, but expected fields, to lala.cgi of Senec webinterface
* the response will contain the same fields, but with the corresponding values
*
* To receive new values, just modify the Json objects and add them to the thing channels
*
* @param hostname Hostname or ip address of senec battery
* @return Instance of SenecHomeResponse
* @throws MalformedURLException Configuration/URL is wrong
* @throws IOException Communication failed
*/
public SenecHomeResponse getStatistics()
throws InterruptedException, TimeoutException, ExecutionException, IOException {
String location = HTTP_PROTO_PREFIX + hostname;
Request request = httpClient.newRequest(location);
request.header(HttpHeader.ACCEPT, MimeTypes.Type.APPLICATION_JSON.asString());
request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString());
ContentResponse response = request.method(HttpMethod.POST)
.content(new StringContentProvider(gson.toJson(new SenecHomeResponse()))).send();
if (response.getStatus() == HttpStatus.OK_200) {
return gson.fromJson(response.getContentAsString(), SenecHomeResponse.class);
} else {
logger.trace("Got unexpected response code {}", response.getStatus());
throw new IOException("Got unexpected response code " + response.getStatus());
}
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link SenecHomeBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Steven Schwarznau - Initial contribution
*/
@NonNullByDefault
public class SenecHomeBindingConstants {
private static final String BINDING_ID = "senechome";
private static final String THING_BASE_ID = "senechome";
public static final ThingTypeUID THING_TYPE_SENEC_HOME_BATTERY = new ThingTypeUID(BINDING_ID, THING_BASE_ID);
public static final String CHANNEL_SENEC_POWER_LIMITATION = "powerLimitation";
public static final String CHANNEL_SENEC_POWER_LIMITATION_STATE = "powerLimitationState";
public static final String CHANNEL_SENEC_BATTERY_STATE = "batteryState";
public static final String CHANNEL_SENEC_POWER_CONSUMPTION = "houseConsumption";
public static final String CHANNEL_SENEC_ENERGY_PRODUCTION = "energyProduction";
public static final String CHANNEL_SENEC_BATTERY_POWER = "batteryPower";
public static final String CHANNEL_SENEC_BATTERY_FUEL_CHARGE = "batteryFuelCharge";
public static final String CHANNEL_SENEC_GRID_POWER = "gridPower";
public static final String CHANNEL_SENEC_GRID_POWER_SUPPLY = "gridPowerSupply";
public static final String CHANNEL_SENEC_GRID_POWER_DRAW = "gridPowerDraw";
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal;
/**
* The {@link SenecHomeConfigurationDTO} class contains fields mapping thing configuration parameters.
*
* @author Steven Schwarznau - Initial contribution
*/
public class SenecHomeConfigurationDTO {
public String hostname;
public int refreshInterval = 15;
public int limitationTresholdValue = 95;
public int limitationDuration = 120;
}

View File

@@ -0,0 +1,255 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.senechome.internal.json.SenecHomeResponse;
import org.openhab.core.cache.ExpiringCache;
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.SmartHomeUnits;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SenecHomeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Steven Schwarznau - Initial contribution
*/
@NonNullByDefault
public class SenecHomeHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SenecHomeHandler.class);
private final static String VALUE_TYPE_INT = "u3";
private final static String VALUE_TYPE_UNSIGNED_INT = "u8";
private final static String VALUE_TYPE_FLOAT = "fl";
private @Nullable ScheduledFuture<?> refreshJob;
private @Nullable PowerLimitationStatusDTO limitationStatus = null;
private final @Nullable SenecHomeApi senecHomeApi;
private SenecHomeConfigurationDTO config = new SenecHomeConfigurationDTO();
private final ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofSeconds(5), this::refreshState);
public SenecHomeHandler(Thing thing, HttpClient httpClient) {
super(thing);
this.senecHomeApi = new SenecHomeApi(httpClient);
}
@Override
public void handleRemoval() {
stopJobIfRunning();
super.handleRemoval();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == REFRESH) {
logger.debug("Refreshing {}", channelUID);
refresh();
} else {
logger.trace("The SenecHome-Binding is a read-only binding and can not handle commands");
}
}
@Override
public void dispose() {
stopJobIfRunning();
super.dispose();
}
protected void stopJobIfRunning() {
final ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
this.refreshJob = null;
}
}
@Override
public void initialize() {
config = getConfigAs(SenecHomeConfigurationDTO.class);
senecHomeApi.setHostname(config.hostname);
refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval, TimeUnit.SECONDS);
limitationStatus = null;
}
private void refresh() {
refreshCache.getValue();
}
public @Nullable Boolean refreshState() {
try {
SenecHomeResponse response = senecHomeApi.getStatistics();
logger.trace("received {}", response);
BigDecimal pvLimitation = new BigDecimal(100).subtract(getSenecValue(response.limitation.powerLimitation))
.setScale(0, RoundingMode.HALF_UP);
updateState(SenecHomeBindingConstants.CHANNEL_SENEC_POWER_LIMITATION,
new QuantityType<Dimensionless>(pvLimitation, SmartHomeUnits.PERCENT));
Channel channelLimitationState = getThing()
.getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_POWER_LIMITATION_STATE);
updatePowerLimitationStatus(channelLimitationState,
(100 - pvLimitation.intValue()) <= config.limitationTresholdValue, config.limitationDuration);
Channel channelConsumption = getThing()
.getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_POWER_CONSUMPTION);
updateState(channelConsumption.getUID(),
new QuantityType<Power>(
getSenecValue(response.energy.homePowerConsumption).setScale(2, RoundingMode.HALF_UP),
SmartHomeUnits.WATT));
Channel channelEnergyProduction = getThing()
.getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_ENERGY_PRODUCTION);
updateState(channelEnergyProduction.getUID(),
new QuantityType<Power>(
getSenecValue(response.energy.inverterPowerGeneration).setScale(0, RoundingMode.HALF_UP),
SmartHomeUnits.WATT));
Channel channelBatteryPower = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_POWER);
updateState(channelBatteryPower.getUID(),
new QuantityType<Power>(
getSenecValue(response.energy.batteryPower).setScale(2, RoundingMode.HALF_UP),
SmartHomeUnits.WATT));
Channel channelBatteryFuelCharge = getThing()
.getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_FUEL_CHARGE);
updateState(channelBatteryFuelCharge.getUID(),
new QuantityType<Dimensionless>(
getSenecValue(response.energy.batteryFuelCharge).setScale(0, RoundingMode.HALF_UP),
SmartHomeUnits.PERCENT));
Channel channelBatteryState = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_STATE);
updateBatteryState(channelBatteryState, getSenecValue(response.energy.batteryState).intValue());
updateGridPowerValues(getSenecValue(response.grid.currentGridValue));
updateStatus(ThingStatus.ONLINE);
} catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
logger.warn("Error refreshing source '{}'", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not connect to Senec web interface:" + e.getMessage());
}
return Boolean.TRUE;
}
protected BigDecimal getSenecValue(String value) {
String[] type = value.split("_");
if (VALUE_TYPE_INT.equalsIgnoreCase(type[0])) {
return new BigDecimal(Integer.valueOf(type[1], 16));
} else if (VALUE_TYPE_UNSIGNED_INT.equalsIgnoreCase(type[0])) {
return new BigDecimal(Integer.valueOf(type[1], 16));
} else if (VALUE_TYPE_FLOAT.equalsIgnoreCase(type[0])) {
return parseFloatValue(type[1]);
} else {
logger.warn("Unknown value type [{}]", type[0]);
}
return BigDecimal.ZERO;
}
/**
* Parse the hex coded float value of Senec device and return as BigDecimal
*
* @param value String with hex float
* @return BigDecimal with float value
*/
private static BigDecimal parseFloatValue(String value) {
// sample: value = 43E26188
float f = Float.intBitsToFloat(Integer.parseUnsignedInt(value, 16));
return new BigDecimal(f);
}
protected void updatePowerLimitationStatus(Channel channel, boolean status, int duration) {
if (this.limitationStatus != null) {
if (this.limitationStatus.state == status) {
long stateSince = new Date().getTime() - this.limitationStatus.time;
if (((int) (stateSince / 1000)) < duration) {
// skip updating state (possible flapping state)
return;
} else {
logger.debug("{} longer than required duration {}", status, duration);
}
} else {
this.limitationStatus.state = status;
this.limitationStatus.time = new Date().getTime();
// skip updating state (state changed, possible flapping state)
return;
}
} else {
this.limitationStatus = new PowerLimitationStatusDTO();
this.limitationStatus.state = status;
}
logger.debug("Updating power limitation state {}", status);
updateState(channel.getUID(), status ? OnOffType.ON : OnOffType.OFF);
}
protected void updateBatteryState(Channel channel, int code) {
updateState(channel.getUID(), new StringType(SenecBatteryStatus.descriptionFromCode(code)));
}
protected void updateGridPowerValues(BigDecimal gridTotalValue) {
BigDecimal gridTotal = gridTotalValue.setScale(2, RoundingMode.HALF_UP);
Channel channelGridPower = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER);
if (channelGridPower != null) {
updateState(channelGridPower.getUID(), new QuantityType<Power>(gridTotal, SmartHomeUnits.WATT));
}
Channel channelGridPowerSupply = getThing()
.getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_SUPPLY);
if (channelGridPowerSupply != null) {
BigDecimal gridSupply = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? gridTotal.abs() : BigDecimal.ZERO;
updateState(channelGridPowerSupply.getUID(), new QuantityType<Power>(gridSupply, SmartHomeUnits.WATT));
}
Channel channelGridPowerDraw = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_DRAW);
if (channelGridPowerDraw != null) {
BigDecimal gridDraw = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : gridTotal.abs();
updateState(channelGridPowerDraw.getUID(), new QuantityType<Power>(gridDraw, SmartHomeUnits.WATT));
}
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link SenecHomeHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Steven Schwarznau - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.senechome", service = ThingHandlerFactory.class)
public class SenecHomeHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.singleton(SenecHomeBindingConstants.THING_TYPE_SENEC_HOME_BATTERY);
private HttpClient httpClient;
@Activate
public SenecHomeHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (SenecHomeBindingConstants.THING_TYPE_SENEC_HOME_BATTERY.equals(thingTypeUID)) {
return new SenecHomeHandler(thing, httpClient);
}
return null;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal.json;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
/**
* Json model of senec home devices: This sub model contains values of current workload, i. e. current consumption and
* battery charge.
*
* @author Steven Schwarznau - Initial Contribution
*/
public class SenecHomeEnergy implements Serializable {
private static final long serialVersionUID = -6171687327416551070L;
public @SerializedName("GUI_HOUSE_POW") String homePowerConsumption;
public @SerializedName("GUI_INVERTER_POWER") String inverterPowerGeneration;
public @SerializedName("GUI_BAT_DATA_POWER") String batteryPower;
public @SerializedName("GUI_BAT_DATA_FUEL_CHARGE") String batteryFuelCharge;
public @SerializedName("STAT_STATE") String batteryState;
@Override
public String toString() {
return "SenecHomeEnergy [homePowerConsumption=" + homePowerConsumption + ", inverterPowerGeneration="
+ inverterPowerGeneration + ", batteryPower=" + batteryPower + ", batteryFuelCharge="
+ batteryFuelCharge + ", batteryState=" + batteryState + "]";
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal.json;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
/**
* Json model of senec home devices: This sub model provides the current power limitation by the inverter.
*
* @author Steven Schwarznau - Initial Contribution
*/
public class SenecHomeGrid implements Serializable {
private static final long serialVersionUID = -7479338321370375451L;
/**
* grid value indicating the current power draw (for values larger zero) or supply (for negative values)
*/
public @SerializedName("P_TOTAL") String currentGridValue;
@Override
public String toString() {
return "SenecHomeGrid [currentGridValue=" + currentGridValue + "]";
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal.json;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
/**
* Json model of senec home devices: This sub model contains grid related power values.
*
* @author Steven Schwarznau - Initial Contribution
*/
public class SenecHomeLimitation implements Serializable {
private static final long serialVersionUID = -8990871346958824085L;
public @SerializedName("POWER_RATIO") String powerLimitation;
@Override
public String toString() {
return "SenecHomePower [powerLimitation=" + powerLimitation + "]";
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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.senechome.internal.json;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
/**
* Json model of senec home devices rebuilt by analyzing the api.
*
* @author Steven Schwarznau - Initial Contribution
*/
public class SenecHomeResponse implements Serializable {
private static final long serialVersionUID = 5302080655053778494L;
public @SerializedName("PV1") SenecHomeLimitation limitation = new SenecHomeLimitation();
public @SerializedName("ENERGY") SenecHomeEnergy energy = new SenecHomeEnergy();
public @SerializedName("PM1OBJ1") SenecHomeGrid grid = new SenecHomeGrid();
@Override
public String toString() {
return "SenecHomeResponse [limitation=" + limitation + ", energy=" + energy + ", grid=" + grid + "]";
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="senechome" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>SenecHome Binding</name>
<description>This is the binding for SenecHome.</description>
<author>Steven Schwarznau and Korbinian Probst</author>
</binding:binding>

View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="senechome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Sample Thing Type -->
<thing-type id="senechome">
<label>Senec Home</label>
<description>Senec Home</description>
<channels>
<channel id="powerLimitation" typeId="powerLimitation"/>
<channel id="powerLimitationState" typeId="powerLimitationState"/>
<channel id="houseConsumption" typeId="houseConsumption"/>
<channel id="energyProduction" typeId="energyProduction"/>
<channel id="batteryPower" typeId="batteryPower"/>
<channel id="batteryFuelCharge" typeId="batteryFuelCharge"/>
<channel id="batteryState" typeId="system.battery-level"/>
<channel id="gridPower" typeId="gridPower"/>
<channel id="gridPowerSupply" typeId="gridPowerSupply"/>
<channel id="gridPowerDraw" typeId="gridPowerDraw"/>
</channels>
<config-description>
<parameter name="hostname" type="text" required="true">
<label>Hostname/IP Address</label>
<description>Enter the hostname or ip address of your Senec Home device</description>
<context>network-address</context>
</parameter>
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>Rate of refreshing details (in s)</description>
<default>15</default>
</parameter>
<parameter name="limitationTresholdValue" type="integer" min="0" max="100" unit="%">
<label>Limitation Treshold</label>
<description>Treshold in percent, defines when limitation state is enabled</description>
<default>95</default>
</parameter>
<parameter name="limitationDuration" type="integer" unit="s" min="1" max="1200">
<label>Limitation Duration</label>
<description>Duration of stable values until state is changed, defined in seconds</description>
<default>120</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="powerLimitation">
<item-type>Number:Dimensionless</item-type>
<label>Power Limitation</label>
<category>Energy</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="powerLimitationState">
<item-type>Switch</item-type>
<label>Limitation State</label>
<category>Energy</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="houseConsumption">
<item-type>Number:Power</item-type>
<label>Home Power Consumption</label>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="batteryPower">
<item-type>Number:Power</item-type>
<label>Battery Power</label>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="batteryFuelCharge">
<item-type>Number:Dimensionless</item-type>
<label>Battery Fuel</label>
<category>Battery</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="energyProduction">
<item-type>Number:Power</item-type>
<label>Solar Production</label>
<category>Energy</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="gridPower">
<item-type>Number:Power</item-type>
<label>Grid Power</label>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="gridPowerSupply">
<item-type>Number:Power</item-type>
<label>Grid Supply</label>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="gridPowerDraw">
<item-type>Number:Power</item-type>
<label>Grid Draw</label>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
</thing:thing-descriptions>