diff --git a/bundles/org.openhab.binding.somfymylink/.classpath b/bundles/org.openhab.binding.somfymylink/.classpath
new file mode 100644
index 0000000000000..39abf1c5e9102
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/.classpath
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.somfymylink/.project b/bundles/org.openhab.binding.somfymylink/.project
new file mode 100644
index 0000000000000..104819095ccf0
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.binding.somfymylink
+
+
+
+
+
+ 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.somfymylink/NOTICE b/bundles/org.openhab.binding.somfymylink/NOTICE
new file mode 100644
index 0000000000000..4c20ef446c1e4
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/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/openhab2-addons
diff --git a/bundles/org.openhab.binding.somfymylink/README.md b/bundles/org.openhab.binding.somfymylink/README.md
new file mode 100644
index 0000000000000..efbfe951511f9
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/README.md
@@ -0,0 +1,52 @@
+# Binding
+
+_Give some details about what this binding is meant for - a protocol, system, specific device._
+
+_If possible, provide some resources like pictures, a YouTube video, etc. to give an impression of what can be done with this binding. You can place such resources into a `doc` folder next to this README.md._
+
+## Supported Things
+
+_Please describe the different supported things / devices within this section._
+_Which different types are supported, which models were tested etc.?_
+_Note that it is planned to generate some part of this based on the XML files within ```ESH-INF/thing``` of your binding._
+
+## Discovery
+
+_Describe the available auto-discovery features here. Mention for what it works and what needs to be kept in mind when using it._
+
+## Binding Configuration
+
+_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it. In this section, you should link to this file and provide some information about the options. The file could e.g. look like:_
+
+```
+# Configuration for the Philips Hue Binding
+#
+# Default secret key for the pairing of the Philips Hue Bridge.
+# It has to be between 10-40 (alphanumeric) characters
+# This may be changed by the user for security reasons.
+secret=EclipseSmartHome
+```
+
+_Note that it is planned to generate some part of this based on the information that is available within ```ESH-INF/binding``` of your binding._
+
+_If your binding does not offer any generic configurations, you can remove this section completely._
+
+## Thing Configuration
+
+_Describe what is needed to manually configure a thing, either through the (Paper) UI or via a thing-file. This should be mainly about its mandatory and optional configuration parameters. A short example entry for a thing file can help!_
+
+_Note that it is planned to generate some part of this based on the XML files within ```ESH-INF/thing``` of your binding._
+
+## Channels
+
+_Here you should provide information about available channel types, what their meaning is and how they can be used._
+
+_Note that it is planned to generate some part of this based on the XML files within ```ESH-INF/thing``` of your binding._
+
+## Full Example
+
+_Provide a full usage example based on textual configuration files (*.things, *.items, *.sitemap)._
+
+## Any custom content here!
+
+_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_
diff --git a/bundles/org.openhab.binding.somfymylink/pom.xml b/bundles/org.openhab.binding.somfymylink/pom.xml
new file mode 100644
index 0000000000000..251f442da7394
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 2.5.0-SNAPSHOT
+
+
+ org.openhab.binding.somfymylink
+
+ openHAB Add-ons :: Bundles :: Somfy MyLink Binding
+
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.0
+ compile
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ 4.2.0
+ maven-plugin
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/SomfyMyLinkBindingConstants.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/SomfyMyLinkBindingConstants.java
new file mode 100644
index 0000000000000..d0f971995aaf3
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/SomfyMyLinkBindingConstants.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkBindingConstants {
+
+ private static final String BINDING_ID = "somfymylink";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_SHADE = new ThingTypeUID(BINDING_ID, "shade");
+ public static final ThingTypeUID THING_TYPE_SCENE = new ThingTypeUID(BINDING_ID, "scene");
+ public static final ThingTypeUID THING_TYPE_MYLINK = new ThingTypeUID(BINDING_ID, "mylink");
+
+ // List of all Channel ids
+ public static final String CHANNEL_SHADELEVEL = "shadelevel";
+ public static final String CHANNEL_SCENECONTROL = "scenecontrol";
+ public static final String CHANNEL_SCENES = "sceneid";
+
+
+ // Thing config properties
+ public static final String TARGET_ID = "targetId";
+ public static final String SCENE_ID = "sceneId";
+
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/SomfyMyLinkHandlerFactory.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/SomfyMyLinkHandlerFactory.java
new file mode 100644
index 0000000000000..ac52e0b920acc
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/SomfyMyLinkHandlerFactory.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal;
+
+import static org.openhab.binding.somfymylink.internal.SomfyMyLinkBindingConstants.*;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.Bridge;
+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.somfymylink.internal.handler.SomfyMyLinkBridgeHandler;
+import org.openhab.binding.somfymylink.internal.handler.SomfyMyLinkStateDescriptionOptionsProvider;
+import org.openhab.binding.somfymylink.internal.handler.SomfySceneHandler;
+import org.openhab.binding.somfymylink.internal.handler.SomfyShadeHandler;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link SomfyMyLinkHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.somfymylink", service = ThingHandlerFactory.class)
+public class SomfyMyLinkHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = new HashSet<>(
+ Arrays.asList(THING_TYPE_MYLINK, THING_TYPE_SHADE, THING_TYPE_SCENE));
+
+ public static final Set DISCOVERABLE_DEVICE_TYPES_UIDS = new HashSet<>(
+ Arrays.asList(THING_TYPE_SHADE, THING_TYPE_SCENE));
+
+ @Nullable
+ private SomfyMyLinkStateDescriptionOptionsProvider stateDescriptionProvider;
+
+ @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 (thingTypeUID.equals(THING_TYPE_MYLINK)) {
+ return new SomfyMyLinkBridgeHandler((Bridge) thing, stateDescriptionProvider);
+ }
+ if (THING_TYPE_SHADE.equals(thingTypeUID)) {
+ return new SomfyShadeHandler(thing);
+ }
+ if (THING_TYPE_SCENE.equals(thingTypeUID)) {
+ return new SomfySceneHandler(thing);
+ }
+
+ return null;
+ }
+
+ @Reference
+ protected void setDynamicStateDescriptionProvider(SomfyMyLinkStateDescriptionOptionsProvider provider) {
+ this.stateDescriptionProvider = provider;
+ }
+
+ protected void unsetDynamicStateDescriptionProvider(SomfyMyLinkStateDescriptionOptionsProvider provider) {
+ this.stateDescriptionProvider = null;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/config/SomfyMyLinkConfiguration.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/config/SomfyMyLinkConfiguration.java
new file mode 100644
index 0000000000000..bc1d965c642b1
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/config/SomfyMyLinkConfiguration.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SomfyMyLinkConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkConfiguration {
+ public String ipAddress = "";
+
+ public String systemId = "";
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/discovery/SomfyMyLinkDeviceDiscoveryService.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/discovery/SomfyMyLinkDeviceDiscoveryService.java
new file mode 100644
index 0000000000000..77804bfe6373c
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/discovery/SomfyMyLinkDeviceDiscoveryService.java
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.discovery;
+
+import static org.openhab.binding.somfymylink.internal.SomfyMyLinkBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService;
+import org.eclipse.smarthome.config.discovery.DiscoveryResult;
+import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder;
+import org.eclipse.smarthome.core.thing.ThingStatus;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.ThingUID;
+import org.openhab.binding.somfymylink.internal.SomfyMyLinkHandlerFactory;
+import org.openhab.binding.somfymylink.internal.handler.SomfyMyLinkBridgeHandler;
+import org.openhab.binding.somfymylink.internal.handler.SomfyMyLinkException;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkScene;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkShade;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkDeviceDiscoveryService extends AbstractDiscoveryService {
+
+ private static final int DISCOVERY_REFRESH_SEC = 1800;
+
+ private final Logger logger = LoggerFactory.getLogger(SomfyMyLinkDeviceDiscoveryService.class);
+
+ @Nullable
+ private ScheduledFuture> scanTask;
+
+ private SomfyMyLinkBridgeHandler mylinkHandler;
+
+ @Nullable
+ private ScheduledFuture> discoveryJob;
+
+ public SomfyMyLinkDeviceDiscoveryService(SomfyMyLinkBridgeHandler mylinkHandler) throws IllegalArgumentException {
+ super(SomfyMyLinkHandlerFactory.DISCOVERABLE_DEVICE_TYPES_UIDS, 10);
+
+ this.mylinkHandler = mylinkHandler;
+ }
+
+ @Override
+ @Activate
+ public void activate(@Nullable Map<@NonNull String, @Nullable Object> configProperties) {
+ super.activate(configProperties);
+ }
+
+ @Override
+ @Deactivate
+ public void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ logger.debug("Starting Somfy My Link background discovery");
+
+ if (discoveryJob == null || discoveryJob.isCancelled()) {
+ discoveryJob = scheduler.scheduleWithFixedDelay(this::runDiscovery, 10, DISCOVERY_REFRESH_SEC,
+ TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ logger.debug("Stopping Somfy MyLink background discovery");
+ if (discoveryJob != null && !discoveryJob.isCancelled()) {
+ discoveryJob.cancel(true);
+ discoveryJob = null;
+ }
+ }
+
+ @Override
+ protected void startScan() {
+ runDiscovery();
+ }
+
+ private synchronized void runDiscovery() {
+ logger.debug("Starting scanning for things...");
+
+ if (this.scanTask == null || this.scanTask.isDone()) {
+ this.scanTask = scheduler.schedule(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ discoverDevices();
+ } catch (SomfyMyLinkException e) {
+ logger.info("Error scanning for devices: " + e.getMessage(), e);
+
+ if (scanListener != null) {
+ scanListener.onErrorOccurred(e);
+ }
+ }
+ }
+ }, 0, TimeUnit.SECONDS);
+ }
+ }
+
+ private void discoverDevices() throws SomfyMyLinkException {
+ if(this.mylinkHandler.getThing().getStatus() != ThingStatus.ONLINE) {
+ logger.debug("Skipping device discover as bridge is {}", this.mylinkHandler.getThing().getStatus());
+ return;
+ }
+
+ // get the shade list
+ SomfyMyLinkShade[] shades = this.mylinkHandler.getShadeList();
+
+ for (SomfyMyLinkShade shade : shades) {
+ String id = shade.getTargetID();
+ String label = "Somfy Shade " + shade.getName();
+
+ if(id != null) {
+ logger.info("Adding device {}", id);
+ notifyShadeDiscovery(THING_TYPE_SHADE, id, label);
+ }
+ }
+
+ SomfyMyLinkScene[] scenes = this.mylinkHandler.getSceneList();
+
+ for (SomfyMyLinkScene scene : scenes) {
+ String id = scene.getTargetID();
+ String label = "Somfy Scene " + scene.getName();
+
+ if(id != null) {
+ logger.info("Adding device {}", id);
+ notifySceneDiscovery(THING_TYPE_SCENE, id, label);
+ }
+ }
+ }
+
+ private void notifyShadeDiscovery(ThingTypeUID thingTypeUID, String targetId, String label) {
+ if (StringUtils.isEmpty(targetId)) {
+ logger.info("Discovered {} with no integration ID", label);
+ return;
+ }
+
+ ThingUID bridgeUID = this.mylinkHandler.getThing().getUID();
+ ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, targetId);
+
+ Map properties = new HashMap<>();
+
+ properties.put(TARGET_ID, targetId);
+
+ DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(label)
+ .withProperties(properties).withRepresentationProperty(TARGET_ID).build();
+
+ thingDiscovered(result);
+
+ logger.debug("Discovered {}", uid);
+ }
+
+ private void notifySceneDiscovery(ThingTypeUID thingTypeUID, String sceneId, String label) {
+ if (StringUtils.isEmpty(sceneId)) {
+ logger.info("Discovered {} with no scene ID", label);
+ return;
+ }
+
+ ThingUID bridgeUID = this.mylinkHandler.getThing().getUID();
+ ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sceneId);
+
+ Map properties = new HashMap<>();
+
+ properties.put(SCENE_ID, sceneId);
+
+ DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(label)
+ .withProperties(properties).withRepresentationProperty(SCENE_ID).build();
+
+ thingDiscovered(result);
+
+ logger.debug("Discovered {}", uid);
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkBridgeHandler.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkBridgeHandler.java
new file mode 100644
index 0000000000000..386da9e56eeb7
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkBridgeHandler.java
@@ -0,0 +1,517 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.smarthome.config.discovery.DiscoveryService;
+import org.eclipse.smarthome.core.library.types.StringType;
+import org.eclipse.smarthome.core.thing.Bridge;
+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.BaseBridgeHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+import org.eclipse.smarthome.core.types.StateOption;
+import org.openhab.binding.somfymylink.internal.SomfyMyLinkBindingConstants;
+import org.openhab.binding.somfymylink.internal.config.SomfyMyLinkConfiguration;
+import org.openhab.binding.somfymylink.internal.discovery.SomfyMyLinkDeviceDiscoveryService;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkErrorResponse;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkPingResponse;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkResponseBase;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkScene;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkScenesResponse;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkShade;
+import org.openhab.binding.somfymylink.internal.model.SomfyMyLinkShadesResponse;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.openhab.binding.somfymylink.internal.SomfyMyLinkBindingConstants.*;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link SomfyMyLinkBridgeHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkBridgeHandler extends BaseBridgeHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(SomfyMyLinkBridgeHandler.class);
+
+ @Nullable
+ private SomfyMyLinkConfiguration config;
+
+ private static final int HEARTBEAT_MINUTES = 2;
+ private static final int MYLINK_PORT = 44100;
+ private static final int MYLINK_DEFAULT_TIMEOUT = 5000;
+ private static final Object CONNECTION_LOCK = new Object();
+ private static final int CONNECTION_DELAY = 1000;
+
+ private static final String MYLINK_COMMAND_TEMPLATE = "{\"id\": %1$s, \"method\": \"%2$s\",\"params\": {\"%3$s\": %4$s,\"auth\": \"%5$s\"}}";
+
+ @Nullable
+ private ScheduledFuture> heartbeat;
+
+ @Nullable
+ private ServiceRegistration discoveryServiceRegistration;
+
+ private SomfyMyLinkDeviceDiscoveryService discovery;
+
+ @Nullable
+ private SomfyMyLinkStateDescriptionOptionsProvider stateDescriptionProvider;
+
+ // Gson & parser
+ private final Gson gson = new Gson();
+
+ public SomfyMyLinkBridgeHandler(Bridge bridge, @Nullable SomfyMyLinkStateDescriptionOptionsProvider stateDescriptionProvider) {
+ super(bridge);
+
+ this.discovery = new SomfyMyLinkDeviceDiscoveryService(this);
+ this.stateDescriptionProvider = stateDescriptionProvider;
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("Command received on mylink {}", command);
+
+ try {
+ if (CHANNEL_SCENES.equals(channelUID.getId())) {
+ if (command instanceof RefreshType) {
+ // TODO: handle data refresh
+ return;
+ }
+
+ if (CHANNEL_SCENES.equals(channelUID.getId()) && command instanceof StringType) {
+ Integer sceneId = Integer.decode(command.toString());
+ commandScene(sceneId);
+ }
+ }
+ } catch (SomfyMyLinkException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing mylink");
+ config = getThing().getConfiguration().as(SomfyMyLinkConfiguration.class);
+
+ if (validConfiguration(config)) {
+ this.discoveryServiceRegistration = this.bundleContext.registerService(DiscoveryService.class, this.discovery, null);
+ this.discovery.activate(null);
+
+ // kick off the bridge connection process
+ this.scheduler.schedule(new Runnable() {
+ @Override
+ public void run() {
+ connect();
+ }
+ }, 0, TimeUnit.SECONDS);
+ }
+ }
+
+ private boolean validConfiguration(@Nullable SomfyMyLinkConfiguration config) {
+ if (config == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "mylink configuration missing");
+ return false;
+ }
+
+ if (StringUtils.isEmpty(config.ipAddress)) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "mylink address not specified");
+ return false;
+ }
+
+ return true;
+ }
+
+ private void connect() {
+ try {
+ if(config == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "mylink config not specified");
+ return;
+ }
+
+ if (StringUtils.isEmpty(config.ipAddress) || StringUtils.isEmpty(config.systemId)) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "mylink config not specified");
+ return;
+ }
+
+ // start the keepalive process
+ ensureKeepAlive();
+
+ logger.debug("Connecting to mylink at {}", config.ipAddress);
+
+ // send a ping
+ sendPing();
+
+ logger.debug("Connected to mylink at {}", config.ipAddress);
+
+ updateStatus(ThingStatus.ONLINE);
+
+ } catch (SomfyMyLinkException e) {
+ logger.debug("Problem connecting to mylink, bridge OFFLINE");
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ private void ensureKeepAlive()
+ {
+ if(heartbeat == null) {
+ logger.debug("Starting keepalive job in {} min, every {} min", HEARTBEAT_MINUTES, HEARTBEAT_MINUTES);
+ //heartbeat = scheduler.scheduleWithFixedDelay(this::sendKeepAlive, HEARTBEAT_MINUTES, HEARTBEAT_MINUTES, TimeUnit.MINUTES);
+
+ heartbeat = this.scheduler.scheduleWithFixedDelay(new Runnable() {
+ @Override
+ public void run() {
+ sendKeepAlive();
+ }
+ }, 1, 1, TimeUnit.MINUTES);
+
+ }
+ }
+
+ private void disconnect() {
+ logger.debug("Disconnecting from mylink");
+
+ if (heartbeat != null) {
+ logger.debug("Cancelling keepalive job");
+ heartbeat.cancel(true);
+ } else {
+ logger.debug("Keepalive was not active");
+ }
+ }
+
+ private void sendKeepAlive() {
+ try {
+ logger.debug("Keep alive triggered");
+
+ if(getThing().getStatus() != ThingStatus.ONLINE) {
+ // try connecting
+ logger.debug("Bridge offline, trying to connect");
+ connect();
+ } else {
+ // send a ping
+ sendPing();
+ logger.debug("Keep alive succeeded");
+ }
+ } catch (SomfyMyLinkException e) {
+ logger.debug("Problem pinging mylink during keepalive");
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ public SomfyMyLinkShade[] getShadeList() throws SomfyMyLinkException {
+ String command = buildShadeCommand("mylink.status.info", "*.*");
+
+ SomfyMyLinkShadesResponse response = (SomfyMyLinkShadesResponse) sendCommandWithResponse(command, SomfyMyLinkShadesResponse.class);
+
+ if (response != null) {
+ return response.getResult();
+ }
+
+ return new SomfyMyLinkShade[0];
+ }
+
+ public void sendPing() throws SomfyMyLinkException {
+ String command = buildShadeCommand("mylink.status.ping", "*.*");
+
+ SomfyMyLinkPingResponse response = (SomfyMyLinkPingResponse) sendCommandWithResponse(command, SomfyMyLinkPingResponse.class);
+
+ if (response != null) {
+ return;
+ }
+
+ return;
+ }
+
+ public SomfyMyLinkScene[] getSceneList() throws SomfyMyLinkException {
+ String command = buildShadeCommand("mylink.scene.list", "*.*");
+
+ SomfyMyLinkScenesResponse response = (SomfyMyLinkScenesResponse) sendCommandWithResponse(command, SomfyMyLinkScenesResponse.class);
+
+ if (response != null) {
+ List options = new ArrayList<>();
+ for (SomfyMyLinkScene scene : response.result) {
+ options.add(new StateOption(scene.getTargetID(), scene.getName()));
+ }
+
+ logger.debug("Setting {} options on bridge", options.size());
+
+ stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), SomfyMyLinkBindingConstants.CHANNEL_SCENES), options);
+
+ return response.getResult();
+ }
+
+ return new SomfyMyLinkScene[0];
+ }
+
+ public void commandShadeUp(String targetId) throws SomfyMyLinkException {
+ try {
+ String command = buildShadeCommand("mylink.move.up", targetId);
+ sendCommand(command);
+ } catch (SomfyMyLinkException e) {
+ logger.info("Error commanding shade up: " + e.getMessage());
+ throw new SomfyMyLinkException("Error commanding shade up", e);
+ }
+ }
+
+ public void commandShadeDown(String targetId) throws SomfyMyLinkException {
+ try {
+ String command = buildShadeCommand("mylink.move.down", targetId);
+ sendCommand(command);
+ } catch (SomfyMyLinkException e) {
+ logger.info("Error commanding shade down: " + e.getMessage());
+ throw new SomfyMyLinkException("Error commanding shade down", e);
+ }
+ }
+
+ public void commandShadeStop(String targetId) throws SomfyMyLinkException {
+ try {
+ String command = buildShadeCommand("mylink.move.stop", targetId);
+ sendCommand(command);
+ } catch (SomfyMyLinkException e) {
+ logger.info("Error commanding shade stop: " + e.getMessage());
+ throw new SomfyMyLinkException("Error commanding shade stop", e);
+ }
+ }
+
+ public void commandScene(Integer sceneId) throws SomfyMyLinkException {
+ try {
+ String command = buildSceneCommand("mylink.scene.run", sceneId);
+ sendCommand(command);
+ } catch (SomfyMyLinkException e) {
+ logger.info("Error commanding shade stop: " + e.getMessage());
+ throw new SomfyMyLinkException("Error commanding shade stop", e);
+ }
+ }
+
+ private void sendCommand(String command) throws SomfyMyLinkException {
+ //String myLinkCommand = buildCommand(command, targetId);
+
+ synchronized(CONNECTION_LOCK) {
+ try {
+ logger.debug("Sending: {}", command);
+ Socket socket = getConnection();
+ OutputStream out = socket.getOutputStream();
+
+ try {
+ byte[] sendBuffer = command.getBytes(StandardCharsets.US_ASCII);
+ // send the command
+ out.write(sendBuffer, 0, sendBuffer.length);
+ } finally {
+ logger.debug("Cleaning up after command");
+ // cleanup
+ try {
+ out.close();
+ socket.close();
+ } catch (SocketException e) {
+ logger.debug("Error during socket tidy up. {}", e.getMessage());
+ } catch (IOException e) {
+ logger.debug("Error during socket tidy up. {}", e.getMessage());
+ }
+ }
+
+ // give time for mylink to process
+ Thread.sleep(CONNECTION_DELAY);
+
+ } catch (SocketTimeoutException e) {
+ logger.warn("Timeout sending command to mylink: {} Message: {}", command, e.getMessage());
+ throw new SomfyMyLinkException("Timeout sending command to mylink", e);
+ } catch (SocketException e) {
+ logger.warn("Problem sending command to mylink: {} Message: {}", command, e.getMessage());
+ throw new SomfyMyLinkException("Problem sending command to mylink", e);
+ } catch (IOException e) {
+ logger.warn("Problem sending command to mylink: {} Message: {}", command, e.getMessage());
+ throw new SomfyMyLinkException("Problem sending command to mylink", e);
+ } catch (InterruptedException e) {
+ logger.debug("Interrupted while waiting after sending command to mylink: {} Message: {}", command, e.getMessage());
+ }
+ }
+ }
+
+ @Nullable
+ private SomfyMyLinkResponseBase sendCommandWithResponse(String command, Type responseType)
+ throws SomfyMyLinkException {
+
+ synchronized(CONNECTION_LOCK) {
+ try {
+ logger.debug("Sending: {}", command);
+ Socket socket = getConnection();
+ OutputStream out = socket.getOutputStream();
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ try {
+ byte[] sendBuffer = command.getBytes(StandardCharsets.US_ASCII);
+
+ // send the command
+ out.write(sendBuffer, 0, sendBuffer.length);
+
+ // now read the reply
+ char[] readBuff = new char[1024];
+ int readCount;
+ String message = "";
+
+ // while we are getting data ...
+ while (((readCount = in.read(readBuff)) != -1)) {
+ logger.debug("Got response. Len: " + readCount);
+ message += new String(readBuff, 0, readCount);
+ try {
+
+ logger.debug("Got message: " + message);
+
+ JsonParser parser = new JsonParser();
+ JsonObject o = parser.parse(message).getAsJsonObject();
+
+ if(o.has("error")) {
+
+ SomfyMyLinkErrorResponse errorResponse = gson.fromJson(message, SomfyMyLinkErrorResponse.class);
+
+ logger.info("Error communicating with mylink: {}", errorResponse.error.message);
+ throw new SomfyMyLinkException("Error communicating with mylink: " + errorResponse.error.message);
+ }
+
+ SomfyMyLinkResponseBase data = gson.fromJson(message, responseType);
+
+ return data;
+ } catch (JsonSyntaxException e) {
+ // it wasn't a full message?
+ logger.debug("Trouble parsing message received. Message:" + e.getMessage());
+ }
+ }
+ } finally {
+ // cleanup
+ try {
+ out.close();
+ in.close();
+ socket.close();
+ } catch (SocketException e) {
+ logger.debug("Error during socket tidy up. {}", e.getMessage());
+ } catch (IOException e) {
+ logger.debug("Error during socket tidy up. {}", e.getMessage());
+ }
+ }
+
+ // only if we didn't already get a response give time for mylink to process
+ Thread.sleep(CONNECTION_DELAY);
+
+ return null;
+ } catch (SocketTimeoutException e) {
+ logger.info("Timeout sending command to mylink: " + command + "Message: " + e.getMessage());
+ throw new SomfyMyLinkException("Timeout sending command to mylink", e);
+ } catch (SocketException e) {
+ logger.info("Problem sending command to mylink: " + command + "Message: " + e.getMessage());
+ throw new SomfyMyLinkException("Problem sending command to mylink", e);
+ } catch (IOException e) {
+ logger.info("Problem sending command to mylink: " + command + "Message: " + e.getMessage());
+ throw new SomfyMyLinkException("Problem sending command to mylink", e);
+ } catch (InterruptedException e) {
+ logger.debug("Interrupted while waiting after sending command to mylink: " + command + "Message: " + e.getMessage());
+ return null;
+ }
+ }
+ }
+
+ private Socket getConnection() throws UnknownHostException, IOException {
+ if(config == null) throw new SomfyMyLinkException("Config not setup correctly");
+
+ logger.debug("Getting connection to mylink on:" + config.ipAddress + " Post: " + MYLINK_PORT);
+ String myLinkAddress = config.ipAddress;
+ Socket socket = new Socket(myLinkAddress, MYLINK_PORT);
+ socket.setSoTimeout(MYLINK_DEFAULT_TIMEOUT);
+ return socket;
+ }
+
+ private String buildShadeCommand(String method, String targetId) {
+ if(config == null && StringUtils.isEmpty(config.systemId)) throw new SomfyMyLinkException("Config not setup correctly");
+
+ int randomNum = ThreadLocalRandom.current().nextInt(1, 1000);
+
+ // quote and fix '-' back to '.'
+ String tId = String.format("\"%1$s\"", targetId).replace('-', '.');
+
+ String myLinkCommand = String.format(MYLINK_COMMAND_TEMPLATE, randomNum, method, "targetID", tId, config.systemId);
+
+ return myLinkCommand;
+ }
+
+ private String buildSceneCommand(String method, Integer sceneId) {
+ if(config == null && StringUtils.isEmpty(config.systemId)) throw new SomfyMyLinkException("Config not setup correctly");
+
+ int randomNum = ThreadLocalRandom.current().nextInt(1, 1000);
+
+ String myLinkCommand = String.format(MYLINK_COMMAND_TEMPLATE, randomNum, method, "sceneId", sceneId, config.systemId);
+
+ return myLinkCommand;
+ }
+
+ @Override
+ public void thingUpdated(Thing thing) {
+ SomfyMyLinkConfiguration newConfig = thing.getConfiguration().as(SomfyMyLinkConfiguration.class);
+
+ boolean validConfig = validConfiguration(newConfig);
+ boolean needsReconnect = false; // validConfig && !this.config.sameConnectionParameters(newConfig);
+
+ if (!validConfig || needsReconnect) {
+ dispose();
+ }
+
+ this.thing = thing;
+ config = newConfig;
+
+ if (needsReconnect) {
+ initialize();
+ }
+ }
+
+ @Override
+ public void dispose() {
+ logger.debug("Dispose called on {}", SomfyMyLinkBridgeHandler.class);
+ disconnect();
+
+ if (discoveryServiceRegistration != null) {
+ this.discovery.deactivate();
+ this.discoveryServiceRegistration.unregister();
+ discoveryServiceRegistration = null;
+ }
+
+ logger.debug("Dispose finishing on {}", SomfyMyLinkBridgeHandler.class);
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkException.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkException.java
new file mode 100644
index 0000000000000..9bd31e5cc920e
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkException.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public SomfyMyLinkException() {
+ super();
+ }
+
+ public SomfyMyLinkException(String message) {
+ super(message);
+ }
+
+ public SomfyMyLinkException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SomfyMyLinkException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkStateDescriptionOptionsProvider.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkStateDescriptionOptionsProvider.java
new file mode 100644
index 0000000000000..d91d7ec28494a
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyMyLinkStateDescriptionOptionsProvider.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.handler;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.Channel;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.type.DynamicStateDescriptionProvider;
+import org.eclipse.smarthome.core.types.StateDescription;
+import org.eclipse.smarthome.core.types.StateOption;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+
+/**
+ * Dynamic provider of state options while leaving other state description fields as original.
+ *
+ * @author Gregory Moyer - Initial contribution
+ * @author Mark Hilbush - Adapted to squeezebox binding
+ */
+@Component(service = { DynamicStateDescriptionProvider.class, SomfyMyLinkStateDescriptionOptionsProvider.class })
+@NonNullByDefault
+public class SomfyMyLinkStateDescriptionOptionsProvider implements DynamicStateDescriptionProvider {
+ private final Map> channelOptionsMap = new ConcurrentHashMap<>();
+
+ public void setStateOptions(ChannelUID channelUID, List options) {
+ channelOptionsMap.put(channelUID, options);
+ }
+
+ @Override
+ public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
+ @Nullable Locale locale) {
+ List options = channelOptionsMap.get(channel.getUID());
+ if (options == null) {
+ return null;
+ }
+
+ if (original != null) {
+ return new StateDescription(original.getMinimum(), original.getMaximum(), original.getStep(),
+ original.getPattern(), original.isReadOnly(), options);
+ }
+ return new StateDescription(null, null, null, null, false, options);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ channelOptionsMap.clear();
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfySceneHandler.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfySceneHandler.java
new file mode 100644
index 0000000000000..f4552a8065175
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfySceneHandler.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.handler;
+
+import static org.openhab.binding.somfymylink.internal.SomfyMyLinkBindingConstants.CHANNEL_SCENECONTROL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.library.types.OnOffType;
+import org.eclipse.smarthome.core.thing.Bridge;
+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.binding.BridgeHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfySceneHandler extends BaseThingHandler {
+
+ public SomfySceneHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ try {
+ if (command instanceof RefreshType) {
+ return;
+ }
+
+ if (CHANNEL_SCENECONTROL.equals(channelUID.getId()) && command instanceof OnOffType) {
+ Integer targetId = Integer.decode(channelUID.getThingUID().getId());
+
+ if (command.equals(OnOffType.ON)) {
+ getBridgeHandler().commandScene(targetId);
+ updateState(channelUID, OnOffType.OFF);
+ } else {
+ // do nothing
+ }
+ }
+ } catch (SomfyMyLinkException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ protected SomfyMyLinkBridgeHandler getBridgeHandler() {
+ Bridge bridge = this.getBridge();
+ if(bridge == null) throw new SomfyMyLinkException("No bridge was found");
+
+ BridgeHandler handler = bridge.getHandler();
+ if(handler == null) throw new SomfyMyLinkException("No handler was found");
+
+ return (SomfyMyLinkBridgeHandler) handler;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyShadeHandler.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyShadeHandler.java
new file mode 100644
index 0000000000000..bf5033f8c1b7d
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/handler/SomfyShadeHandler.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.handler;
+
+import static org.openhab.binding.somfymylink.internal.SomfyMyLinkBindingConstants.CHANNEL_SHADELEVEL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.library.types.StopMoveType;
+import org.eclipse.smarthome.core.library.types.UpDownType;
+import org.eclipse.smarthome.core.thing.Bridge;
+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.ThingStatusInfo;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
+import org.eclipse.smarthome.core.thing.binding.BridgeHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyShadeHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(SomfyShadeHandler.class);
+
+ public SomfyShadeHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ initDeviceState();
+ }
+
+ @Override
+ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+ logger.debug("Bridge status changed to {} updating {}", bridgeStatusInfo.getStatus(), getThing().getLabel());
+
+ if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
+ initDeviceState();
+
+ } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+ }
+ }
+
+ public void initDeviceState() {
+
+ Bridge bridge = getBridge();
+
+ if (bridge == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
+ logger.debug("Initialized device state for shade {} {}", ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
+ } else if (bridge.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ logger.debug("Initialized device state for shade {}", ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+ logger.debug("Initialized device state for shade {} {}", ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ try {
+ if (CHANNEL_SHADELEVEL.equals(channelUID.getId())) {
+ String targetId = channelUID.getThingUID().getId();
+
+ if (command instanceof RefreshType) {
+ // TODO: handle data refresh
+ return;
+ }
+ if (CHANNEL_SHADELEVEL.equals(channelUID.getId()) && command instanceof UpDownType) {
+ if (command.equals(UpDownType.DOWN)) {
+ getBridgeHandler().commandShadeDown(targetId);
+ } else {
+ getBridgeHandler().commandShadeUp(targetId);
+ }
+ }
+ if (CHANNEL_SHADELEVEL.equals(channelUID.getId()) && command instanceof StopMoveType) {
+ getBridgeHandler().commandShadeStop(targetId);
+ }
+ }
+ } catch (SomfyMyLinkException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ protected SomfyMyLinkBridgeHandler getBridgeHandler() {
+ Bridge bridge = this.getBridge();
+ if(bridge == null) throw new SomfyMyLinkException("No bridge was found");
+
+ BridgeHandler handler = bridge.getHandler();
+ if(handler == null) throw new SomfyMyLinkException("No handler was found");
+
+ return (SomfyMyLinkBridgeHandler) handler;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkError.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkError.java
new file mode 100644
index 0000000000000..aa813748b19f7
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkError.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+
+@NonNullByDefault
+public class SomfyMyLinkError {
+
+ public String code = "";
+
+ public String message = "";
+}
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkErrorResponse.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkErrorResponse.java
new file mode 100644
index 0000000000000..879397214a553
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkErrorResponse.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkErrorResponse extends SomfyMyLinkResponseBase {
+
+ public SomfyMyLinkError error = new SomfyMyLinkError();
+
+ @Nullable
+ public SomfyMyLinkError getError() {
+ return error;
+ }
+}
+
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkPingResponse.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkPingResponse.java
new file mode 100644
index 0000000000000..23bf0428b0ac3
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkPingResponse.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkPingResponse extends SomfyMyLinkResponseBase {
+
+ public String[] result = new String[0];
+
+ public String[] getResult() {
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkResponseBase.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkResponseBase.java
new file mode 100644
index 0000000000000..0b03713a512dd
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkResponseBase.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkResponseBase {
+
+ @Nullable
+ public String jsonrpc;
+
+ @Nullable
+ public String id;
+
+ @Nullable
+ public String getId() {
+ return id;
+ }
+
+ @Nullable
+ public String getJsonRpc() {
+ return jsonrpc;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkScene.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkScene.java
new file mode 100644
index 0000000000000..eeccce70cd64a
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkScene.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkScene {
+
+ private String sceneID = "";
+
+ private String name = "";
+
+ public String getTargetID() {
+ return sceneID;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkScenesResponse.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkScenesResponse.java
new file mode 100644
index 0000000000000..53c1108cd5480
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkScenesResponse.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkScenesResponse extends SomfyMyLinkResponseBase {
+
+ public SomfyMyLinkScene[] result = new SomfyMyLinkScene[0];
+
+ public SomfyMyLinkScene[] getResult() {
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkShade.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkShade.java
new file mode 100644
index 0000000000000..8368294eab2c3
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkShade.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkShade {
+
+ @Nullable
+ private String targetID;
+
+ @Nullable
+ private String name;
+
+ @Nullable
+ public String getTargetID() {
+ return targetID != null ? targetID.replace('.', '-') : null;
+ }
+
+ @Nullable
+ public String getName() {
+ return name;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkShadesResponse.java b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkShadesResponse.java
new file mode 100644
index 0000000000000..105c5d92ac1f5
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/java/org/openhab/binding/somfymylink/internal/model/SomfyMyLinkShadesResponse.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2019 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.somfymylink.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SomfyMyLinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Chris Johnson - Initial contribution
+ */
+@NonNullByDefault
+public class SomfyMyLinkShadesResponse extends SomfyMyLinkResponseBase {
+
+ public SomfyMyLinkShade[] result = new SomfyMyLinkShade[0];
+
+ public SomfyMyLinkShade[] getResult() {
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/binding/binding.xml
new file mode 100644
index 0000000000000..eee071da6d94f
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ Somfy MyLink Binding
+ This is the binding for Somfy MyLink.
+ Chris Johnson
+
+
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/i18n/somfymylink_xx_XX.properties b/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/i18n/somfymylink_xx_XX.properties
new file mode 100644
index 0000000000000..a816824eea9ac
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/i18n/somfymylink_xx_XX.properties
@@ -0,0 +1,13 @@
+# FIXME: please substitute the xx_XX with a proper locale, ie. de_DE
+# FIXME: please do not add the file to the repo if you add or change no content
+# binding
+binding.somfymylink.name =
+binding.somfymylink.description =
+
+# thing types
+thing-type.somfymylink.sample.label =
+thing-type.somfymylink.sample.description =
+
+# channel types
+channel-type.somfymylink.sample-channel.label =
+channel-type.somfymylink.sample-channel.description =
diff --git a/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..b8b2209049c13
--- /dev/null
+++ b/bundles/org.openhab.binding.somfymylink/src/main/resources/ESH-INF/thing/thing-types.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ Somfy MyLink bridge enabling communication with Somfy devices
+
+
+
+
+
+ network-address
+
+ The IP or host name of the Somfy MyLink
+
+
+
+ The system id of the My Link bridge. This can be found in the integration settings on your My Link app
+
+
+
+
+
+
+
+
+
+
+ Scene control
+
+
+
+
+
+
+
+ Address of scene in the Somfy system
+
+
+
+
+
+
+
+
+
+
+ Controls shades
+
+
+
+
+
+
+
+ Address of shade in the Somfy system
+
+
+
+
+
+ String
+
+ Comma-separated list of scenes of form sceneId=sceneName
+
+
+
+
+ Rollershutter
+
+ Device control (UP, DOWN, MY/STOP, closure 0-100%)
+
+
+
+ Switch
+
+ Button to trigger a scene or rule
+ Switch
+
+
+