diff --git a/CODEOWNERS b/CODEOWNERS
index 48f1fc1855175..e8ce863eefa44 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -48,6 +48,7 @@
/bundles/org.openhab.binding.dwdpollenflug/ @DerOetzi
/bundles/org.openhab.binding.dwdunwetter/ @limdul79
/bundles/org.openhab.binding.elerotransmitterstick/ @vbier
+/bundles/org.openhab.binding.energenie/ @hmerk
/bundles/org.openhab.binding.enocean/ @fruggy83
/bundles/org.openhab.binding.enturno/ @klocsson
/bundles/org.openhab.binding.etherrain/ @dfad1469
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 00064b888e0fa..3772d424888d7 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -231,6 +231,11 @@
org.openhab.binding.elerotransmitterstick
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.energenie
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.enocean
diff --git a/bundles/org.openhab.binding.energenie/.classpath b/bundles/org.openhab.binding.energenie/.classpath
new file mode 100644
index 0000000000000..a5d95095ccaaf
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/.classpath
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.energenie/.project b/bundles/org.openhab.binding.energenie/.project
new file mode 100644
index 0000000000000..32000eb2789d5
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.binding.energenie
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.binding.energenie/NOTICE b/bundles/org.openhab.binding.energenie/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/NOTICE
@@ -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
diff --git a/bundles/org.openhab.binding.energenie/README.md b/bundles/org.openhab.binding.energenie/README.md
new file mode 100644
index 0000000000000..602843cbb12fd
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/README.md
@@ -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
+ }
+}
+```
diff --git a/bundles/org.openhab.binding.energenie/pom.xml b/bundles/org.openhab.binding.energenie/pom.xml
new file mode 100644
index 0000000000000..6f9af69ac4fc9
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/pom.xml
@@ -0,0 +1,16 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 2.5.5-SNAPSHOT
+
+
+ org.openhab.binding.energenie
+
+ openHAB Add-ons :: Bundles :: Energenie Binding
+
+
diff --git a/bundles/org.openhab.binding.energenie/src/main/feature/feature.xml b/bundles/org.openhab.binding.energenie/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..7bc80e9c06c0f
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.energenie/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieBindingConstants.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieBindingConstants.java
new file mode 100644
index 0000000000000..fa40899f741b5
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieBindingConstants.java
@@ -0,0 +1,79 @@
+/**
+ * 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.eclipse.smarthome.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 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()));
+
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieHandlerFactory.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieHandlerFactory.java
new file mode 100644
index 0000000000000..9bb7af2ea4979
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieHandlerFactory.java
@@ -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.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
+import org.openhab.binding.energenie.internal.handler.EnergenieHandler;
+import org.openhab.binding.energenie.internal.handler.EnergeniePWMHandler;
+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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergeniePWMStateEnum.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergeniePWMStateEnum.java
new file mode 100644
index 0000000000000..12f17c89001b7
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergeniePWMStateEnum.java
@@ -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.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.library.unit.SmartHomeUnits;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.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;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieProtocolEnum.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieProtocolEnum.java
new file mode 100644
index 0000000000000..1e3709890df6d
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/EnergenieProtocolEnum.java
@@ -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;
+ }
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/config/EnergenieConfiguration.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/config/EnergenieConfiguration.java
new file mode 100644
index 0000000000000..b2c74434c7fcf
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/config/EnergenieConfiguration.java
@@ -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 = "";
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergenieHandler.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergenieHandler.java
new file mode 100644
index 0000000000000..cc6b945adedee
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergenieHandler.java
@@ -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.eclipse.smarthome.core.cache.ExpiringCache;
+import org.eclipse.smarthome.core.library.types.OnOffType;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingStatus;
+import org.eclipse.smarthome.core.thing.ThingStatusDetail;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
+import org.eclipse.smarthome.core.thing.util.ThingHandlerHelper;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+import org.openhab.binding.energenie.internal.EnergenieProtocolEnum;
+import org.openhab.binding.energenie.internal.config.EnergenieConfiguration;
+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 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)));
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergeniePWMHandler.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergeniePWMHandler.java
new file mode 100644
index 0000000000000..5cf140864fe65
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergeniePWMHandler.java
@@ -0,0 +1,132 @@
+/**
+ * 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.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingStatus;
+import org.eclipse.smarthome.core.thing.ThingStatusDetail;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+import org.eclipse.smarthome.io.net.http.HttpUtil;
+import org.openhab.binding.energenie.internal.EnergeniePWMStateEnum;
+import org.openhab.binding.energenie.internal.config.EnergenieConfiguration;
+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);
+ }
+ }
+
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergenieSocket.java b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergenieSocket.java
new file mode 100644
index 0000000000000..ab9e476f60860
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/java/org/openhab/binding/energenie/internal/handler/EnergenieSocket.java
@@ -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.eclipse.smarthome.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();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/binding/binding.xml
new file mode 100644
index 0000000000000..8582bce616318
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/binding/binding.xml
@@ -0,0 +1,10 @@
+
+
+
+ Energenie Binding
+ This is the binding for Energenie.
+ Hans-Jörg Merk
+
+
diff --git a/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/config/config.xml b/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/config/config.xml
new file mode 100644
index 0000000000000..5b79eac6206ee
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/config/config.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ The IP address of this device
+ network-address
+
+
+
+ The password of this device
+ 1
+ password
+
+
+
diff --git a/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..947734b5333ab
--- /dev/null
+++ b/bundles/org.openhab.binding.energenie/src/main/resources/ESH-INF/thing/thing-types.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+ Energenie programmable power strip with LAN interface
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Energenie programmable power strip with LAN interface
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Energenie programmable power strip with LAN interface
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Energenie programmable power strip with WLAN interface
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Energenie energy meter with LAN interface
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Switch
+
+ Channel representing the state of socket 1
+
+
+
+ Switch
+
+ Channel representing the state of socket 2
+
+
+
+ Switch
+
+ Channel representing the state of socket 3
+
+
+
+ Switch
+
+ Channel representing the state of socket 4
+
+
+
+ Number:ElectricPotential
+
+ Channel representing the voltage
+
+
+
+
+ Number:ElectricCurrent
+
+ Channel representing the current
+
+
+
+
+ Number:Power
+
+ Channel representing the power consumption
+
+
+
+
+ Number:Energy
+
+ Channel representing the energy consumption
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 132ec52fca9f6..2566c42672532 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -83,6 +83,7 @@
org.openhab.binding.dwdunwetter
org.openhab.binding.ecobee
org.openhab.binding.elerotransmitterstick
+ org.openhab.binding.energenie
org.openhab.binding.enocean
org.openhab.binding.enturno
org.openhab.binding.etherrain