added migrated 2.x add-ons

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

View File

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

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link AdorneBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public class AdorneBindingConstants {
public static final String BINDING_ID = "adorne";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_HUB = new ThingTypeUID(BINDING_ID, "hub");
public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch");
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
// List of all Channel ids
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_BRIGHTNESS = "brightness";
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link AdorneDeviceState} class defines a simple POJO representing the Adorne device state.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public class AdorneDeviceState {
public final int zoneId;
public final String name;
public final ThingTypeUID deviceType;
public final boolean onOff;
public final int brightness;
public AdorneDeviceState(int zoneId, String name, ThingTypeUID deviceType, boolean onOff, int brightness) {
this.zoneId = zoneId;
this.name = name;
this.deviceType = deviceType;
this.onOff = onOff;
this.brightness = brightness;
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link AdorneHubConfiguration} class represents the hub configuration options.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public class AdorneHubConfiguration {
public String host = "LCM1.local";
public Integer port = 2112;
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link AdorneSwitchConfiguration} class represents the switch configuration options.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public class AdorneSwitchConfiguration {
public @Nullable Integer zoneId;
}

View File

@@ -0,0 +1,132 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.discovery;
import static org.openhab.binding.adorne.internal.AdorneBindingConstants.*;
import java.util.Collections;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.adorne.internal.configuration.AdorneHubConfiguration;
import org.openhab.binding.adorne.internal.hub.AdorneHubChangeNotify;
import org.openhab.binding.adorne.internal.hub.AdorneHubController;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.UIDUtils;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AdorneDiscoveryService} discovers things for the Adorne hub and Adorne devices.
* Discovery is only supported if the hub is accessible via default host and port.
*
* @author Mark Theiding - Initial Contribution
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.adorne")
public class AdorneDiscoveryService extends AbstractDiscoveryService implements AdorneHubChangeNotify {
private final Logger logger = LoggerFactory.getLogger(AdorneDiscoveryService.class);
private static final int DISCOVERY_TIMEOUT_SECONDS = 10;
private static final String DISCOVERY_HUB_LABEL = "Adorne Hub";
private static final String DISCOVERY_ZONE_ID = "zoneId";
private @Nullable AdorneHubController adorneHubController;
/**
* Creates a AdorneDiscoveryService with disabled auto-discovery.
*/
public AdorneDiscoveryService() {
// Passing false as last argument to super constructor turns off background discovery
super(Collections.singleton(new ThingTypeUID(BINDING_ID, "-")), DISCOVERY_TIMEOUT_SECONDS, false);
// We create the hub controller with default host and port. In the future we could let users create hubs
// manually with custom host and port settings and then perform discovery here for those hubs.
adorneHubController = null;
}
/**
* Kick off discovery of all devices on the hub
*/
@Override
protected void startScan() {
logger.debug("Discovery scan started");
AdorneHubController adorneHubController = new AdorneHubController(new AdorneHubConfiguration(), scheduler,
this);
this.adorneHubController = adorneHubController;
// Hack - we wrap the ThingUID in an array to make it appear effectively final to the compiler throughout the
// chain of futures. Passing it through the chain as context would bloat the code.
ThingUID[] bridgeUID = new ThingUID[1];
// Future enhancement: Need a timeout for each future execution to recover from bugs in the hub controller, but
// Java8 doesn't yet offer that
adorneHubController.start().thenCompose(Void -> {
// We use the hub's MAC address as its unique identifier
return adorneHubController.getMACAddress();
}).thenCompose(macAddress -> {
String macAddressNoColon = macAddress.replace(':', '-'); // Colons are not allowed in ThingUIDs
bridgeUID[0] = new ThingUID(THING_TYPE_HUB, macAddressNoColon);
// We have fully discovered the hub
thingDiscovered(DiscoveryResultBuilder.create(bridgeUID[0]).withLabel(DISCOVERY_HUB_LABEL).build());
return adorneHubController.getZones();
}).thenAccept(zoneIds -> {
zoneIds.forEach(zoneId -> {
adorneHubController.getState(zoneId).thenAccept(state -> {
String id = UIDUtils.encode(state.name); // Strip zone ID's name to become a valid ThingUID
// We have fully discovered a new zone ID
thingDiscovered(DiscoveryResultBuilder
.create(new ThingUID(state.deviceType, bridgeUID[0], id.toLowerCase()))
.withLabel(state.name).withBridge(bridgeUID[0])
.withProperty(DISCOVERY_ZONE_ID, state.zoneId).build());
}).exceptionally(e -> {
logger.warn("Discovery of zone ID {} failed ({})", zoneId, e.getMessage());
return null;
});
});
adorneHubController.stopWhenCommandsServed(); // Shut down hub once all discovery requests have been served
}).exceptionally(e -> {
logger.warn("Discovery failed ({})", e.getMessage());
return null;
});
}
/**
* Notification to stop scanning
*/
@Override
protected void stopScan() {
super.stopScan();
AdorneHubController adorneHubController = this.adorneHubController;
if (adorneHubController != null) {
adorneHubController.stop();
this.adorneHubController = null;
logger.debug("Discovery timed out. Scan stopped.");
}
}
// Nothing to do on change notifications
@Override
public void stateChangeNotify(int zoneId, boolean onOff, int brightness) {
}
@Override
public void connectionChangeNotify(boolean connected) {
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.handler;
import static org.openhab.binding.adorne.internal.AdorneBindingConstants.CHANNEL_BRIGHTNESS;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.adorne.internal.hub.AdorneHubController;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AdorneDimmerHandler} is responsible for handling commands, which are
* sent to one of the channels. It supports the brightness channel in addition to the inherited switch channel.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public class AdorneDimmerHandler extends AdorneSwitchHandler {
private final Logger logger = LoggerFactory.getLogger(AdorneDimmerHandler.class);
public AdorneDimmerHandler(Thing thing) {
super(thing);
}
/**
* Handles refresh and percent commands for channel
* {@link org.openhab.binding.adorne.internal.AdorneBindingConstants#CHANNEL_BRIGHTNESS}
* It delegates all other commands to its parent class.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("handleCommand (channelUID:{} command:{}", channelUID, command);
try {
if (channelUID.getId().equals(CHANNEL_BRIGHTNESS)) {
if (command instanceof RefreshType) {
refreshBrightness();
} else if (command instanceof PercentType) {
// Change the brightness through the hub controller
AdorneHubController adorneHubController = getAdorneHubController();
int level = ((PercentType) command).intValue();
if (level >= 1 && level <= 100) { // Ignore commands outside of the supported 1-100 range
adorneHubController.setBrightness(zoneId, level);
} else {
logger.debug("Ignored command to set brightness to level {}", level);
}
}
} else {
super.handleCommand(channelUID, command); // Parent can handle everything else
}
} catch (IllegalStateException e) {
// Hub controller could't handle our commands. Unfortunately the framework has no mechanism to report
// runtime errors. If we throw the exception up the framework logs it as an error - we don't want that - we
// want the framework to handle it gracefully. No point to update the thing status, since the
// AdorneHubController already does that. So we are forced to swallow the exception here.
logger.debug("Failed to execute command {} for channel {} for thing {} ({})", command, channelUID,
getThing().getLabel(), e.getMessage());
}
}
/**
* Refreshes the brightness of our thing to the actual state of the device.
*
*/
public void refreshBrightness() {
// Asynchronously get our brightness from the hub controller and update our state accordingly
AdorneHubController adorneHubController = getAdorneHubController();
adorneHubController.getState(zoneId).thenAccept(state -> {
updateState(CHANNEL_BRIGHTNESS, new PercentType(state.brightness));
logger.debug("Refreshed dimmer {} with brightness {}", getThing().getLabel(), state.brightness);
});
}
/**
* Refreshes all supported channels.
*
*/
@Override
public void refresh() {
super.refresh();
refreshBrightness();
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.handler;
import static org.openhab.binding.adorne.internal.AdorneBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AdorneHandlerFactory} is responsible for creating thing handlers.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.adorne", service = ThingHandlerFactory.class)
public class AdorneHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(AdorneHandlerFactory.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_HUB, THING_TYPE_SWITCH, THING_TYPE_DIMMER).collect(Collectors.toSet()));
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
/**
* Creates handlers for switches, dimmers and hubs.
*/
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_SWITCH)) {
logger.debug("Creating an AdorneSwitchHandler for thing '{}'", thing.getUID());
return new AdorneSwitchHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_DIMMER)) {
logger.debug("Creating an AdorneDimmerHandler for thing '{}'", thing.getUID());
return new AdorneDimmerHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_HUB)) {
logger.debug("Creating an AdorneHubHandler for bridge '{}'", thing.getUID());
return new AdorneHubHandler((Bridge) thing);
}
return null;
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.handler;
import static org.openhab.binding.adorne.internal.AdorneBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.adorne.internal.configuration.AdorneHubConfiguration;
import org.openhab.binding.adorne.internal.hub.AdorneHubChangeNotify;
import org.openhab.binding.adorne.internal.hub.AdorneHubController;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AdorneHubHandler} manages the state and status of the Adorne Hub's devices.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public class AdorneHubHandler extends BaseBridgeHandler implements AdorneHubChangeNotify {
private final Logger logger = LoggerFactory.getLogger(AdorneHubHandler.class);
private @Nullable AdorneHubController adorneHubController = null;
public AdorneHubHandler(Bridge bridge) {
super(bridge);
}
/**
* The {@link AdorneHubHandler} does not support any commands itself. This method is a NOOP and only provided since
* its implementation is required.
*
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Unfortunately BaseBridgeHandler doesn't provide a default implementation of handleCommand. However, hub
// commands could be added as a future enhancement e.g. to support hub firmware upgrades.
}
/**
* Establishes the hub controller for communication with the hub.
*/
@Override
public void initialize() {
logger.debug("Initializing hub {}", getThing().getLabel());
updateStatus(ThingStatus.UNKNOWN);
AdorneHubConfiguration config = getConfigAs(AdorneHubConfiguration.class);
logger.debug("Configuration host:{} port:{}", config.host, config.port);
AdorneHubController adorneHubController = new AdorneHubController(config, scheduler, this);
this.adorneHubController = adorneHubController;
// Kick off the hub controller that handles all interactions with the hub for us
adorneHubController.start();
}
/**
* Disposes resources by stopping the hub controller.
*/
@Override
public void dispose() {
AdorneHubController adorneHubController = this.adorneHubController;
if (adorneHubController != null) {
adorneHubController.stop();
}
}
/**
* Returns the hub controller. Returns <code>null</code> if hub controller has not been created yet.
*
* @return hub controller
*/
public @Nullable AdorneHubController getAdorneHubController() {
return adorneHubController;
}
/**
* The {@link AdorneHubHandler} is notified that the state of one of its physical devices has changed. The
* {@link AdorneHubHandler} then asks the appropriate thing handler to update the thing to match the new state.
*
*/
@Override
public void stateChangeNotify(int zoneId, boolean onOff, int brightness) {
logger.debug("State changed (zoneId:{} onOff:{} brightness:{})", zoneId, onOff, brightness);
getThing().getThings().forEach(thing -> {
AdorneSwitchHandler thingHandler = (AdorneSwitchHandler) thing.getHandler();
if (thingHandler != null && thingHandler.getZoneId() == zoneId) {
thingHandler.updateState(CHANNEL_POWER, OnOffType.from(onOff));
if (thing.getThingTypeUID().equals(THING_TYPE_DIMMER)) {
thingHandler.updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
}
}
});
}
/**
* The {@link AdorneHubHandler} is notified that its connectivity has changed.
*
*/
@Override
public void connectionChangeNotify(boolean connected) {
logger.debug("Status changed (connected:{})", connected);
if (connected) {
// Refresh all of our things in case thing states changed while we were disconnected
getThing().getThings().forEach(thing -> {
AdorneSwitchHandler thingHandler = (AdorneSwitchHandler) thing.getHandler();
if (thingHandler != null) {
thingHandler.refresh();
}
});
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
}

View File

@@ -0,0 +1,173 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.handler;
import static org.openhab.binding.adorne.internal.AdorneBindingConstants.CHANNEL_POWER;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.adorne.internal.configuration.AdorneSwitchConfiguration;
import org.openhab.binding.adorne.internal.hub.AdorneHubController;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AdorneSwitchHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public class AdorneSwitchHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(AdorneSwitchHandler.class);
/**
* The zone ID that represents this {@link AdorneSwitchHandler}'s thing
*/
protected int zoneId;
public AdorneSwitchHandler(Thing thing) {
super(thing);
}
/**
* Handles refresh and on/off commands for channel
* {@link org.openhab.binding.adorne.internal.AdorneBindingConstants#CHANNEL_POWER}
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("handleCommand (channelUID:{} command:{}", channelUID, command);
try {
if (channelUID.getId().equals(CHANNEL_POWER)) {
if (command instanceof OnOffType) {
AdorneHubController adorneHubController = getAdorneHubController();
adorneHubController.setOnOff(zoneId, command.equals(OnOffType.ON));
} else if (command instanceof RefreshType) {
refreshOnOff();
}
}
} catch (IllegalStateException e) {
// Hub controller could't handle our commands. Unfortunately the framework has no mechanism to report
// runtime errors. If we throw the exception up the framework logs it as an error - we don't want that - we
// want the framework to handle it gracefully. No point to update the thing status, since the
// AdorneHubController already does that. So we are forced to swallow the exception here.
logger.debug("Failed to execute command {} for channel {} for thing {} ({})", command, channelUID,
getThing().getLabel(), e.getMessage());
}
}
/**
* Sets the handled thing to online.
*/
@Override
public void initialize() {
logger.debug("Initializing switch {}", getThing().getLabel());
AdorneSwitchConfiguration config = getConfigAs(AdorneSwitchConfiguration.class);
Integer configZoneId = config.zoneId;
if (configZoneId != null) {
zoneId = configZoneId;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
return;
}
updateStatus(ThingStatus.ONLINE);
}
/**
* Updates thing status in response to bridge status changes.
*/
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
logger.trace("bridgeStatusChanged bridgeStatusInfo:{}", bridgeStatusInfo.getStatus());
if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
updateStatus(bridgeStatusInfo.getStatus());
}
}
/**
* Returns the hub controller.
*
* @throws IllegalStateException if hub controller is not available yet.
*/
protected AdorneHubController getAdorneHubController() {
Bridge bridge;
AdorneHubHandler hubHandler;
AdorneHubController adorneHubController = null;
bridge = getBridge();
if (bridge != null) {
hubHandler = (AdorneHubHandler) bridge.getHandler();
if (hubHandler != null) {
adorneHubController = hubHandler.getAdorneHubController();
}
}
if (adorneHubController == null) {
throw new IllegalStateException("Hub Controller not available yet.");
}
return adorneHubController;
}
/**
* Returns the zone ID that represents this {@link AdorneSwitchHandler}'s thing
*
* @return zone ID
*/
public int getZoneId() {
return zoneId;
}
/**
* Refreshes the on/off state of our thing to the actual state of the device.
*
*/
public void refreshOnOff() {
// Asynchronously get our onOff state from the hub controller and update our state accordingly
AdorneHubController adorneHubController = getAdorneHubController();
adorneHubController.getState(zoneId).thenAccept(state -> {
OnOffType onOffState = OnOffType.from(state.onOff);
updateState(CHANNEL_POWER, onOffState);
logger.debug("Refreshed switch {} with switch state {}", getThing().getLabel(), onOffState);
});
}
/**
* Refreshes all supported channels.
*
*/
public void refresh() {
refreshOnOff();
}
/**
* Provides a public version of updateState.
*
*/
@Override
public void updateState(String channelID, State state) {
super.updateState(channelID, state);// Leverage our base class' protected method
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.hub;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link AdorneHubChangeNotify} interface is used by the {@link AdorneHubController} to notify listeners about
* Adorne device status and hub connection changes.
*
* @author Mark Theiding - Initial contribution
*/
@NonNullByDefault
public interface AdorneHubChangeNotify {
/**
* Notify listener about state change of on/off and brightness state
*
* @param zoneID zone ID for which change occurred
* @param onOff new on/off state
* @param brightness new brightness
*/
public void stateChangeNotify(int zoneId, boolean onOff, int brightness);
/**
* Notify listener about hub connection change
*
* @param connected new connection state
*/
public void connectionChangeNotify(boolean connected);
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.hub;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonStreamParser;
/**
* The {@link AdorneHubConnection} manages basic connectivity with the Adorne hub.
*
* @author Mark Theiding - Initial Contribution
*/
@NonNullByDefault
public class AdorneHubConnection {
private final Logger logger = LoggerFactory.getLogger(AdorneHubConnection.class);
private final Socket hubSocket;
private final PrintStream hubOut;
private final InputStreamReader hubInReader;
private final JsonStreamParser hubIn;
public AdorneHubConnection(String hubHost, int hubPort, int timeout) throws IOException {
hubSocket = new Socket(hubHost, hubPort);
hubSocket.setSoTimeout(timeout);
hubOut = new PrintStream(hubSocket.getOutputStream());
hubInReader = new InputStreamReader(hubSocket.getInputStream());
hubIn = new JsonStreamParser(hubInReader);
}
public void close() {
try {
hubInReader.close(); // Closes underlying input stream as well
} catch (IOException e) {
logger.warn("Closing hub input reader failed ({})", e.getMessage());
}
hubOut.close(); // Closes underlying output stream as well
try {
hubSocket.close();
} catch (IOException e) {
logger.warn("Closing hub controller socket failed ({})", e.getMessage());
}
}
public void cancel() {
try {
hubSocket.shutdownInput();
} catch (IOException e) {
logger.debug("Couldn't shutdown hub socket");
}
}
public void putMsg(String cmd) {
hubOut.print(cmd);
}
public @Nullable JsonObject getMsg() throws JsonParseException {
JsonElement msg = null;
JsonObject msgJsonObject = null;
msg = hubIn.next();
if (msg == null || (msg instanceof JsonPrimitive && msg.getAsCharacter() == 0)) {
return null; // Eat empty messages
}
logger.debug("Received message {}", msg);
if (msg instanceof JsonObject) {
msgJsonObject = (JsonObject) msg;
}
return msgJsonObject;
}
}

View File

@@ -0,0 +1,511 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.adorne.internal.hub;
import static org.openhab.binding.adorne.internal.AdorneBindingConstants.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.adorne.internal.AdorneDeviceState;
import org.openhab.binding.adorne.internal.configuration.AdorneHubConfiguration;
import org.openhab.core.thing.ThingTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
/**
* The {@link AdorneHubController} manages the interaction with the Adorne hub. The controller maintains a connection
* with the Adorne Hub and listens to device changes and issues device commands. Interaction with the hub is performed
* asynchronously through REST messages.
*
* @author Mark Theiding - Initial Contribution
*/
@NonNullByDefault
public class AdorneHubController {
private final Logger logger = LoggerFactory.getLogger(AdorneHubController.class);
private static final int HUB_CONNECT_TIMEOUT = 10000;
private static final int HUB_RECONNECT_SLEEP_MINIMUM = 1;
private static final int HUB_RECONNECT_SLEEP_MAXIMUM = 15 * 60;
// Hub rest commands
private static final String HUB_REST_SET_ONOFF = "{\"ID\":%d,\"Service\":\"SetZoneProperties\",\"ZID\":%d,\"PropertyList\":{\"Power\":%b}}\0";
private static final String HUB_REST_SET_BRIGHTNESS = "{\"ID\":%d,\"Service\":\"SetZoneProperties\",\"ZID\":%d,\"PropertyList\":{\"PowerLevel\":%d}}\0";
private static final String HUB_REST_REQUEST_STATE = "{\"ID\":%d,\"Service\":\"ReportZoneProperties\",\"ZID\":%d}\0";
private static final String HUB_REST_REQUEST_ZONES = "{\"ID\":%d,\"Service\":\"ListZones\"}\0";
private static final String HUB_REST_REQUEST_MACADDRESS = "{\"ID\":%d,\"Service\":\"SystemInfo\"}\0";
private static final String HUB_TOKEN_SERVICE = "Service";
private static final String HUB_TOKEN_ZID = "ZID";
private static final String HUB_TOKEN_PROPERTY_LIST = "PropertyList";
private static final String HUB_TOKEN_DEVICE_TYPE = "DeviceType";
private static final String HUB_TOKEN_SWITCH = "Switch";
private static final String HUB_TOKEN_DIMMER = "Dimmer";
private static final String HUB_TOKEN_NAME = "Name";
private static final String HUB_TOKEN_POWER = "Power";
private static final String HUB_TOKEN_POWER_LEVEL = "PowerLevel";
private static final String HUB_TOKEN_MAC_ADDRESS = "MACAddress";
private static final String HUB_TOKEN_ZONE_LIST = "ZoneList";
private static final String HUB_SERVICE_REPORT_ZONE_PROPERTIES = "ReportZoneProperties";
private static final String HUB_SERVICE_ZONE_PROPERTIES_CHANGED = "ZonePropertiesChanged";
private static final String HUB_SERVICE_LIST_ZONE = "ListZones";
private static final String HUB_SERVICE_SYSTEM_INFO = "SystemInfo";
private @Nullable Future<?> hubController;
private final String hubHost;
private int hubPort;
private @Nullable AdorneHubConnection hubConnection;
private final CompletableFuture<@Nullable Void> hubControllerConnected;
private int hubReconnectSleep; // Sleep time before we attempt re-connect
private final ScheduledExecutorService scheduler;
private volatile boolean stopWhenCommandsServed; // Stop the controller once all pending commands have been served
// When we submit commmands to the hub we don't correlate commands and responses. We simply use the first available
// response that answers our question. For that we store all pending commands.
// Note that for optimal resiliency we send a new request for each command even if a request is already pending
private final Map<Integer, CompletableFuture<AdorneDeviceState>> stateCommands;
private @Nullable CompletableFuture<List<Integer>> zoneCommand;
private @Nullable CompletableFuture<String> macAddressCommand;
private final AtomicInteger commandId; // We assign increasing command ids to all REST commands to the hub for
// easier troubleshooting
private final AdorneHubChangeNotify changeListener;
private final Object stopLock;
private final Object hubConnectionLock;
private final Object macAddressCommandLock;
private final Object zoneCommandLock;
public AdorneHubController(AdorneHubConfiguration config, ScheduledExecutorService scheduler,
AdorneHubChangeNotify changeListener) {
hubHost = config.host;
hubPort = config.port;
this.scheduler = scheduler;
this.changeListener = changeListener;
hubController = null;
hubConnection = null;
hubControllerConnected = new CompletableFuture<>();
hubReconnectSleep = HUB_RECONNECT_SLEEP_MINIMUM;
stopWhenCommandsServed = false;
stopLock = new Object();
hubConnectionLock = new Object();
macAddressCommandLock = new Object();
zoneCommandLock = new Object();
stateCommands = new HashMap<>();
zoneCommand = null;
macAddressCommand = null;
commandId = new AtomicInteger(0);
}
/**
* Start the hub controller. Call only once.
*
* @return Future to inform the caller that the hub controller is ready for receiving commands
*/
public CompletableFuture<@Nullable Void> start() {
logger.info("Starting hub controller");
hubController = scheduler.submit(this::msgLoop);
return hubControllerConnected;
}
/**
* Stops the hub controller. Can't restart afterwards. If called before start nothing happens.
*/
public void stop() {
logger.info("Stopping hub controller");
synchronized (stopLock) {
// Canceling the controller tells the message loop to stop and also cancels recreation of the message loop
// if that is pending after a disconnect.
Future<?> hubController = this.hubController;
if (hubController != null) {
hubController.cancel(true);
}
}
// Stop the input stream in case controller is waiting on input
// Note this is best effort. If we are unlucky the hub can still enter waiting on input just after our stop
// here. Because waiting on input is long-running we can't just synchronize it with the stop check as case 2
// above. But that is ok as waiting on input has a timeout and will honor stop after that.
synchronized (hubConnectionLock) {
AdorneHubConnection hubConnection = this.hubConnection;
if (hubConnection != null) {
hubConnection.cancel();
}
}
cancelCommands();
}
/**
* Stops the hub controller once all in-flight commands have been executed.
*/
public void stopWhenCommandsServed() {
stopWhenCommandsServed = true;
}
/**
* Turns device on or off.
*
* @param zoneId the device's zone ID
* @param on true to turn on the device
*/
public void setOnOff(int zoneId, boolean on) {
sendRestCmd(String.format(HUB_REST_SET_ONOFF, getNextCommandId(), zoneId, on));
}
/**
* Sets the brightness for a device. Applies only to dimmer devices.
*
* @param zoneId the device's zone ID
* @param level A value from 1-100. Note that in particular value 0 is not supported, which means this method can't
* be used to turn off a dimmer.
*/
public void setBrightness(int zoneId, int level) {
if (level < 1 || level > 100) {
throw new IllegalArgumentException();
}
sendRestCmd(String.format(HUB_REST_SET_BRIGHTNESS, getNextCommandId(), zoneId, level));
}
/**
* Gets asynchronously the state for a device.
*
* @param zoneId the device's zone ID
* @return a future for the {@link AdorneDeviceState}
*/
public CompletableFuture<AdorneDeviceState> getState(int zoneId) {
// Note that we send the REST command for resiliency even if there is a pending command
sendRestCmd(String.format(HUB_REST_REQUEST_STATE, getNextCommandId(), zoneId));
CompletableFuture<AdorneDeviceState> stateCommand;
synchronized (stateCommands) {
stateCommand = stateCommands.get(zoneId);
if (stateCommand == null) {
stateCommand = new CompletableFuture<>();
stateCommands.put(zoneId, stateCommand);
}
}
return stateCommand;
}
/**
* Gets asynchronously all zone IDs that are in use on the hub.
*
* @return a future for the list of zone IDs
*/
public CompletableFuture<List<Integer>> getZones() {
// Note that we send the REST command for resiliency even if there is a pending command
sendRestCmd(String.format(HUB_REST_REQUEST_ZONES, getNextCommandId()));
CompletableFuture<List<Integer>> zoneCommand;
synchronized (zoneCommandLock) {
zoneCommand = this.zoneCommand;
if (zoneCommand == null) {
this.zoneCommand = zoneCommand = new CompletableFuture<>();
}
}
return zoneCommand;
}
/**
* Gets asynchronously the MAC address of the hub.
*
* @return a future for the MAC address
*/
public CompletableFuture<String> getMACAddress() {
// Note that we send the REST command for resiliency even if there is a pending command
sendRestCmd(String.format(HUB_REST_REQUEST_MACADDRESS, getNextCommandId()));
CompletableFuture<String> macAddressCommand;
synchronized (macAddressCommandLock) {
macAddressCommand = this.macAddressCommand;
if (macAddressCommand == null) {
this.macAddressCommand = macAddressCommand = new CompletableFuture<>();
}
}
return macAddressCommand;
}
private void sendRestCmd(String cmd) {
logger.debug("Sending command {}", cmd);
synchronized (hubConnectionLock) {
AdorneHubConnection hubConnection = this.hubConnection;
if (hubConnection != null) {
hubConnection.putMsg(cmd);
} else {
throw new IllegalStateException("Can't send command. Adorne Hub connection is not available.");
}
}
}
/**
* Runs the controller message loop that is interacting with the Adorne Hub by sending commands and listening for
* updates
*/
private void msgLoop() {
try {
JsonObject hubMsg;
JsonPrimitive jsonService;
String service;
// Main message loop listening for updates from the hub
logger.debug("Starting message loop");
while (!shouldStop()) {
if (!connect()) {
int sleep = hubReconnectSleep;
logger.debug("Waiting {} seconds before re-attempting to connect.", sleep);
if (hubReconnectSleep < HUB_RECONNECT_SLEEP_MAXIMUM) {
hubReconnectSleep = hubReconnectSleep * 2; // Increase sleep time exponentially
}
restartMsgLoop(sleep);
return;
} else {
hubReconnectSleep = HUB_RECONNECT_SLEEP_MINIMUM; // Reset
}
hubMsg = null;
try {
AdorneHubConnection hubConnection = this.hubConnection;
if (hubConnection != null) {
hubMsg = hubConnection.getMsg();
}
} catch (JsonParseException e) {
logger.debug("Failed to read valid message {}", e.getMessage());
disconnect(); // Disconnect so we can recover
}
if (hubMsg == null) {
continue;
}
// Process message based on service type
if ((jsonService = hubMsg.getAsJsonPrimitive(HUB_TOKEN_SERVICE)) != null) {
service = jsonService.getAsString();
} else {
continue; // Ignore messages that don't have a service specified
}
if (service.equals(HUB_SERVICE_REPORT_ZONE_PROPERTIES)) {
processMsgReportZoneProperties(hubMsg);
} else if (service.equals(HUB_SERVICE_ZONE_PROPERTIES_CHANGED)) {
processMsgZonePropertiesChanged(hubMsg);
} else if (service.equals(HUB_SERVICE_LIST_ZONE)) {
processMsgListZone(hubMsg);
} else if (service.equals(HUB_SERVICE_SYSTEM_INFO)) {
processMsgSystemInfo(hubMsg);
}
}
} catch (RuntimeException e) {
logger.warn("Hub controller failed", e);
}
// Shut down
disconnect();
cancelCommands();
hubControllerConnected.cancel(false);
logger.info("Exiting hub controller");
}
private boolean shouldStop() {
boolean stateCommandsIsEmpty;
synchronized (stateCommands) {
stateCommandsIsEmpty = stateCommands.isEmpty();
}
boolean commandsServed = stopWhenCommandsServed && stateCommandsIsEmpty && (zoneCommand == null)
&& (macAddressCommand == null);
return isCancelled() || commandsServed;
}
private boolean isCancelled() {
Future<?> hubController = this.hubController;
return hubController == null || hubController.isCancelled();
}
private boolean connect() {
try {
if (hubConnection == null) {
hubConnection = new AdorneHubConnection(hubHost, hubPort, HUB_CONNECT_TIMEOUT);
logger.debug("Hub connection established");
// Working around an Adorne Hub bug: the first command sent from a new connection intermittently
// gets lost in the hub. We are requesting the MAC address here simply to get this fragile first
// command out of the way. Requesting the MAC address and ignoring the result doesn't do any harm.
getMACAddress();
hubControllerConnected.complete(null);
changeListener.connectionChangeNotify(true);
}
return true;
} catch (IOException e) {
logger.debug("Couldn't establish hub connection ({}).", e.getMessage());
return false;
}
}
private void disconnect() {
hubReconnectSleep = HUB_RECONNECT_SLEEP_MINIMUM; // Reset our reconnect sleep time
synchronized (hubConnectionLock) {
AdorneHubConnection hubConnection = this.hubConnection;
if (hubConnection != null) {
hubConnection.close();
this.hubConnection = null;
}
}
changeListener.connectionChangeNotify(false);
}
private void cancelCommands() {
// If there are still pending commands we need to cancel them
synchronized (stateCommands) {
stateCommands.forEach((zoneId, stateCommand) -> stateCommand.cancel(false));
stateCommands.clear();
}
synchronized (zoneCommandLock) {
CompletableFuture<List<Integer>> zoneCommand = this.zoneCommand;
if (zoneCommand != null) {
zoneCommand.cancel(false);
this.zoneCommand = null;
}
}
synchronized (macAddressCommandLock) {
CompletableFuture<String> macAddressCommand = this.macAddressCommand;
if (macAddressCommand != null) {
macAddressCommand.cancel(false);
this.macAddressCommand = null;
}
}
logger.debug("Cancelled commands");
}
private void restartMsgLoop(int sleep) {
synchronized (stopLock) {
if (!isCancelled()) {
this.hubController = scheduler.schedule(this::msgLoop, sleep, TimeUnit.SECONDS);
}
}
}
/**
* The hub sent zone properties in response to a command.
*/
private void processMsgReportZoneProperties(JsonObject hubMsg) {
int zoneId = hubMsg.getAsJsonPrimitive(HUB_TOKEN_ZID).getAsInt();
logger.debug("Reporting zone properties for zone ID {} ", zoneId);
JsonObject jsonPropertyList = hubMsg.getAsJsonObject(HUB_TOKEN_PROPERTY_LIST);
String deviceTypeStr = jsonPropertyList.getAsJsonPrimitive(HUB_TOKEN_DEVICE_TYPE).getAsString();
ThingTypeUID deviceType;
if (deviceTypeStr.equals(HUB_TOKEN_SWITCH)) {
deviceType = THING_TYPE_SWITCH;
} else if (deviceTypeStr.equals(HUB_TOKEN_DIMMER)) {
deviceType = THING_TYPE_DIMMER;
} else {
logger.debug("Unsupported device type {}", deviceTypeStr);
return;
}
AdorneDeviceState state = new AdorneDeviceState(zoneId,
jsonPropertyList.getAsJsonPrimitive(HUB_TOKEN_NAME).getAsString(), deviceType,
jsonPropertyList.getAsJsonPrimitive(HUB_TOKEN_POWER).getAsBoolean(),
jsonPropertyList.getAsJsonPrimitive(HUB_TOKEN_POWER_LEVEL).getAsInt());
synchronized (stateCommands) {
CompletableFuture<AdorneDeviceState> stateCommand = stateCommands.get(zoneId);
if (stateCommand != null) {
stateCommand.complete(state);
stateCommands.remove(zoneId);
}
}
}
/**
* The hub informs us about a zone's change in properties.
*/
private void processMsgZonePropertiesChanged(JsonObject hubMsg) {
int zoneId = hubMsg.getAsJsonPrimitive(HUB_TOKEN_ZID).getAsInt();
logger.debug("Zone properties changed for zone ID {} ", zoneId);
JsonObject jsonPropertyList = hubMsg.getAsJsonObject(HUB_TOKEN_PROPERTY_LIST);
boolean onOff = jsonPropertyList.getAsJsonPrimitive(HUB_TOKEN_POWER).getAsBoolean();
int brightness = jsonPropertyList.getAsJsonPrimitive(HUB_TOKEN_POWER_LEVEL).getAsInt();
changeListener.stateChangeNotify(zoneId, onOff, brightness);
}
/**
* The hub sent a list of zones in response to a command.
*/
private void processMsgListZone(JsonObject hubMsg) {
List<Integer> zones = new ArrayList<>();
JsonArray jsonZoneList;
jsonZoneList = hubMsg.getAsJsonArray(HUB_TOKEN_ZONE_LIST);
jsonZoneList.forEach(jsonZoneId -> {
JsonPrimitive jsonZoneIdValue = ((JsonObject) jsonZoneId).getAsJsonPrimitive(HUB_TOKEN_ZID);
zones.add(jsonZoneIdValue.getAsInt());
});
synchronized (zoneCommandLock) {
CompletableFuture<List<Integer>> zoneCommand = this.zoneCommand;
if (zoneCommand != null) {
zoneCommand.complete(zones);
this.zoneCommand = null;
}
}
}
/**
* The hub sent system info in response to a command.
*/
private void processMsgSystemInfo(JsonObject hubMsg) {
synchronized (macAddressCommandLock) {
CompletableFuture<String> macAddressCommand = this.macAddressCommand;
if (macAddressCommand != null) {
macAddressCommand.complete(hubMsg.getAsJsonPrimitive(HUB_TOKEN_MAC_ADDRESS).getAsString());
this.macAddressCommand = null;
}
}
}
private int getNextCommandId() {
IntUnaryOperator op = commandId -> {
int newCommandId = commandId;
if (commandId == Integer.MAX_VALUE) {
newCommandId = 0;
}
return ++newCommandId;
};
return commandId.updateAndGet(op);
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="adorne" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Adorne Binding</name>
<description>The Adorne Binding controls Legrand's Adorne Wi-Fi ready switches and outlets.</description>
<author>Mark Theiding</author>
</binding:binding>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="adorne"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="http://openhab.org/schemas/thing-description/v1.0.0 http://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="hub">
<label>Adorne Hub</label>
<description>The Adorne Hub serves as the bridge to control Adorne switches, dimmer switches and outlets.</description>
<config-description>
<parameter name="host" type="text">
<default>LCM1.local</default>
<label>Host</label>
<description>
Host name or IP address.
</description>
<context>network_address</context>
</parameter>
<parameter name="port" type="integer">
<default>2112</default>
<label>Port</label>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="adorne"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="switch">
<supported-bridge-type-refs>
<bridge-type-ref id="hub"/>
</supported-bridge-type-refs>
<label>Adorne Switch</label>
<description>Controls an Adorne switch or outlet.</description>
<channels>
<channel id="power" typeId="system.power"/>
</channels>
<config-description>
<parameter name="zoneId" type="integer" required="true">
<label>Zone ID</label>
</parameter>
</config-description>
</thing-type>
<thing-type id="dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="hub"/>
</supported-bridge-type-refs>
<label>Adorne Dimmer Switch</label>
<description>Controls an Adorne dimmer switch.</description>
<channels>
<channel id="power" typeId="system.power"/>
<channel id="brightness" typeId="system.brightness"/>
</channels>
<config-description>
<parameter name="zoneId" type="integer" required="true">
<label>Zone ID</label>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,5 @@
Switch LightBathroom {channel="adorne:switch:home:bathroom:power"}
Switch LightBedroomSwitch1 {channel="adorne:dimmer:home:bedroom1:power"}
Dimmer LightBedroomDimmer1 {channel="adorne:dimmer:home:bedroom1:brightness"}
Switch LightBedroomSwitch2 {channel="adorne:dimmer:home:bedroom2:power"}
Dimmer LightBedroomDimmer2 {channel="adorne:dimmer:home:bedroom2:brightness"}

View File

@@ -0,0 +1,14 @@
sitemap demo label="Adorne Binding Demo"
{
Frame label="Adorne Switch" {
Switch item=LightBathroom label="Bathroom" mappings=["ON"="On", "OFF"="Off"] icon="light-on"
}
Frame label="Adorne Dimmer using Slider" {
Switch item=LightBedroomSwitch1 label="Bedroom 1" mappings=["ON"="On", "OFF"="Off"] icon="light-on"
Slider item=LightBedroomDimmer1 label="Bedroom 1" icon="light-on" minValue=1 maxValue=100 step=1 // Requires OpenHAB 2.5
}
Frame label="Adorne Dimmer using Setpoint" {
Switch item=LightBedroomSwitch2 label="Bedroom 2" mappings=["ON"="On", "OFF"="Off"] icon="light-on"
Setpoint item=LightBedroomDimmer2 label="Bedroom 2" icon="light-on" minValue=1 maxValue=100 step=5
}
}

View File

@@ -0,0 +1,5 @@
Bridge adorne:hub:home "Adorne Hub" {
switch bathroom "Bathroom" [zoneId=0]
dimmer bedroom1 "Bedroom1" [zoneId=1]
dimmer bedroom2 "Bedroom2" [zoneId=2]
}