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 + + +