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.energenie</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,95 @@
# Gembird energenie Binding
This binding integrates the Gembird energenie range of power extenders by using the Energenie Data Exchange Protocol and power reading devices through HTTP interface.
## Supported Things
The Binding supports PM2-LAN, PMS-LAN, PMS2-LAN or PMS-WLAN power extenders as well as PWM-LAN power measurement devices.
## Discovery
Gembird energenie devices don't support autodiscovery.
All Things need to be created manually either in PaperUI or within Things files.
## Binding Configuration
The Binding does not need any specific configuration
## Thing Configuration
The device requires the IP-address and a password as a configuration value in order for the binding to know where to access it and to login to the device.
| Parameter | Description |
|-----------|------------------------------------------------------|
| host | IP-Address of energenie device |
| password | Password to access energenie device, defaults to "1" |
## Channels
The following channels are supported by PM2-LAN, PMS-LAN, PMS2-LAN or PMS-WLAN devices
| channel | type | description |
|----------|--------|----------------------------------------------------|
| socket1 | Switch | This is the control channel for the first socket |
| socket2 | Switch | This is the control channel for the second socket |
| socket3 | Switch | This is the control channel for the third socket |
| socket4 | Switch | This is the control channel for the fourth socket |
PWM-LAN devices support the following channels
| channel | type | description |
|----------|--------------------------|------------------------------------------|
| voltage | Number:ElectricPotential | Channel for output voltage measurement |
| current | Number:ElectricCurrent | Channel for output current measurement |
| power | Number:Power | Channel for output power measurement |
| energy | Number:Energy | channel for output energy measurement |
## Full Example
Things
```
Thing energenie:pm2lan:pm2lan [ host="xxx.xxx.xxx.xxx", password="your password" ]
Thing energenie:pmslan:pmslan [ host="xxx.xxx.xxx.xxx", password="your password" ]
Thing energenie:pms2lan:pms2lan [ host="xxx.xxx.xxx.xxx", password="your password" ]
Thing energenie:pmswlan:pmswlan [ host="xxx.xxx.xxx.xxx", password="your password" ]
Thing energenie:pwmlan:pwmlan [ host="xxx.xxx.xxx.xxx", password="your password" ]
```
Items
```
//Power extenders
Switch Socket1 { channel="energenie:pm2lan:pm2lan:socket1" }
Switch Socket2 { channel="energenie:pm2lan:pm2lan:socket2" }
Switch Socket3 { channel="energenie:pm2lan:pm2lan:socket3" }
Switch Socket4 { channel="energenie:pm2lan:pm2lan:socket4" }
//Power measurement
Number Voltage { channel="energenie:pwmlan:pwmlan:voltage" }
Number Current { channel="energenie:pwmlan:pwmlan:current" }
Number Power { channel="energenie:pwmlan:pwmlan:power" }
Number Energy { channel="energenie:pwmlan:pwmlan:energy" }
```
Sitemap
```
sitemap energenie label="Energenie Devices"
{
Frame {
// Power extenders
Switch item=Socket1
Switch item=Socket2
Switch item=Socket3
Switch item=Socket4
// Power measurement
Number item=Voltage
Number item=Current
Number item=Power
Number item=Energy
}
}
```

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/xsd/maven-4.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.energenie</artifactId>
<name>openHAB Add-ons :: Bundles :: Energenie Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.energenie-${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-energenie" description="Energenie Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.energenie/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,78 @@
/**
* 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.energenie.internal;
import java.util.Collections;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link EnergenieBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class EnergenieBindingConstants {
private static final String BINDING_ID = "energenie";
public static final int TCP_PORT = 5000;
public static final int STATCRYP_LEN = 4;
public static final int CTRLCRYP_LEN = 4;
public static final int KEY_LEN = 8;
public static final int TASK_LEN = 4;
public static final int SOLUTION_LEN = 4;
public static final String STATE_ON = "0x11";
public static final String STATE_ON_NO_VOLTAGE = "0x12";
public static final String STATE_OFF = "0x22";
public static final String STATE_OFF_NO_VOLTAGE = "0x21";
public static final String V21_STATE_ON = "0x41";
public static final String V21_STATE_OFF = "0x82";
public static final String WLAN_STATE_ON = "0x51";
public static final String WLAN_STATE_OFF = "0x92";
public static final byte SWITCH_ON = 0x01;
public static final byte SWITCH_OFF = 0x02;
public static final byte DONT_SWITCH = 0x04;
public static final int SOCKET_COUNT = 4; // AC power sockets, not network ones
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_PM2LAN = new ThingTypeUID(BINDING_ID, "pm2lan");
public static final ThingTypeUID THING_TYPE_PMSLAN = new ThingTypeUID(BINDING_ID, "pmslan");
public static final ThingTypeUID THING_TYPE_PMS2LAN = new ThingTypeUID(BINDING_ID, "pms2lan");
public static final ThingTypeUID THING_TYPE_PMSWLAN = new ThingTypeUID(BINDING_ID, "pmswlan");
public static final ThingTypeUID THING_TYPE_PWMLAN = new ThingTypeUID(BINDING_ID, "pwmlan");
// List of all Channel ids
public static final Pattern CHANNEL_SOCKET = Pattern.compile("socket(\\d)");
public static final String VOLTAGE = "voltage";
public static final String CURRENT = "current";
public static final String POWER = "power";
public static final String ENERGY = "energy";
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_PM2LAN, THING_TYPE_PMSLAN, THING_TYPE_PMS2LAN, THING_TYPE_PMSLAN, THING_TYPE_PWMLAN)
.collect(Collectors.toSet()));
}

View File

@@ -0,0 +1,64 @@
/**
* 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.energenie.internal;
import static org.openhab.binding.energenie.internal.EnergenieBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.energenie.internal.handler.EnergenieHandler;
import org.openhab.binding.energenie.internal.handler.EnergeniePWMHandler;
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.Component;
/**
* The {@link EnergenieHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.energenie", service = ThingHandlerFactory.class)
public class EnergenieHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = EnergenieBindingConstants.SUPPORTED_THING_TYPES_UIDS;
@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 (THING_TYPE_PMSLAN.equals(thingTypeUID)) {
return new EnergenieHandler(thing, EnergenieProtocolEnum.V20);
} else if (THING_TYPE_PM2LAN.equals(thingTypeUID)) {
return new EnergenieHandler(thing, EnergenieProtocolEnum.V20);
} else if (THING_TYPE_PMS2LAN.equals(thingTypeUID)) {
return new EnergenieHandler(thing, EnergenieProtocolEnum.V21);
} else if (THING_TYPE_PMSWLAN.equals(thingTypeUID)) {
return new EnergenieHandler(thing, EnergenieProtocolEnum.WLAN);
} else if (THING_TYPE_PWMLAN.equals(thingTypeUID)) {
return new EnergeniePWMHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,93 @@
/**
* 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.energenie.internal;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EnergeniePWMStateEnum} contains informations for parsing the readState() result.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public enum EnergeniePWMStateEnum {
VOLTAGE("var V = ", 9, 20, 10, SmartHomeUnits.VOLT),
CURRENT("var V = ", 9, 20, 100, SmartHomeUnits.AMPERE),
POWER("var P=", 6, 20, 466, SmartHomeUnits.WATT),
ENERGY("var E=", 6, 20, 25600, SmartHomeUnits.WATT_HOUR);
private final Logger logger = LoggerFactory.getLogger(EnergeniePWMStateEnum.class);
private final String stateResponseSearch;
private final int start;
private final int stop;
private final int divisor;
private final Unit<?> unit;
private EnergeniePWMStateEnum(final String stateResponseSearch, final int start, final int stop, final int divisor,
final Unit<?> unit) {
this.stateResponseSearch = stateResponseSearch;
this.start = start;
this.stop = stop;
this.divisor = divisor;
this.unit = unit;
}
public String getStateResponseSearch() {
return stateResponseSearch;
}
public int getStart() {
return start;
}
public int getStop() {
return stop;
}
public int getDivisor() {
return divisor;
}
public State readState(final String loginResponseString) {
final int findState = loginResponseString.lastIndexOf(stateResponseSearch);
if (findState > 0) {
logger.trace("searchstring {} found at position {}", stateResponseSearch, findState);
final String slicedResponseTmp = loginResponseString.substring(findState + start, findState + stop);
logger.trace("transformed state response = {}", slicedResponseTmp);
final String[] slicedResponse = slicedResponseTmp.split(";");
logger.trace("transformed state response = {} - {}", slicedResponse[0], slicedResponse[1]);
final double value;
try {
value = Double.parseDouble(slicedResponse[0]) / divisor;
return QuantityType.valueOf(value, unit);
} catch (NumberFormatException e) {
logger.debug("Could not Parse State", e);
return UnDefType.UNDEF;
}
} else {
logger.trace("searchstring '{} not found", stateResponseSearch);
return UnDefType.UNDEF;
}
}
}

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.energenie.internal;
import static org.openhab.binding.energenie.internal.EnergenieBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link EnergenieProtocolEnum} contains informations for parsing the readState() result.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public enum EnergenieProtocolEnum {
V20(STATE_ON),
V21(V21_STATE_ON),
WLAN(WLAN_STATE_ON);
private final String statusOn;
private EnergenieProtocolEnum(String statusOn) {
this.statusOn = statusOn;
}
public String getStatusOn() {
return statusOn;
}
}

View File

@@ -0,0 +1,31 @@
/**
* 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.energenie.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link EnergenieConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class EnergenieConfiguration {
/**
* The default refresh interval in Seconds.
*/
public static final int DEFAULT_REFRESH_INTERVAL = 60;
public String host = "";
public String password = "";
}

View File

@@ -0,0 +1,161 @@
/**
* 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.energenie.internal.handler;
import static org.openhab.binding.energenie.internal.EnergenieBindingConstants.*;
import java.io.IOException;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.energenie.internal.EnergenieProtocolEnum;
import org.openhab.binding.energenie.internal.config.EnergenieConfiguration;
import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.library.types.OnOffType;
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.thing.util.ThingHandlerHelper;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EnergenieHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class EnergenieHandler extends BaseThingHandler {
private static final String CHANNEL_SOCKET_PREFIX = "socket";
private final Logger logger = LoggerFactory.getLogger(EnergenieHandler.class);
/**
* Use cache for refresh command to not update again when call is made within 4 seconds of previous call.
*/
private final ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofSeconds(5), this::refreshState);
private final String statusOn;
private @Nullable EnergenieSocket energenieSocket;
private @Nullable ScheduledFuture<?> refreshJob;
private int refreshInterval;
private @Nullable String host;
public EnergenieHandler(final Thing thing, final EnergenieProtocolEnum protocol) {
super(thing);
this.statusOn = protocol.getStatusOn();
}
@Override
public void handleCommand(final ChannelUID channelUID, final Command command) {
if (command instanceof RefreshType) {
refreshCache.getValue();
} else if (command instanceof OnOffType) {
final byte[] ctrl = { DONT_SWITCH, DONT_SWITCH, DONT_SWITCH, DONT_SWITCH };
final Matcher matcher = CHANNEL_SOCKET.matcher(channelUID.getId());
try {
boolean found = false;
if (matcher.find()) {
final int index = Integer.parseInt(matcher.group(1)) - 1;
if (index >= 0 && index < SOCKET_COUNT) {
ctrl[index] = OnOffType.ON.equals(command) ? SWITCH_ON : SWITCH_OFF;
stateUpdate(energenieSocket.sendCommand(ctrl));
found = true;
}
}
if (!found) {
logger.debug("Invalid channel id {}, should be value between 1 and {}", channelUID, SOCKET_COUNT);
}
} catch (final IOException e) {
updateStatus(ThingStatus.OFFLINE);
logger.debug("Couldn't get I/O for the connection to: {}:{}.", host, TCP_PORT, e);
}
}
}
@Override
public void initialize() {
final EnergenieConfiguration config = getConfigAs(EnergenieConfiguration.class);
if (!config.host.isEmpty() && !config.password.isEmpty()) {
refreshInterval = EnergenieConfiguration.DEFAULT_REFRESH_INTERVAL;
host = config.host;
logger.debug("Initializing EnergenieHandler for Host '{}'", config.host);
energenieSocket = new EnergenieSocket(config.host, config.password);
updateStatus(ThingStatus.UNKNOWN);
refreshJob = scheduler.scheduleWithFixedDelay(this::refreshState, 6, refreshInterval, TimeUnit.SECONDS);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Can not access device , IP-Address or password not set");
}
}
@Override
public void dispose() {
logger.debug("EnergenieHandler disposed.");
final ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob != null) {
refreshJob.cancel(true);
this.refreshJob = null;
}
}
private boolean refreshState() {
final EnergenieSocket socket = this.energenieSocket;
if (socket != null) {
try {
stateUpdate(socket.retrieveStatus());
if (thing.getStatus() != ThingStatus.ONLINE && ThingHandlerHelper.isHandlerInitialized(thing)) {
updateStatus(ThingStatus.ONLINE);
}
return true;
} catch (final UnknownHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Can't find host: " + e.getMessage());
} catch (final IOException e) {
logger.debug("Couldn't get I/O for the connection to: {}:{}.", host, TCP_PORT, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Couldn't get I/O for the connection");
} catch (final RuntimeException e) {
logger.debug("Unexpected error", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
}
}
return false;
}
public void stateUpdate(final byte[] status) {
for (int i = 0; i < 4; i++) {
final String socket = CHANNEL_SOCKET_PREFIX + (i + 1);
final String stringStatus = String.format("0x%02x", status[i]);
updateState(socket, OnOffType.from(stringStatus.equals(statusOn)));
}
}
}

View File

@@ -0,0 +1,131 @@
/**
* 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.energenie.internal.handler;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.energenie.internal.EnergeniePWMStateEnum;
import org.openhab.binding.energenie.internal.config.EnergenieConfiguration;
import org.openhab.core.io.net.http.HttpUtil;
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.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EnergeniePWMHandler} is responsible for reading states and update PWM channels.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class EnergeniePWMHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(EnergeniePWMHandler.class);
private static final int HTTP_TIMEOUT_MILLISECONDS = 6000;
private String host = "";
private String password = "";
private int refreshInterval;
private @Nullable ScheduledFuture<?> refreshJob;
public EnergeniePWMHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
scheduler.execute(this::getState);
}
}
@Override
public void initialize() {
EnergenieConfiguration config = getConfigAs(EnergenieConfiguration.class);
if (!config.host.isEmpty() && !config.password.isEmpty()) {
host = config.host;
password = config.password;
refreshInterval = EnergenieConfiguration.DEFAULT_REFRESH_INTERVAL;
logger.debug("Initializing EnergeniePWMHandler for Host '{}'", host);
updateStatus(ThingStatus.ONLINE);
onUpdate();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Can not access device , IP-Address or password not set");
}
}
@Override
public void dispose() {
logger.debug("EnergeniePWMHandler disposed.");
final ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob != null) {
refreshJob.cancel(true);
this.refreshJob = null;
}
}
public synchronized void getState() {
String url = "http://" + host + "/login.html";
String urlParameters = "pw=" + password;
InputStream urlContent = new ByteArrayInputStream(urlParameters.getBytes(StandardCharsets.UTF_8));
String loginResponseString = null;
try {
logger.trace("sending 'POST' request to URL : {}", url);
loginResponseString = HttpUtil.executeUrl("POST", url, urlContent, "TEXT/PLAIN", HTTP_TIMEOUT_MILLISECONDS);
if (loginResponseString != null) {
updateState("voltage", EnergeniePWMStateEnum.VOLTAGE.readState(loginResponseString));
updateState("current", EnergeniePWMStateEnum.CURRENT.readState(loginResponseString));
updateState("power", EnergeniePWMStateEnum.POWER.readState(loginResponseString));
updateState("energy", EnergeniePWMStateEnum.ENERGY.readState(loginResponseString));
try {
HttpUtil.executeUrl("POST", url, HTTP_TIMEOUT_MILLISECONDS);
logger.trace("logout from ip {}", host);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"failed to logout: " + e.getMessage());
}
}
} catch (IOException e) {
logger.debug("energenie: failed to login to {} with ip {}", thing.getUID(), host, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
private synchronized void onUpdate() {
ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob == null || refreshJob.isCancelled()) {
this.refreshJob = scheduler.scheduleWithFixedDelay(this::getState, 5, refreshInterval, TimeUnit.SECONDS);
}
}
}

View File

@@ -0,0 +1,182 @@
/**
* 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.energenie.internal.handler;
import static org.openhab.binding.energenie.internal.EnergenieBindingConstants.*;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class handling the Socket connections.
*
* @author Hans-Jörg Merk - Initial contribution
* @author Hilbrand Bouwkamp - Moved Socket to it's own class
*/
@NonNullByDefault
class EnergenieSocket {
private static final int SOCKET_TIMEOUT_MILLISECONDS = 1500;
private static final byte[] MESSAGE = { 0x11 };
private final Logger logger = LoggerFactory.getLogger(EnergenieSocket.class);
private final String host;
private final byte[] key;
public EnergenieSocket(final String host, final String password) {
this.host = host;
this.key = getKey(password);
}
private static byte[] getKey(final String password) {
final int passwordLength = password.length();
String passwordString = password;
for (int i = 0; i < (8 - passwordLength); i++) {
passwordString = passwordString + " ";
}
return passwordString.getBytes();
}
public synchronized byte[] sendCommand(final byte[] ctrl) throws IOException {
try (final TaskSocket taskSocket = authorize()) {
final OutputStream output = taskSocket.socket.getOutputStream();
final DataInputStream input = new DataInputStream(taskSocket.socket.getInputStream());
if (output == null) {
throw new IOException("No connection");
} else {
if (logger.isTraceEnabled()) {
logger.trace("Control message send to EG (int) '{}' (hex)'{}'", ctrl, HexUtils.bytesToHex(ctrl));
}
output.write(encryptControls(ctrl, taskSocket.task));
output.flush();
readStatus(input, taskSocket);
return updateStatus(taskSocket);
}
}
}
public synchronized byte[] retrieveStatus() throws IOException {
try (final TaskSocket taskSocket = authorize()) {
return updateStatus(taskSocket);
}
}
private TaskSocket authorize() throws IOException {
final TaskSocket taskSocket = new TaskSocket();
final OutputStream output = taskSocket.socket.getOutputStream();
final DataInputStream input = new DataInputStream(taskSocket.socket.getInputStream());
if (output == null) {
throw new IOException("No connection");
}
output.write(MESSAGE);
output.flush();
logger.trace("Start Condition '{}' send to EG", MESSAGE);
input.readFully(taskSocket.task);
if (logger.isTraceEnabled()) {
logger.trace("EG responded with task (int) '{}' (hex) '{}'", taskSocket.task,
HexUtils.bytesToHex(taskSocket.task));
}
final byte[] solutionMessage = calculateSolution(taskSocket.task);
output.write(solutionMessage);
output.flush();
logger.trace("Solution '{}' send to EG", solutionMessage);
readStatus(input, taskSocket);
return taskSocket;
}
private void readStatus(final DataInputStream input, final TaskSocket taskSocket) throws IOException {
input.readFully(taskSocket.statcryp);
if (logger.isTraceEnabled()) {
logger.trace("EG responded with statcryp (int) '{}' (hex) '{}'", taskSocket.statcryp,
HexUtils.bytesToHex(taskSocket.statcryp));
}
}
private byte[] updateStatus(final TaskSocket taskSocket) throws IOException {
final byte[] status = decryptStatus(taskSocket);
if (logger.isTraceEnabled()) {
logger.trace("EG responded with status (int) '{}' (hex) '{}'", status, HexUtils.bytesToHex(status));
}
return status;
}
private byte[] calculateSolution(final byte[] task) {
final int[] uIntTask = new int[4];
for (int i = 0; i < 4; i++) {
uIntTask[i] = Byte.toUnsignedInt(task[i]);
}
final int solutionLoword = (((uIntTask[0] ^ key[2]) * key[0]) ^ (key[6] | (key[4] << 8)) ^ uIntTask[2]);
final byte[] loword = ByteBuffer.allocate(4).putInt(solutionLoword).array();
final int solutionHiword = (((uIntTask[1] ^ key[3]) * key[1]) ^ (key[7] | (key[5] << 8)) ^ uIntTask[3]);
final byte[] hiword = ByteBuffer.allocate(4).putInt(solutionHiword).array();
final byte[] solution = new byte[SOLUTION_LEN];
solution[0] = loword[3];
solution[1] = loword[2];
solution[2] = hiword[3];
solution[3] = hiword[2];
return solution;
}
private byte[] decryptStatus(final TaskSocket taskSocket) {
final byte[] status = new byte[4];
for (int i = 0; i < 4; i++) {
status[i] = (byte) ((((taskSocket.statcryp[3 - i] - key[1]) ^ key[0]) - taskSocket.task[3])
^ taskSocket.task[2]);
}
return status;
}
private byte[] encryptControls(final byte[] controls, final byte[] task) {
final byte[] ctrlcryp = new byte[CTRLCRYP_LEN];
for (int i = 0; i < 4; i++) {
ctrlcryp[i] = (byte) ((((controls[3 - i] ^ task[2]) + task[3]) ^ key[0]) + key[1]);
}
return ctrlcryp;
}
private class TaskSocket implements Closeable {
final Socket socket;
final byte[] task = new byte[TASK_LEN];
final byte[] statcryp = new byte[STATCRYP_LEN];
public TaskSocket() throws UnknownHostException, IOException {
socket = new Socket(host, TCP_PORT);
socket.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
}
@Override
public void close() throws IOException {
socket.close();
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="energenie" 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>Energenie Binding</name>
<description>This is the binding for Energenie.</description>
<author>Hans-Jörg Merk</author>
</binding:binding>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:energenie:config">
<parameter name="host" type="text" required="true">
<label>IP-Address</label>
<description>The IP address of this device</description>
<context>network-address</context>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>The password of this device</description>
<default>1</default>
<context>password</context>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="energenie"
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">
<thing-type id="pm2lan">
<label>EG-PM2-LAN</label>
<description>Energenie programmable power strip with LAN interface</description>
<channels>
<channel id="socket1" typeId="socket1"/>
<channel id="socket2" typeId="socket2"/>
<channel id="socket3" typeId="socket3"/>
<channel id="socket4" typeId="socket4"/>
</channels>
<config-description-ref uri="thing-type:energenie:config"/>
</thing-type>
<thing-type id="pmslan">
<label>EG-PMS-LAN</label>
<description>Energenie programmable power strip with LAN interface</description>
<channels>
<channel id="socket1" typeId="socket1"/>
<channel id="socket2" typeId="socket2"/>
<channel id="socket3" typeId="socket3"/>
<channel id="socket4" typeId="socket4"/>
</channels>
<config-description-ref uri="thing-type:energenie:config"/>
</thing-type>
<thing-type id="pms2lan">
<label>EG-PMS2-LAN</label>
<description>Energenie programmable power strip with LAN interface</description>
<channels>
<channel id="socket1" typeId="socket1"/>
<channel id="socket2" typeId="socket2"/>
<channel id="socket3" typeId="socket3"/>
<channel id="socket4" typeId="socket4"/>
</channels>
<config-description-ref uri="thing-type:energenie:config"/>
</thing-type>
<thing-type id="pmswlan">
<label>EG-PMS-WLAN</label>
<description>Energenie programmable power strip with WLAN interface</description>
<channels>
<channel id="socket1" typeId="socket1"/>
<channel id="socket2" typeId="socket2"/>
<channel id="socket3" typeId="socket3"/>
<channel id="socket4" typeId="socket4"/>
</channels>
<config-description-ref uri="thing-type:energenie:config"/>
</thing-type>
<thing-type id="pwmlan">
<label>EGM-PWM-LAN</label>
<description>Energenie energy meter with LAN interface</description>
<channels>
<channel id="voltage" typeId="voltage"/>
<channel id="current" typeId="current"/>
<channel id="power" typeId="power"/>
<channel id="energy" typeId="energy"/>
</channels>
<config-description-ref uri="thing-type:energenie:config"/>
</thing-type>
<channel-type id="socket1">
<item-type>Switch</item-type>
<label>Socket 1 State</label>
<description>Channel representing the state of socket 1</description>
</channel-type>
<channel-type id="socket2">
<item-type>Switch</item-type>
<label>Socket 2 State</label>
<description>Channel representing the state of socket 2</description>
</channel-type>
<channel-type id="socket3">
<item-type>Switch</item-type>
<label>Socket 3 State</label>
<description>Channel representing the state of socket 3</description>
</channel-type>
<channel-type id="socket4">
<item-type>Switch</item-type>
<label>Socket 4 State</label>
<description>Channel representing the state of socket 4</description>
</channel-type>
<channel-type id="voltage">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>Channel representing the voltage</description>
<state pattern="%.0f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="current">
<item-type>Number:ElectricCurrent</item-type>
<label>Current</label>
<description>Channel representing the current</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="power">
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Channel representing the power consumption</description>
<state pattern="%.0f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="energy">
<item-type>Number:Energy</item-type>
<label>Energy</label>
<description>Channel representing the energy consumption</description>
<state pattern="%.0f %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>