[lutron] Add LEAP protocol support (#8650)
* [lutron] Add LEAP protocol support Signed-off-by: Bob Adair <bob.github@att.net>
This commit is contained in:
@@ -28,6 +28,7 @@ public class LutronBindingConstants {
|
||||
|
||||
// Bridge Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_IPBRIDGE = new ThingTypeUID(BINDING_ID, "ipbridge");
|
||||
public static final ThingTypeUID THING_TYPE_LEAPBRIDGE = new ThingTypeUID(BINDING_ID, "leapbridge");
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
|
||||
@@ -51,12 +52,15 @@ public class LutronBindingConstants {
|
||||
public static final ThingTypeUID THING_TYPE_PALLADIOMKEYPAD = new ThingTypeUID(BINDING_ID, "palladiomkeypad");
|
||||
public static final ThingTypeUID THING_TYPE_WCI = new ThingTypeUID(BINDING_ID, "wci");
|
||||
public static final ThingTypeUID THING_TYPE_SYSVAR = new ThingTypeUID(BINDING_ID, "sysvar");
|
||||
public static final ThingTypeUID THING_TYPE_OGROUP = new ThingTypeUID(BINDING_ID, "ogroup");
|
||||
public static final ThingTypeUID THING_TYPE_FAN = new ThingTypeUID(BINDING_ID, "fan");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_LIGHTLEVEL = "lightlevel";
|
||||
public static final String CHANNEL_SHADELEVEL = "shadelevel";
|
||||
public static final String CHANNEL_SWITCH = "switchstatus";
|
||||
public static final String CHANNEL_OCCUPANCYSTATUS = "occupancystatus";
|
||||
public static final String CHANNEL_GROUPSTATE = "groupstate";
|
||||
public static final String CHANNEL_CLOCKMODE = "clockmode";
|
||||
public static final String CHANNEL_SUNRISE = "sunrise";
|
||||
public static final String CHANNEL_SUNSET = "sunset";
|
||||
@@ -67,6 +71,9 @@ public class LutronBindingConstants {
|
||||
public static final String CHANNEL_BLINDLIFTLEVEL = "blindliftlevel";
|
||||
public static final String CHANNEL_BLINDTILTLEVEL = "blindtiltlevel";
|
||||
public static final String CHANNEL_VARSTATE = "varstate";
|
||||
public static final String CHANNEL_FANSPEED = "fanspeed";
|
||||
public static final String CHANNEL_FANLEVEL = "fanlevel";
|
||||
public static final String CHANNEL_COMMAND = "command"; // For LEAP bridge debugging
|
||||
|
||||
// Bridge config properties (used by discovery service)
|
||||
public static final String HOST = "ipAddress";
|
||||
|
||||
@@ -31,12 +31,15 @@ import org.openhab.binding.lutron.internal.grxprg.PrgConstants;
|
||||
import org.openhab.binding.lutron.internal.handler.BlindHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.CcoHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.DimmerHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.FanHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.GrafikEyeKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.GreenModeHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.IPBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.IntlKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.KeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.MaintainedCcoHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.OGroupHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.OccupancySensorHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.PalladiomKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.PicoKeypadHandler;
|
||||
@@ -87,11 +90,13 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
// Used by LutronDeviceDiscoveryService to discover these types
|
||||
public static final Set<ThingTypeUID> DISCOVERABLE_DEVICE_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_DIMMER, THING_TYPE_SWITCH, THING_TYPE_OCCUPANCYSENSOR,
|
||||
THING_TYPE_KEYPAD, THING_TYPE_TTKEYPAD, THING_TYPE_INTLKEYPAD, THING_TYPE_PICO,
|
||||
THING_TYPE_VIRTUALKEYPAD, THING_TYPE_VCRX, THING_TYPE_CCO, THING_TYPE_SHADE, THING_TYPE_TIMECLOCK,
|
||||
THING_TYPE_GREENMODE, THING_TYPE_QSIO, THING_TYPE_GRAFIKEYEKEYPAD, THING_TYPE_BLIND,
|
||||
THING_TYPE_PALLADIOMKEYPAD, THING_TYPE_WCI).collect(Collectors.toSet()));
|
||||
.unmodifiableSet(Stream
|
||||
.of(THING_TYPE_DIMMER, THING_TYPE_SWITCH, THING_TYPE_OCCUPANCYSENSOR, THING_TYPE_KEYPAD,
|
||||
THING_TYPE_TTKEYPAD, THING_TYPE_INTLKEYPAD, THING_TYPE_PICO, THING_TYPE_VIRTUALKEYPAD,
|
||||
THING_TYPE_VCRX, THING_TYPE_CCO, THING_TYPE_SHADE, THING_TYPE_TIMECLOCK,
|
||||
THING_TYPE_GREENMODE, THING_TYPE_QSIO, THING_TYPE_GRAFIKEYEKEYPAD, THING_TYPE_BLIND,
|
||||
THING_TYPE_PALLADIOMKEYPAD, THING_TYPE_WCI, THING_TYPE_OGROUP, THING_TYPE_FAN)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
// Used by the HwDiscoveryService
|
||||
public static final Set<ThingTypeUID> HW_DISCOVERABLE_DEVICE_TYPES_UIDS = Collections
|
||||
@@ -99,11 +104,13 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
// Other types that can be initiated but not discovered
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_IPBRIDGE, PrgConstants.THING_TYPE_PRGBRIDGE,
|
||||
PrgConstants.THING_TYPE_GRAFIKEYE, RadioRAConstants.THING_TYPE_RS232,
|
||||
RadioRAConstants.THING_TYPE_DIMMER, RadioRAConstants.THING_TYPE_SWITCH,
|
||||
RadioRAConstants.THING_TYPE_PHANTOM, HwConstants.THING_TYPE_HWSERIALBRIDGE, THING_TYPE_CCO_PULSED,
|
||||
THING_TYPE_CCO_MAINTAINED, THING_TYPE_SYSVAR).collect(Collectors.toSet()));
|
||||
.unmodifiableSet(Stream
|
||||
.of(THING_TYPE_IPBRIDGE, THING_TYPE_LEAPBRIDGE, PrgConstants.THING_TYPE_PRGBRIDGE,
|
||||
PrgConstants.THING_TYPE_GRAFIKEYE, RadioRAConstants.THING_TYPE_RS232,
|
||||
RadioRAConstants.THING_TYPE_DIMMER, RadioRAConstants.THING_TYPE_SWITCH,
|
||||
RadioRAConstants.THING_TYPE_PHANTOM, HwConstants.THING_TYPE_HWSERIALBRIDGE,
|
||||
THING_TYPE_CCO_PULSED, THING_TYPE_CCO_MAINTAINED, THING_TYPE_SYSVAR)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronHandlerFactory.class);
|
||||
|
||||
@@ -135,6 +142,9 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
IPBridgeHandler bridgeHandler = new IPBridgeHandler((Bridge) thing);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (thingTypeUID.equals(THING_TYPE_LEAPBRIDGE)) {
|
||||
LeapBridgeHandler bridgeHandler = new LeapBridgeHandler((Bridge) thing);
|
||||
return bridgeHandler;
|
||||
} else if (thingTypeUID.equals(THING_TYPE_DIMMER)) {
|
||||
return new DimmerHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SHADE)) {
|
||||
@@ -177,6 +187,10 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
return new BlindHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SYSVAR)) {
|
||||
return new SysvarHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_OGROUP)) {
|
||||
return new OGroupHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_FAN)) {
|
||||
return new FanHandler(thing);
|
||||
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_PRGBRIDGE)) {
|
||||
return new PrgBridgeHandler((Bridge) thing);
|
||||
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_GRAFIKEYE)) {
|
||||
@@ -203,7 +217,7 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
if (thingHandler instanceof IPBridgeHandler) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegMap.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
logger.debug("Unregistering discovery service.");
|
||||
logger.debug("Unregistering device discovery service.");
|
||||
serviceReg.unregister();
|
||||
}
|
||||
}
|
||||
@@ -212,10 +226,10 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
/**
|
||||
* Register a discovery service for an IP bridge handler.
|
||||
*
|
||||
* @param bridgeHandler bridge handler for which to register the discovery service
|
||||
* @param bridgeHandler IP bridge handler for which to register the discovery service
|
||||
*/
|
||||
private synchronized void registerDiscoveryService(IPBridgeHandler bridgeHandler) {
|
||||
logger.debug("Registering discovery service.");
|
||||
logger.debug("Registering XML device discovery service.");
|
||||
LutronDeviceDiscoveryService discoveryService = new LutronDeviceDiscoveryService(bridgeHandler, httpClient);
|
||||
bridgeHandler.setDiscoveryService(discoveryService);
|
||||
discoveryServiceRegMap.put(bridgeHandler.getThing().getUID(),
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.lutron.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Configuration settings for a {@link org.openhab.binding.lutron.internal.handler.FanHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FanConfig {
|
||||
public int integrationId = 0; // Initialize to invalid value
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.lutron.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Configuration settings for an {@link org.openhab.binding.lutron.internal.handler.LeapBridgeHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapBridgeConfig {
|
||||
public @Nullable String ipAddress;
|
||||
public int port = 8081;
|
||||
public @Nullable String keystore;
|
||||
public @Nullable String keystorePassword;
|
||||
public boolean certValidate = false;
|
||||
public int reconnect;
|
||||
public int heartbeat;
|
||||
public int delay = 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.lutron.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Configuration settings for a {@link org.openhab.binding.lutron.internal.handler.OGroupHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OGroupConfig {
|
||||
public int integrationId;
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* 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.lutron.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.LutronHandlerFactory;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
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.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LeapDeviceDiscoveryService} discovers devices paired with Lutron bridges using the LEAP protocol.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapDeviceDiscoveryService extends AbstractDiscoveryService
|
||||
implements DiscoveryService, ThingHandlerService {
|
||||
|
||||
private static final int DISCOVERY_SERVICE_TIMEOUT = 0; // seconds
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LeapDeviceDiscoveryService.class);
|
||||
|
||||
/** Area number to name map **/
|
||||
private @Nullable Map<Integer, String> areaMap;
|
||||
private @Nullable List<OccupancyGroup> oGroupList;
|
||||
|
||||
private @NonNullByDefault({}) LeapBridgeHandler bridgeHandler;
|
||||
|
||||
public LeapDeviceDiscoveryService() {
|
||||
super(LutronHandlerFactory.DISCOVERABLE_DEVICE_TYPES_UIDS, DISCOVERY_SERVICE_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof LeapBridgeHandler) {
|
||||
bridgeHandler = (LeapBridgeHandler) handler;
|
||||
bridgeHandler.setDiscoveryService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Active discovery scan started");
|
||||
bridgeHandler.queryDiscoveryData();
|
||||
}
|
||||
|
||||
public void processDeviceDefinitions(List<Device> deviceList) {
|
||||
for (Device device : deviceList) {
|
||||
// Integer zoneid = device.getZone();
|
||||
Integer deviceId = device.getDevice();
|
||||
String label = device.getFullyQualifiedName();
|
||||
if (deviceId > 0) {
|
||||
logger.debug("Discovered device: {} type: {} id: {}", label, device.deviceType, deviceId);
|
||||
if (device.deviceType != null) {
|
||||
switch (device.deviceType) {
|
||||
case "SmartBridge":
|
||||
case "RA2SelectMainRepeater":
|
||||
notifyDiscovery(THING_TYPE_VIRTUALKEYPAD, deviceId, label, "model", "Caseta");
|
||||
break;
|
||||
case "WallDimmer":
|
||||
case "PlugInDimmer":
|
||||
notifyDiscovery(THING_TYPE_DIMMER, deviceId, label);
|
||||
break;
|
||||
case "WallSwitch":
|
||||
case "PlugInSwitch":
|
||||
notifyDiscovery(THING_TYPE_SWITCH, deviceId, label);
|
||||
break;
|
||||
case "CasetaFanSpeedController":
|
||||
case "MaestroFanSpeedController":
|
||||
notifyDiscovery(THING_TYPE_FAN, deviceId, label);
|
||||
break;
|
||||
case "Pico2Button":
|
||||
notifyDiscovery(THING_TYPE_PICO, deviceId, label, "model", "2B");
|
||||
break;
|
||||
case "Pico2ButtonRaiseLower":
|
||||
notifyDiscovery(THING_TYPE_PICO, deviceId, label, "model", "2BRL");
|
||||
break;
|
||||
case "Pico3ButtonRaiseLower":
|
||||
notifyDiscovery(THING_TYPE_PICO, deviceId, label, "model", "3BRL");
|
||||
break;
|
||||
case "SerenaRollerShade":
|
||||
case "SerenaHoneycombShade":
|
||||
case "TriathlonRollerShade":
|
||||
case "TriathlonHoneycombShade":
|
||||
case "QsWirelessShade":
|
||||
notifyDiscovery(THING_TYPE_SHADE, deviceId, label);
|
||||
break;
|
||||
case "RPSOccupancySensor":
|
||||
// Don't discover sensors. Using occupancy groups instead.
|
||||
break;
|
||||
default:
|
||||
logger.info("Unrecognized device type: {}", device.deviceType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processOccupancyGroups() {
|
||||
Map<Integer, String> areaMap = this.areaMap;
|
||||
List<OccupancyGroup> oGroupList = this.oGroupList;
|
||||
|
||||
if (areaMap != null && oGroupList != null) {
|
||||
logger.trace("Processing occupancy groups");
|
||||
for (OccupancyGroup oGroup : oGroupList) {
|
||||
logger.trace("Processing OccupancyGroup: {}", oGroup.href);
|
||||
int groupNum = oGroup.getOccupancyGroup();
|
||||
// Only process occupancy groups with associated occupancy sensors
|
||||
if (groupNum > 0 && oGroup.associatedSensors != null) {
|
||||
String areaName;
|
||||
if (oGroup.associatedAreas.length > 0) {
|
||||
// If multiple associated areas are listed, use only the first
|
||||
areaName = areaMap.get(oGroup.associatedAreas[0].getAreaNumber());
|
||||
} else {
|
||||
areaName = "Occupancy Group";
|
||||
}
|
||||
logger.debug("Discovered occupancy group: {} areas: {} area name: {}", groupNum,
|
||||
oGroup.associatedAreas.length, areaName);
|
||||
notifyDiscovery(THING_TYPE_OGROUP, groupNum, areaName);
|
||||
}
|
||||
}
|
||||
this.areaMap = null;
|
||||
this.oGroupList = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOccupancyGroups(List<OccupancyGroup> oGroupList) {
|
||||
logger.trace("Setting occupancy groups list");
|
||||
this.oGroupList = oGroupList;
|
||||
|
||||
if (areaMap != null) {
|
||||
processOccupancyGroups();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAreas(List<Area> areaList) {
|
||||
Map<Integer, String> areaMap = new HashMap<>();
|
||||
|
||||
logger.trace("Setting areas map");
|
||||
for (Area area : areaList) {
|
||||
int areaNum = area.getArea();
|
||||
logger.trace("Inserting area into map - num: {} name: {}", areaNum, area.name);
|
||||
if (areaNum > 0) {
|
||||
areaMap.put(areaNum, area.name);
|
||||
} else {
|
||||
logger.debug("Ignoring area with unparsable href {}", area.href);
|
||||
}
|
||||
}
|
||||
this.areaMap = areaMap;
|
||||
|
||||
if (oGroupList != null) {
|
||||
processOccupancyGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDiscovery(ThingTypeUID thingTypeUID, @Nullable Integer integrationId, String label,
|
||||
@Nullable String propName, @Nullable Object propValue) {
|
||||
if (integrationId == null) {
|
||||
logger.debug("Discovered {} with no integration ID", label);
|
||||
return;
|
||||
}
|
||||
ThingUID bridgeUID = this.bridgeHandler.getThing().getUID();
|
||||
ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, integrationId.toString());
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
properties.put(INTEGRATION_ID, integrationId);
|
||||
if (propName != null && propValue != null) {
|
||||
properties.put(propName, propValue);
|
||||
}
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(label)
|
||||
.withProperties(properties).withRepresentationProperty(INTEGRATION_ID).build();
|
||||
thingDiscovered(result);
|
||||
logger.trace("Discovered {}", uid);
|
||||
}
|
||||
|
||||
private void notifyDiscovery(ThingTypeUID thingTypeUID, Integer integrationId, String label) {
|
||||
notifyDiscovery(thingTypeUID, integrationId, label, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronDeviceDiscoveryService.class);
|
||||
|
||||
private IPBridgeHandler bridgeHandler;
|
||||
private final IPBridgeHandler bridgeHandler;
|
||||
private DbXmlInfoReader dbXmlInfoReader = new DbXmlInfoReader();
|
||||
|
||||
private final HttpClient httpClient;
|
||||
@@ -111,8 +111,9 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
@Override
|
||||
protected synchronized void startScan() {
|
||||
Future<?> scanTask = this.scanTask;
|
||||
if (scanTask == null || scanTask.isDone()) {
|
||||
scanTask = scheduler.submit(this::asyncDiscoveryTask);
|
||||
this.scanTask = scheduler.submit(this::asyncDiscoveryTask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +132,7 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
private void readDeviceDatabase() {
|
||||
Project project = null;
|
||||
|
||||
if (bridgeHandler == null || bridgeHandler.getIPBridgeConfig() == null) {
|
||||
if (bridgeHandler.getIPBridgeConfig() == null) {
|
||||
logger.debug("Unable to get bridge config. Exiting.");
|
||||
return;
|
||||
}
|
||||
@@ -240,9 +241,9 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
for (DeviceNode deviceNode : area.getDeviceNodes()) {
|
||||
if (deviceNode instanceof DeviceGroup) {
|
||||
processDeviceGroup((DeviceGroup) deviceNode, context);
|
||||
processDeviceGroup(area, (DeviceGroup) deviceNode, context);
|
||||
} else if (deviceNode instanceof Device) {
|
||||
processDevice((Device) deviceNode, context);
|
||||
processDevice(area, (Device) deviceNode, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,17 +258,17 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
context.pop();
|
||||
}
|
||||
|
||||
private void processDeviceGroup(DeviceGroup deviceGroup, Stack<String> context) {
|
||||
private void processDeviceGroup(Area area, DeviceGroup deviceGroup, Stack<String> context) {
|
||||
context.push(deviceGroup.getName());
|
||||
|
||||
for (Device device : deviceGroup.getDevices()) {
|
||||
processDevice(device, context);
|
||||
processDevice(area, device, context);
|
||||
}
|
||||
|
||||
context.pop();
|
||||
}
|
||||
|
||||
private void processDevice(Device device, Stack<String> context) {
|
||||
private void processDevice(Area area, Device device, Stack<String> context) {
|
||||
List<Integer> buttons;
|
||||
KeypadConfig kpConfig;
|
||||
String kpModel;
|
||||
@@ -280,6 +281,7 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
switch (type) {
|
||||
case MOTION_SENSOR:
|
||||
notifyDiscovery(THING_TYPE_OCCUPANCYSENSOR, device.getIntegrationId(), label);
|
||||
notifyDiscovery(THING_TYPE_OGROUP, area.getIntegrationId(), area.getName());
|
||||
break;
|
||||
|
||||
case SEETOUCH_KEYPAD:
|
||||
@@ -389,10 +391,13 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
case FLUORESCENT_DB:
|
||||
case ZERO_TO_TEN:
|
||||
case AUTO_DETECT:
|
||||
case CEILING_FAN_TYPE:
|
||||
notifyDiscovery(THING_TYPE_DIMMER, output.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case CEILING_FAN_TYPE:
|
||||
notifyDiscovery(THING_TYPE_FAN, output.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case NON_DIM:
|
||||
case NON_DIM_INC:
|
||||
case NON_DIM_ELV:
|
||||
|
||||
@@ -38,8 +38,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LutronMdnsBridgeDiscoveryService} discovers Lutron Caseta Smart Bridge Pro and eventually RA2 Select Main
|
||||
* Repeater and other Lutron devices on the network using mDNS.
|
||||
* The {@link LutronMdnsBridgeDiscoveryService} discovers Lutron Caseta Smart Bridge, Caseta Smart Bridge Pro, RA2
|
||||
* Select Main Repeater, and other Lutron devices on the network using mDNS.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@@ -51,11 +51,15 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
private static final String LUTRON_MDNS_SERVICE_TYPE = "_lutron._tcp.local.";
|
||||
|
||||
private static final String PRODFAM_CASETA = "Caseta";
|
||||
private static final String PRODTYP_CASETA_SB = "Smart Bridge";
|
||||
private static final String DEVCLASS_CASETA_SB = "08040100";
|
||||
private static final String PRODTYP_CASETA_SBP2 = "Smart Bridge Pro 2";
|
||||
private static final String DEVCLASS_CASETA_SBP2 = "08050100";
|
||||
|
||||
private static final String PRODFAM_RA2_SELECT = "RA2 Select";
|
||||
private static final String PRODTYP_RA2_SELECT = "Main Repeater";
|
||||
private static final String DEVCLASS_RA2_SELECT = "080E0401";
|
||||
|
||||
private static final String DEVCLASS_CONNECT_BRIDGE = "08090301";
|
||||
private static final String DEFAULT_LABEL = "Unknown Lutron bridge";
|
||||
|
||||
@@ -108,7 +112,11 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
String bridgeHostName = ipAddresses[0].getHostName();
|
||||
logger.debug("Lutron mDNS bridge hostname: {}", bridgeHostName);
|
||||
|
||||
if (DEVCLASS_CASETA_SBP2.equals(devclass)) {
|
||||
if (DEVCLASS_CASETA_SB.equals(devclass)) {
|
||||
properties.put(PROPERTY_PRODFAM, PRODFAM_CASETA);
|
||||
properties.put(PROPERTY_PRODTYP, PRODTYP_CASETA_SB);
|
||||
label = PRODFAM_CASETA + " " + PRODTYP_CASETA_SB;
|
||||
} else if (DEVCLASS_CASETA_SBP2.equals(devclass)) {
|
||||
properties.put(PROPERTY_PRODFAM, PRODFAM_CASETA);
|
||||
properties.put(PROPERTY_PRODTYP, PRODTYP_CASETA_SBP2);
|
||||
label = PRODFAM_CASETA + " " + PRODTYP_CASETA_SBP2;
|
||||
@@ -122,7 +130,7 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
} else {
|
||||
logger.info("Lutron device with unknown DEVCLASS discovered via mDNS: {}. Configure device manually.",
|
||||
devclass);
|
||||
return null; // For now, exit if service has unknown DEVCLASS
|
||||
return null; // Exit if service has unknown DEVCLASS
|
||||
}
|
||||
|
||||
if (!bridgeHostName.equals(ipAddresses[0].getHostAddress())) {
|
||||
@@ -161,10 +169,15 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||
String serial = getSerial(service);
|
||||
String devclass = service.getPropertyString("DEVCLASS");
|
||||
if (serial == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ThingUID(THING_TYPE_IPBRIDGE, serial);
|
||||
if (DEVCLASS_CASETA_SB.equals(devclass)) {
|
||||
return new ThingUID(THING_TYPE_LEAPBRIDGE, serial);
|
||||
} else {
|
||||
return new ThingUID(THING_TYPE_IPBRIDGE, serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
*/
|
||||
public class Area {
|
||||
private String name;
|
||||
private Integer integrationId;
|
||||
private List<DeviceNode> deviceNodes;
|
||||
private List<Output> outputs;
|
||||
private List<Area> areas;
|
||||
@@ -32,6 +33,10 @@ public class Area {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
public List<DeviceNode> getDeviceNodes() {
|
||||
return deviceNodes != null ? deviceNodes : Collections.<DeviceNode> emptyList();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.DeviceCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@@ -46,23 +48,14 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Initial contribution, based partly on Allan Tong's KeypadHandler class
|
||||
*/
|
||||
public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
|
||||
protected static final Integer ACTION_PRESS = 3;
|
||||
protected static final Integer ACTION_RELEASE = 4;
|
||||
protected static final Integer ACTION_HOLD = 5;
|
||||
protected static final Integer ACTION_LED_STATE = 9;
|
||||
|
||||
protected static final Integer LED_OFF = 0;
|
||||
protected static final Integer LED_ON = 1;
|
||||
protected static final Integer LED_FLASH = 2; // Same as 1 on RA2 keypads
|
||||
protected static final Integer LED_RAPIDFLASH = 3; // Same as 1 on RA2 keypads
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BaseKeypadHandler.class);
|
||||
|
||||
protected List<KeypadComponent> buttonList = new ArrayList<>();
|
||||
protected List<KeypadComponent> ledList = new ArrayList<>();
|
||||
protected List<KeypadComponent> cciList = new ArrayList<>();
|
||||
|
||||
Map<Integer, Integer> leapButtonMap;
|
||||
|
||||
protected int integrationId;
|
||||
protected String model;
|
||||
protected Boolean autoRelease;
|
||||
@@ -75,6 +68,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
private final Object asyncInitLock = new Object();
|
||||
|
||||
protected KeypadConfig kp;
|
||||
protected TargetType commandTargetType = TargetType.KEYPAD; // For LEAP bridge
|
||||
|
||||
public BaseKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
@@ -123,7 +117,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
List<Channel> channelList = new ArrayList<>();
|
||||
List<Channel> existingChannels = getThing().getChannels();
|
||||
|
||||
if (existingChannels != null && !existingChannels.isEmpty()) {
|
||||
if (!existingChannels.isEmpty()) {
|
||||
// Clear existing channels
|
||||
logger.debug("Clearing existing channels for keypad {}", integrationId);
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
@@ -261,7 +255,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
// To reduce query volume, query only 1st LED and LEDs with linked channels.
|
||||
for (KeypadComponent component : ledList) {
|
||||
if (component.id() == ledList.get(0).id() || isLinked(channelFromComponent(component.id()))) {
|
||||
queryDevice(component.id(), ACTION_LED_STATE);
|
||||
queryDevice(commandTargetType, component.id(), DeviceCommand.ACTION_LED_STATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,12 +284,12 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
// For LEDs, handle RefreshType and OnOffType commands
|
||||
if (isLed(componentID)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryDevice(componentID, ACTION_LED_STATE);
|
||||
queryDevice(commandTargetType, componentID, DeviceCommand.ACTION_LED_STATE);
|
||||
} else if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
device(componentID, ACTION_LED_STATE, LED_ON);
|
||||
device(commandTargetType, componentID, null, DeviceCommand.ACTION_LED_STATE, DeviceCommand.LED_ON);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
device(componentID, ACTION_LED_STATE, LED_OFF);
|
||||
device(commandTargetType, componentID, null, DeviceCommand.ACTION_LED_STATE, DeviceCommand.LED_OFF);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid command {} received for channel {} device {}", command, channelUID,
|
||||
@@ -307,13 +301,15 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
// For buttons, handle OnOffType commands
|
||||
if (isButton(componentID)) {
|
||||
if (command instanceof OnOffType) {
|
||||
// Annotate commands with LEAP button number for LEAP bridge
|
||||
Integer leapComponent = (this.leapButtonMap == null) ? null : leapButtonMap.get(componentID);
|
||||
if (command == OnOffType.ON) {
|
||||
device(componentID, ACTION_PRESS);
|
||||
device(commandTargetType, componentID, leapComponent, DeviceCommand.ACTION_PRESS, null);
|
||||
if (autoRelease) {
|
||||
device(componentID, ACTION_RELEASE);
|
||||
device(commandTargetType, componentID, leapComponent, DeviceCommand.ACTION_RELEASE, null);
|
||||
}
|
||||
} else if (command == OnOffType.OFF) {
|
||||
device(componentID, ACTION_RELEASE);
|
||||
device(commandTargetType, componentID, leapComponent, DeviceCommand.ACTION_RELEASE, null);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid command type {} received for channel {} device {}", command, channelUID,
|
||||
@@ -342,7 +338,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
|
||||
// if this channel is for an LED, query the Lutron controller for the current state
|
||||
if (isLed(id)) {
|
||||
queryDevice(id, ACTION_LED_STATE);
|
||||
queryDevice(commandTargetType, id, DeviceCommand.ACTION_LED_STATE);
|
||||
}
|
||||
// Button and CCI state can't be queried, only monitored for updates.
|
||||
// Init button state to OFF on channel init.
|
||||
@@ -368,16 +364,16 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
ChannelUID channelUID = channelFromComponent(component);
|
||||
|
||||
if (channelUID != null) {
|
||||
if (ACTION_LED_STATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
if (DeviceCommand.ACTION_LED_STATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE); // set thing status online if this is an initial response
|
||||
}
|
||||
if (LED_ON.toString().equals(parameters[2])) {
|
||||
if (DeviceCommand.LED_ON.toString().equals(parameters[2])) {
|
||||
updateState(channelUID, OnOffType.ON);
|
||||
} else if (LED_OFF.toString().equals(parameters[2])) {
|
||||
} else if (DeviceCommand.LED_OFF.toString().equals(parameters[2])) {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
}
|
||||
} else if (ACTION_PRESS.toString().equals(parameters[1])) {
|
||||
} else if (DeviceCommand.ACTION_PRESS.toString().equals(parameters[1])) {
|
||||
if (isButton(component)) {
|
||||
updateState(channelUID, OnOffType.ON);
|
||||
if (autoRelease) {
|
||||
@@ -386,13 +382,13 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
} else { // component is CCI
|
||||
updateState(channelUID, OpenClosedType.CLOSED);
|
||||
}
|
||||
} else if (ACTION_RELEASE.toString().equals(parameters[1])) {
|
||||
} else if (DeviceCommand.ACTION_RELEASE.toString().equals(parameters[1])) {
|
||||
if (isButton(component)) {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
} else { // component is CCI
|
||||
updateState(channelUID, OpenClosedType.OPEN);
|
||||
}
|
||||
} else if (ACTION_HOLD.toString().equals(parameters[1])) {
|
||||
} else if (DeviceCommand.ACTION_HOLD.toString().equals(parameters[1])) {
|
||||
updateState(channelUID, OnOffType.OFF); // Signal a release if we receive a hold code as we will not
|
||||
// get a subsequent release.
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.openhab.binding.lutron.internal.config.BlindConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
@@ -37,16 +39,6 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Initial contribution based on Alan Tong's DimmerHandler
|
||||
*/
|
||||
public class BlindHandler extends LutronHandler {
|
||||
private static final Integer ACTION_LIFTLEVEL = 1;
|
||||
private static final Integer ACTION_TILTLEVEL = 9;
|
||||
private static final Integer ACTION_LIFTTILTLEVEL = 10;
|
||||
private static final Integer ACTION_STARTRAISINGTILT = 11;
|
||||
private static final Integer ACTION_STARTLOWERINGTILT = 12;
|
||||
private static final Integer ACTION_STOPTILT = 13;
|
||||
private static final Integer ACTION_STARTRAISINGLIFT = 14;
|
||||
private static final Integer ACTION_STARTLOWERINGLIFT = 15;
|
||||
private static final Integer ACTION_STOPLIFT = 16;
|
||||
private static final Integer ACTION_POSITION_UPDATE = 32; // undocumented in integration protocol guide
|
||||
private static final Integer PARAMETER_POSITION_UPDATE = 2; // undocumented in integration protocol guide
|
||||
|
||||
private int tiltMax = 100; // max 50 for horizontal sheer, 100 for venetian
|
||||
@@ -96,8 +88,9 @@ public class BlindHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_LIFTLEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@@ -107,9 +100,9 @@ public class BlindHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
// Refresh state when new item is linked.
|
||||
if (channelUID.getId().equals(CHANNEL_BLINDLIFTLEVEL)) {
|
||||
queryOutput(ACTION_LIFTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL);
|
||||
} else if (channelUID.getId().equals(CHANNEL_BLINDTILTLEVEL)) {
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,30 +118,30 @@ public class BlindHandler extends LutronHandler {
|
||||
private void handleLiftCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_LIFTLEVEL, level, 0);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL, level, null, null);
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISINGLIFT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTRAISINGLIFT, null, null, null);
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERINGLIFT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTLOWERINGLIFT, null, null, null);
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOPLIFT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STOPLIFT, null, null, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_LIFTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTiltCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_TILTLEVEL, Math.min(level, tiltMax), 0);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL, Math.min(level, tiltMax), null, null);
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISINGTILT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTRAISINGTILT, null, null, null);
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERINGTILT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTLOWERINGTILT, null, null, null);
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOPTILT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STOPTILT, null, null, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,21 +152,21 @@ public class BlindHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
if (ACTION_LIFTLEVEL.toString().equals(parameters[0])) {
|
||||
if (OutputCommand.ACTION_LIFTLEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal liftLevel = new BigDecimal(parameters[1]);
|
||||
logger.trace("Blind {} received lift level: {}", getIntegrationId(), liftLevel);
|
||||
updateState(CHANNEL_BLINDLIFTLEVEL, new PercentType(liftLevel));
|
||||
} else if (ACTION_TILTLEVEL.toString().equals(parameters[0])) {
|
||||
} else if (OutputCommand.ACTION_TILTLEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal tiltLevel = new BigDecimal(parameters[1]);
|
||||
logger.trace("Blind {} received tilt level: {}", getIntegrationId(), tiltLevel);
|
||||
updateState(CHANNEL_BLINDTILTLEVEL, new PercentType(tiltLevel));
|
||||
} else if (ACTION_LIFTTILTLEVEL.toString().equals(parameters[0]) && parameters.length > 2) {
|
||||
} else if (OutputCommand.ACTION_LIFTTILTLEVEL.toString().equals(parameters[0]) && parameters.length > 2) {
|
||||
BigDecimal liftLevel = new BigDecimal(parameters[1]);
|
||||
BigDecimal tiltLevel = new BigDecimal(parameters[2]);
|
||||
logger.trace("Blind {} received lift/tilt level: {} {}", getIntegrationId(), liftLevel, tiltLevel);
|
||||
updateState(CHANNEL_BLINDLIFTLEVEL, new PercentType(liftLevel));
|
||||
updateState(CHANNEL_BLINDTILTLEVEL, new PercentType(tiltLevel));
|
||||
} else if (ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
} else if (OutputCommand.ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
&& PARAMETER_POSITION_UPDATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
BigDecimal level = new BigDecimal(parameters[2]);
|
||||
logger.trace("Blind {} received lift level position update: {}", getIntegrationId(), level);
|
||||
|
||||
@@ -15,12 +15,13 @@ package org.openhab.binding.lutron.internal.handler;
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -45,9 +46,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CcoHandler extends LutronHandler {
|
||||
private static final Integer ACTION_PULSE = 6;
|
||||
private static final Integer ACTION_STATE = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CcoHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@@ -123,7 +121,8 @@ public class CcoHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_STATE); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@@ -139,7 +138,7 @@ public class CcoHandler extends LutronHandler {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
} else if (outputType == CcoOutputType.MAINTAINED) {
|
||||
// Query the device state and let the service routine update the channel state
|
||||
queryOutput(ACTION_STATE);
|
||||
queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
|
||||
} else {
|
||||
logger.warn("invalid output type defined for CCO {}", integrationId);
|
||||
}
|
||||
@@ -153,22 +152,22 @@ public class CcoHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
if (command instanceof OnOffType && command == OnOffType.ON) {
|
||||
if (outputType == CcoOutputType.PULSED) {
|
||||
output(ACTION_PULSE, String.format(Locale.ROOT, "%.2f", defaultPulse));
|
||||
output(TargetType.CCO, OutputCommand.ACTION_PULSE, Double.valueOf(defaultPulse), null, null);
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
} else {
|
||||
output(ACTION_STATE, 100);
|
||||
output(TargetType.CCO, OutputCommand.ACTION_STATE, 100, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
else if (command instanceof OnOffType && command == OnOffType.OFF) {
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
output(ACTION_STATE, 0);
|
||||
output(TargetType.CCO, OutputCommand.ACTION_STATE, 0, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
else if (command instanceof RefreshType) {
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
queryOutput(ACTION_STATE);
|
||||
queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
|
||||
} else {
|
||||
updateState(CHANNEL_SWITCH, OnOffType.OFF);
|
||||
}
|
||||
@@ -186,7 +185,7 @@ public class CcoHandler extends LutronHandler {
|
||||
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_STATE.toString().equals(parameters[0])) {
|
||||
&& OutputCommand.ACTION_STATE.toString().equals(parameters[0])) {
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.openhab.binding.lutron.internal.action.DimmerActions;
|
||||
import org.openhab.binding.lutron.internal.config.DimmerConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronDuration;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@@ -42,8 +44,6 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Added initDeviceState method, and onLevel and onToLast parameters
|
||||
*/
|
||||
public class DimmerHandler extends LutronHandler {
|
||||
private static final Integer ACTION_ZONELEVEL = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DimmerHandler.class);
|
||||
private DimmerConfig config;
|
||||
private LutronDuration fadeInTime;
|
||||
@@ -90,7 +90,8 @@ public class DimmerHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
lastLightLevel.set(config.onLevel);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
@@ -101,7 +102,7 @@ public class DimmerHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,28 +111,28 @@ public class DimmerHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
|
||||
if (command instanceof Number) {
|
||||
int level = ((Number) command).intValue();
|
||||
output(ACTION_ZONELEVEL, level, 0.25);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, level, new LutronDuration("0.25"), null);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
if (config.onToLast) {
|
||||
output(ACTION_ZONELEVEL, lastLightLevel.get(), fadeInTime);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, lastLightLevel.get(), fadeInTime, null);
|
||||
} else {
|
||||
output(ACTION_ZONELEVEL, config.onLevel, fadeInTime);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, config.onLevel, fadeInTime, null);
|
||||
}
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
output(ACTION_ZONELEVEL, 0, fadeOutTime);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, 0, fadeOutTime, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLightLevel(BigDecimal level, LutronDuration fade, LutronDuration delay) {
|
||||
int intLevel = level.intValue();
|
||||
output(ACTION_ZONELEVEL, intLevel, fade, delay);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, intLevel, fade, delay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
&& OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.lutron.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.config.FanConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with ceiling fan speed controllers.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FanHandler extends LutronHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(FanHandler.class);
|
||||
|
||||
private FanConfig config = new FanConfig();
|
||||
|
||||
public FanHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
if (config.integrationId <= 0) {
|
||||
throw new IllegalStateException("handler not initialized");
|
||||
} else {
|
||||
return config.integrationId;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(FanConfig.class);
|
||||
if (config.integrationId <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId configured");
|
||||
return;
|
||||
}
|
||||
logger.debug("Initializing Fan handler for integration ID {}", getIntegrationId());
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Fan {}", getIntegrationId());
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(TargetType.FAN, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_FANSPEED) || channelUID.getId().equals(CHANNEL_FANLEVEL)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(TargetType.FAN, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_FANLEVEL)) {
|
||||
if (command instanceof Number) {
|
||||
int level = ((Number) command).intValue();
|
||||
FanSpeedType speed = FanSpeedType.toFanSpeedType(level);
|
||||
// output(TargetType.FAN, LutronCommand.ACTION_ZONELEVEL, level, null, null);
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, speed, null, null));
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
// output(TargetType.FAN, LutronCommand.ACTION_ZONELEVEL, 100, null, null);
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, FanSpeedType.HIGH, null, null));
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
// output(TargetType.FAN, LutronCommand.ACTION_ZONELEVEL, 0, null, null);
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, FanSpeedType.OFF, null, null));
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_FANSPEED)) {
|
||||
if (command instanceof StringType) {
|
||||
FanSpeedType speed = FanSpeedType.toFanSpeedType(command.toString());
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, speed, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
updateState(CHANNEL_FANLEVEL, new PercentType(level));
|
||||
FanSpeedType fanSpeed = FanSpeedType.toFanSpeedType(level.intValue());
|
||||
updateState(CHANNEL_FANSPEED, new StringType(fanSpeed.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.ModeCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@@ -39,8 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GreenModeHandler extends LutronHandler {
|
||||
private static final Integer ACTION_STEP = 1;
|
||||
public static final int GREENSTEP_MIN = 1;
|
||||
private static final int GREENSTEP_MIN = 1;
|
||||
|
||||
// poll interval parameters are in minutes
|
||||
private static final int POLL_INTERVAL_DEFAULT = 15;
|
||||
@@ -94,7 +94,8 @@ public class GreenModeHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryGreenMode(ACTION_STEP); // handleUpdate() will set thing status to online when response arrives
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@@ -123,13 +124,13 @@ public class GreenModeHandler extends LutronHandler {
|
||||
|
||||
private synchronized void pollState() {
|
||||
logger.trace("Executing green mode polling job for integration ID {}", integrationId);
|
||||
queryGreenMode(ACTION_STEP);
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_STEP)) {
|
||||
queryGreenMode(ACTION_STEP);
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,16 +138,16 @@ public class GreenModeHandler extends LutronHandler {
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_STEP)) {
|
||||
if (command == OnOffType.ON) {
|
||||
greenMode(ACTION_STEP, 2);
|
||||
greenMode(ModeCommand.ACTION_STEP, 2);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
greenMode(ACTION_STEP, 1);
|
||||
greenMode(ModeCommand.ACTION_STEP, 1);
|
||||
} else if (command instanceof Number) {
|
||||
Integer step = ((Number) command).intValue();
|
||||
if (step.intValue() >= GREENSTEP_MIN) {
|
||||
greenMode(ACTION_STEP, step);
|
||||
greenMode(ModeCommand.ACTION_STEP, step);
|
||||
}
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryGreenMode(ACTION_STEP);
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
} else {
|
||||
logger.debug("Ignoring invalid command {} for id {}", command, integrationId);
|
||||
}
|
||||
@@ -159,7 +160,7 @@ public class GreenModeHandler extends LutronHandler {
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
try {
|
||||
if (type == LutronCommandType.MODE && parameters.length > 1
|
||||
&& ACTION_STEP.toString().equals(parameters[0])) {
|
||||
&& ModeCommand.ACTION_STEP.toString().equals(parameters[0])) {
|
||||
Long step = Long.valueOf(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
@@ -30,15 +30,16 @@ import org.openhab.binding.lutron.internal.config.IPBridgeConfig;
|
||||
import org.openhab.binding.lutron.internal.discovery.LutronDeviceDiscoveryService;
|
||||
import org.openhab.binding.lutron.internal.net.TelnetSession;
|
||||
import org.openhab.binding.lutron.internal.net.TelnetSessionListener;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.LIPCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
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.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
@@ -51,9 +52,9 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Added reconnect and heartbeat config parameters, moved discovery service registration to
|
||||
* LutronHandlerFactory
|
||||
*/
|
||||
public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
public class IPBridgeHandler extends LutronBridgeHandler {
|
||||
private static final Pattern RESPONSE_REGEX = Pattern
|
||||
.compile("~(OUTPUT|DEVICE|SYSTEM|TIMECLOCK|MODE|SYSVAR),([0-9\\.:/]+),([0-9,\\.:/]*)\\Z");
|
||||
.compile("~(OUTPUT|DEVICE|SYSTEM|TIMECLOCK|MODE|SYSVAR|GROUP),([0-9\\.:/]+),([0-9,\\.:/]*)\\Z");
|
||||
|
||||
private static final String DB_UPDATE_DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";
|
||||
|
||||
@@ -85,7 +86,7 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
private int sendDelay;
|
||||
|
||||
private TelnetSession session;
|
||||
private BlockingQueue<LutronCommand> sendQueue = new LinkedBlockingQueue<>();
|
||||
private BlockingQueue<LutronCommandNew> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private Thread messageSender;
|
||||
private ScheduledFuture<?> keepAlive;
|
||||
@@ -206,8 +207,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// Disable prompts
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_PROMPT,
|
||||
MONITOR_DISABLE));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.EXECUTE, LutronCommandType.MONITORING, null,
|
||||
MONITOR_PROMPT, MONITOR_DISABLE));
|
||||
|
||||
if (requireSysvarMonitoring.get()) {
|
||||
setSysvarMonitoring(true);
|
||||
@@ -215,7 +216,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
// Check the time device database was last updated. On the initial connect, this will trigger
|
||||
// a scan for paired devices.
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSTEM, -1, SYSTEM_DBEXPORTDATETIME));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.QUERY, LutronCommandType.SYSTEM, null,
|
||||
SYSTEM_DBEXPORTDATETIME));
|
||||
|
||||
messageSender = new Thread(this::sendCommandsThread, "Lutron sender");
|
||||
messageSender.start();
|
||||
@@ -228,7 +230,7 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
private void sendCommandsThread() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
LutronCommand command = sendQueue.take();
|
||||
LutronCommandNew command = sendQueue.take();
|
||||
|
||||
logger.debug("Sending command {}", command);
|
||||
|
||||
@@ -317,8 +319,9 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
void sendCommand(LutronCommand command) {
|
||||
this.sendQueue.add(command);
|
||||
@Override
|
||||
public void sendCommand(LutronCommandNew command) {
|
||||
sendQueue.add(command);
|
||||
}
|
||||
|
||||
private LutronHandler findThingHandler(int integrationId) {
|
||||
@@ -423,7 +426,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
keepAliveReconnect = scheduler.schedule(this::reconnect, KEEPALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
logger.trace("Sending keepalive query");
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSTEM, -1, SYSTEM_DBEXPORTDATETIME));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.QUERY, LutronCommandType.SYSTEM, null,
|
||||
SYSTEM_DBEXPORTDATETIME));
|
||||
}
|
||||
|
||||
private void setDbUpdateDate(String dateString, String timeString) {
|
||||
@@ -455,8 +459,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private void setSysvarMonitoring(boolean enable) {
|
||||
Integer setting = (enable) ? MONITOR_ENABLE : MONITOR_DISABLE;
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_SYSVAR, setting));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.EXECUTE, LutronCommandType.MONITORING, null,
|
||||
MONITOR_SYSVAR, setting));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,802 @@
|
||||
/**
|
||||
* 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.lutron.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.config.LeapBridgeConfig;
|
||||
import org.openhab.binding.lutron.internal.discovery.LeapDeviceDiscoveryService;
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
import org.openhab.binding.lutron.internal.protocol.GroupCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapMessageParser;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapMessageParserCallbacks;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Bridge handler responsible for communicating with Lutron hubs that support the LEAP protocol, such as Caseta and
|
||||
* RA2 Select.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessageParserCallbacks {
|
||||
private static final int DEFAULT_RECONNECT_MINUTES = 5;
|
||||
private static final int DEFAULT_HEARTBEAT_MINUTES = 5;
|
||||
private static final long KEEPALIVE_TIMEOUT_SECONDS = 30;
|
||||
|
||||
private static final String STATUS_INITIALIZING = "Initializing";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LeapBridgeHandler.class);
|
||||
|
||||
private @NonNullByDefault({}) LeapBridgeConfig config;
|
||||
private int reconnectInterval;
|
||||
private int heartbeatInterval;
|
||||
private int sendDelay;
|
||||
|
||||
private @NonNullByDefault({}) SSLSocketFactory sslsocketfactory;
|
||||
private @Nullable SSLSocket sslsocket;
|
||||
private @Nullable BufferedWriter writer;
|
||||
private @Nullable BufferedReader reader;
|
||||
|
||||
private @NonNullByDefault({}) LeapMessageParser leapMessageParser;
|
||||
|
||||
private final BlockingQueue<LeapCommand> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private @Nullable Future<?> asyncInitializeTask;
|
||||
|
||||
private @Nullable Thread senderThread;
|
||||
private @Nullable Thread readerThread;
|
||||
|
||||
private @Nullable ScheduledFuture<?> keepAliveJob;
|
||||
private @Nullable ScheduledFuture<?> keepAliveReconnectJob;
|
||||
private @Nullable ScheduledFuture<?> connectRetryJob;
|
||||
private final Object keepAliveReconnectLock = new Object();
|
||||
|
||||
private final Map<Integer, Integer> zoneToDevice = new HashMap<>();
|
||||
private final Map<Integer, Integer> deviceToZone = new HashMap<>();
|
||||
private final Object zoneMapsLock = new Object();
|
||||
|
||||
private @Nullable Map<Integer, List<Integer>> deviceButtonMap;
|
||||
private final Object deviceButtonMapLock = new Object();
|
||||
|
||||
private volatile boolean deviceDataLoaded = false;
|
||||
private volatile boolean buttonDataLoaded = false;
|
||||
|
||||
private final Map<Integer, LutronHandler> childHandlerMap = new ConcurrentHashMap<>();
|
||||
private final Map<Integer, OGroupHandler> groupHandlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
private @Nullable LeapDeviceDiscoveryService discoveryService;
|
||||
|
||||
public void setDiscoveryService(LeapDeviceDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
public LeapBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
leapMessageParser = new LeapMessageParser(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(LeapDeviceDiscoveryService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
SSLContext sslContext;
|
||||
|
||||
childHandlerMap.clear();
|
||||
groupHandlerMap.clear();
|
||||
|
||||
config = getConfigAs(LeapBridgeConfig.class);
|
||||
String keystorePassword = (config.keystorePassword == null) ? "" : config.keystorePassword;
|
||||
|
||||
String ipAddress = config.ipAddress;
|
||||
if (ipAddress == null || ipAddress.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge address not specified");
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectInterval = (config.reconnect > 0) ? config.reconnect : DEFAULT_RECONNECT_MINUTES;
|
||||
heartbeatInterval = (config.heartbeat > 0) ? config.heartbeat : DEFAULT_HEARTBEAT_MINUTES;
|
||||
sendDelay = (config.delay < 0) ? 0 : config.delay;
|
||||
|
||||
if (config.keystore == null || keystorePassword == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Keystore/keystore password not configured");
|
||||
return;
|
||||
} else {
|
||||
try (FileInputStream keystoreInputStream = new FileInputStream(config.keystore)) {
|
||||
logger.trace("Initializing keystore");
|
||||
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
|
||||
keystore.load(keystoreInputStream, keystorePassword.toCharArray());
|
||||
|
||||
logger.trace("Initializing SSL Context");
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keystore, keystorePassword.toCharArray());
|
||||
|
||||
TrustManager[] trustManagers;
|
||||
if (config.certValidate) {
|
||||
// Use default trust manager which will attempt to validate server certificate from hub
|
||||
TrustManagerFactory tmf = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(keystore);
|
||||
trustManagers = tmf.getTrustManagers();
|
||||
} else {
|
||||
// Use no-op trust manager which will not verify certificates
|
||||
trustManagers = defineNoOpTrustManager();
|
||||
}
|
||||
|
||||
sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
|
||||
sslsocketfactory = sslContext.getSocketFactory();
|
||||
} catch (FileNotFoundException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Keystore file not found");
|
||||
return;
|
||||
} catch (CertificateException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Certificate exception");
|
||||
return;
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Key unrecoverable with supplied password");
|
||||
return;
|
||||
} catch (KeyManagementException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Key management exception");
|
||||
logger.debug("Key management exception", e);
|
||||
return;
|
||||
} catch (KeyStoreException | NoSuchAlgorithmException | IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Error initializing keystore");
|
||||
logger.debug("Error initializing keystore", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Connecting");
|
||||
asyncInitializeTask = scheduler.submit(this::connect); // start the async connect task
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a no-op SSL trust manager which will not verify server or client certificates.
|
||||
*/
|
||||
private TrustManager[] defineNoOpTrustManager() {
|
||||
return new TrustManager[] { new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
|
||||
logger.debug("Assuming client certificate is valid");
|
||||
if (chain != null && logger.isTraceEnabled()) {
|
||||
for (int cert = 0; cert < chain.length; cert++) {
|
||||
logger.trace("Subject DN: {}", chain[cert].getSubjectDN());
|
||||
logger.trace("Issuer DN: {}", chain[cert].getIssuerDN());
|
||||
logger.trace("Serial number {}:", chain[cert].getSerialNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
|
||||
logger.debug("Assuming server certificate is valid");
|
||||
if (chain != null && logger.isTraceEnabled()) {
|
||||
for (int cert = 0; cert < chain.length; cert++) {
|
||||
logger.trace("Subject DN: {}", chain[cert].getSubjectDN());
|
||||
logger.trace("Issuer DN: {}", chain[cert].getIssuerDN());
|
||||
logger.trace("Serial number: {}", chain[cert].getSerialNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate @Nullable [] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
private synchronized void connect() {
|
||||
deviceDataLoaded = false;
|
||||
buttonDataLoaded = false;
|
||||
|
||||
try {
|
||||
logger.debug("Opening SSL connection to {}:{}", config.ipAddress, config.port);
|
||||
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(config.ipAddress, config.port);
|
||||
sslsocket.startHandshake();
|
||||
writer = new BufferedWriter(new OutputStreamWriter(sslsocket.getOutputStream()));
|
||||
reader = new BufferedReader(new InputStreamReader(sslsocket.getInputStream()));
|
||||
this.sslsocket = sslsocket;
|
||||
} catch (UnknownHostException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Unknown host");
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// port out of valid range
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid port number");
|
||||
return;
|
||||
} catch (InterruptedIOException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.debug("Interrupted while establishing connection");
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error opening SSL connection. Check log.");
|
||||
logger.info("Error opening SSL connection: {}", e.getMessage());
|
||||
disconnect(false);
|
||||
scheduleConnectRetry(reconnectInterval); // Possibly a temporary problem. Try again later.
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, STATUS_INITIALIZING);
|
||||
|
||||
Thread readerThread = new Thread(this::readerThreadJob, "Lutron reader");
|
||||
readerThread.setDaemon(true);
|
||||
readerThread.start();
|
||||
this.readerThread = readerThread;
|
||||
|
||||
Thread senderThread = new Thread(this::senderThreadJob, "Lutron sender");
|
||||
senderThread.setDaemon(true);
|
||||
senderThread.start();
|
||||
this.senderThread = senderThread;
|
||||
|
||||
sendCommand(new LeapCommand(Request.getButtonGroups()));
|
||||
queryDiscoveryData();
|
||||
sendCommand(new LeapCommand(Request.subscribeOccupancyGroupStatus()));
|
||||
|
||||
logger.debug("Starting keepalive job with interval {}", heartbeatInterval);
|
||||
keepAliveJob = scheduler.scheduleWithFixedDelay(this::sendKeepAlive, heartbeatInterval, heartbeatInterval,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by connect() and discovery service to request fresh discovery data
|
||||
*/
|
||||
public void queryDiscoveryData() {
|
||||
sendCommand(new LeapCommand(Request.getDevices()));
|
||||
sendCommand(new LeapCommand(Request.getAreas()));
|
||||
sendCommand(new LeapCommand(Request.getOccupancyGroups()));
|
||||
}
|
||||
|
||||
private void scheduleConnectRetry(long waitMinutes) {
|
||||
logger.debug("Scheduling connection retry in {} minutes", waitMinutes);
|
||||
connectRetryJob = scheduler.schedule(this::connect, waitMinutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from bridge, cancel retry and keepalive jobs, stop reader and writer threads, and clean up.
|
||||
*
|
||||
* @param interruptAll Set if reconnect task should be interrupted if running. Should be false when calling from
|
||||
* connect or reconnect, and true when calling from dispose.
|
||||
*/
|
||||
private synchronized void disconnect(boolean interruptAll) {
|
||||
logger.debug("Disconnecting");
|
||||
|
||||
Thread senderThread = this.senderThread;
|
||||
Thread readerThread = this.readerThread;
|
||||
|
||||
ScheduledFuture<?> connectRetryJob = this.connectRetryJob;
|
||||
if (connectRetryJob != null) {
|
||||
connectRetryJob.cancel(true);
|
||||
}
|
||||
ScheduledFuture<?> keepAliveJob = this.keepAliveJob;
|
||||
if (keepAliveJob != null) {
|
||||
keepAliveJob.cancel(true);
|
||||
}
|
||||
|
||||
reconnectTaskCancel(interruptAll); // May be called from keepAliveReconnectJob thread
|
||||
|
||||
if (senderThread != null && senderThread.isAlive()) {
|
||||
senderThread.interrupt();
|
||||
}
|
||||
if (readerThread != null && readerThread.isAlive()) {
|
||||
readerThread.interrupt();
|
||||
}
|
||||
SSLSocket sslsocket = this.sslsocket;
|
||||
if (sslsocket != null) {
|
||||
try {
|
||||
sslsocket.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing SSL socket: {}", e.getMessage());
|
||||
}
|
||||
this.sslsocket = null;
|
||||
}
|
||||
BufferedReader reader = this.reader;
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing reader: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
BufferedWriter writer = this.writer;
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing writer: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
deviceDataLoaded = false;
|
||||
buttonDataLoaded = false;
|
||||
}
|
||||
|
||||
private synchronized void reconnect() {
|
||||
logger.debug("Attempting to reconnect to the bridge");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "reconnecting");
|
||||
disconnect(false);
|
||||
connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed by the message sender thread (senderThread)
|
||||
*/
|
||||
private void senderThreadJob() {
|
||||
logger.debug("Command sender thread started");
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted() && writer != null) {
|
||||
LeapCommand command = sendQueue.take();
|
||||
logger.trace("Sending command {}", command);
|
||||
|
||||
try {
|
||||
BufferedWriter writer = this.writer;
|
||||
if (writer != null) {
|
||||
writer.write(command.toString() + "\n");
|
||||
writer.flush();
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
logger.debug("Interrupted while sending");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Interrupted");
|
||||
break; // exit loop and terminate thread
|
||||
} catch (IOException e) {
|
||||
logger.warn("Communication error, will try to reconnect. Error: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
sendQueue.add(command); // Requeue command
|
||||
reconnect();
|
||||
break; // reconnect() will start a new thread; terminate this one
|
||||
}
|
||||
if (sendDelay > 0) {
|
||||
Thread.sleep(sendDelay); // introduce delay to throttle send rate
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
logger.debug("Command sender thread exiting");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed by the message reader thread (readerThread)
|
||||
*/
|
||||
private void readerThreadJob() {
|
||||
logger.debug("Message reader thread started");
|
||||
String msg = null;
|
||||
try {
|
||||
BufferedReader reader = this.reader;
|
||||
while (!Thread.interrupted() && reader != null && (msg = reader.readLine()) != null) {
|
||||
leapMessageParser.handleMessage(msg);
|
||||
}
|
||||
if (msg == null) {
|
||||
logger.debug("End of input stream detected");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection lost");
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
logger.debug("Interrupted while reading");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Interrupted");
|
||||
} catch (IOException e) {
|
||||
logger.debug("I/O error while reading from stream: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception in reader thread", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
logger.debug("Message reader thread exiting");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if NoContent response received for a buttongroup read request. Creates empty deviceButtonMap.
|
||||
*/
|
||||
@Override
|
||||
public void handleEmptyButtonGroupDefinition() {
|
||||
logger.debug("No content in button group definition. Creating empty deviceButtonMap.");
|
||||
Map<Integer, List<Integer>> deviceButtonMap = new HashMap<>();
|
||||
synchronized (deviceButtonMapLock) {
|
||||
this.deviceButtonMap = deviceButtonMap;
|
||||
buttonDataLoaded = true;
|
||||
}
|
||||
checkInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state to online if offline/initializing and all required initialization info is loaded.
|
||||
* Currently this means device (zone) and button group data.
|
||||
*/
|
||||
private void checkInitialized() {
|
||||
ThingStatusInfo statusInfo = getThing().getStatusInfo();
|
||||
if (statusInfo.getStatus() == ThingStatus.OFFLINE && STATUS_INITIALIZING.equals(statusInfo.getDescription())) {
|
||||
if (deviceDataLoaded && buttonDataLoaded) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify child thing handler of a zonelevel update from a received zone status message.
|
||||
*/
|
||||
@Override
|
||||
public void handleZoneUpdate(ZoneStatus zoneStatus) {
|
||||
logger.trace("Zone: {} level: {}", zoneStatus.getZone(), zoneStatus.level);
|
||||
Integer integrationId = zoneToDevice(zoneStatus.getZone());
|
||||
|
||||
if (integrationId == null) {
|
||||
logger.debug("Unable to map zone {} to device", zoneStatus.getZone());
|
||||
return;
|
||||
}
|
||||
logger.trace("Zone {} mapped to device id {}", zoneStatus.getZone(), integrationId);
|
||||
|
||||
// dispatch update to proper thing handler
|
||||
LutronHandler handler = findThingHandler(integrationId);
|
||||
if (handler != null) {
|
||||
if (zoneStatus.fanSpeed != null) {
|
||||
// handle fan controller
|
||||
FanSpeedType fanSpeed = zoneStatus.fanSpeed;
|
||||
try {
|
||||
handler.handleUpdate(LutronCommandType.OUTPUT, OutputCommand.ACTION_ZONELEVEL.toString(),
|
||||
Integer.valueOf(fanSpeed.speed()).toString());
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number format exception parsing update");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception while processing update");
|
||||
}
|
||||
} else {
|
||||
// handle switch/dimmer/shade
|
||||
try {
|
||||
handler.handleUpdate(LutronCommandType.OUTPUT, OutputCommand.ACTION_ZONELEVEL.toString(),
|
||||
Integer.valueOf(zoneStatus.level).toString());
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number format exception parsing update");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception while processing update");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("No thing configured for integration ID {}", integrationId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify child group handler of a received occupancy group update.
|
||||
*
|
||||
* @param occupancyStatus
|
||||
* @param groupNumber
|
||||
*/
|
||||
@Override
|
||||
public void handleGroupUpdate(int groupNumber, String occupancyStatus) {
|
||||
logger.trace("Group {} state update: {}", groupNumber, occupancyStatus);
|
||||
|
||||
// dispatch update to proper handler
|
||||
OGroupHandler handler = findGroupHandler(groupNumber);
|
||||
if (handler != null) {
|
||||
try {
|
||||
switch (occupancyStatus) {
|
||||
case "Occupied":
|
||||
handler.handleUpdate(LutronCommandType.GROUP, GroupCommand.ACTION_GROUPSTATE.toString(),
|
||||
GroupCommand.STATE_GRP_OCCUPIED.toString());
|
||||
break;
|
||||
case "Unoccupied":
|
||||
handler.handleUpdate(LutronCommandType.GROUP, GroupCommand.ACTION_GROUPSTATE.toString(),
|
||||
GroupCommand.STATE_GRP_UNOCCUPIED.toString());
|
||||
break;
|
||||
case "Unknown":
|
||||
handler.handleUpdate(LutronCommandType.GROUP, GroupCommand.ACTION_GROUPSTATE.toString(),
|
||||
GroupCommand.STATE_GRP_UNKNOWN.toString());
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unexpected occupancy status: {}", occupancyStatus);
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number format exception parsing update");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception while processing update");
|
||||
}
|
||||
} else {
|
||||
logger.debug("No group thing configured for group ID {}", groupNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList) {
|
||||
Map<Integer, List<Integer>> deviceButtonMap = new HashMap<>();
|
||||
|
||||
for (ButtonGroup buttonGroup : buttonGroupList) {
|
||||
int parentDevice = buttonGroup.getParentDevice();
|
||||
logger.trace("Found ButtonGroup: {} parent device: {}", buttonGroup.getButtonGroup(), parentDevice);
|
||||
List<Integer> buttonList = buttonGroup.getButtonList();
|
||||
deviceButtonMap.put(parentDevice, buttonList);
|
||||
}
|
||||
synchronized (deviceButtonMapLock) {
|
||||
this.deviceButtonMap = deviceButtonMap;
|
||||
buttonDataLoaded = true;
|
||||
}
|
||||
checkInitialized();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleDeviceDefintion(List<Device> deviceList) {
|
||||
synchronized (zoneMapsLock) {
|
||||
zoneToDevice.clear();
|
||||
deviceToZone.clear();
|
||||
for (Device device : deviceList) {
|
||||
Integer zoneid = device.getZone();
|
||||
Integer deviceid = device.getDevice();
|
||||
logger.trace("Found device: {} id: {} zone: {}", device.name, deviceid, zoneid);
|
||||
if (zoneid > 0 && deviceid > 0) {
|
||||
zoneToDevice.put(zoneid, deviceid);
|
||||
deviceToZone.put(deviceid, zoneid);
|
||||
}
|
||||
if (deviceid == 1) { // ID 1 is the bridge
|
||||
setBridgeProperties(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
deviceDataLoaded = true;
|
||||
checkInitialized();
|
||||
|
||||
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.processDeviceDefinitions(deviceList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleAreaDefinition(List<Area> areaList) {
|
||||
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.setAreas(areaList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleOccupancyGroupDefinition(List<OccupancyGroup> oGroupList) {
|
||||
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.setOccupancyGroups(oGroupList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validMessageReceived(String communiqueType) {
|
||||
reconnectTaskCancel(true); // Got a good message, so cancel reconnect task.
|
||||
}
|
||||
|
||||
/**
|
||||
* Set informational bridge properties from the Device entry for the hub/repeater
|
||||
*/
|
||||
private void setBridgeProperties(Device device) {
|
||||
if (device.getDevice() == 1 && device.repeaterProperties != null) {
|
||||
Map<String, String> properties = editProperties();
|
||||
if (device.name != null) {
|
||||
properties.put(PROPERTY_PRODTYP, device.name);
|
||||
}
|
||||
if (device.modelNumber != null) {
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, device.modelNumber);
|
||||
}
|
||||
if (device.serialNumber != null) {
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.serialNumber);
|
||||
}
|
||||
if (device.firmwareImage != null && device.firmwareImage.firmware != null
|
||||
&& device.firmwareImage.firmware.displayName != null) {
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.firmwareImage.firmware.displayName);
|
||||
}
|
||||
updateProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a LeapCommand for transmission by the sender thread.
|
||||
*/
|
||||
public void sendCommand(@Nullable LeapCommand command) {
|
||||
if (command != null) {
|
||||
sendQueue.add(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a LutronCommand into a LeapCommand and queue it for transmission by the sender thread.
|
||||
*/
|
||||
@Override
|
||||
public void sendCommand(LutronCommandNew command) {
|
||||
logger.trace("Received request to send Lutron command: {}", command);
|
||||
sendCommand(command.leapCommand(this, deviceToZone(command.getIntegrationId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LEAP button number for given integrationID and component. Returns 0 if button number cannot be
|
||||
* determined.
|
||||
*/
|
||||
public int getButton(int integrationID, int component) {
|
||||
synchronized (deviceButtonMapLock) {
|
||||
if (deviceButtonMap != null) {
|
||||
List<Integer> buttonList = deviceButtonMap.get(integrationID);
|
||||
if (buttonList != null && component <= buttonList.size()) {
|
||||
return buttonList.get(component - 1);
|
||||
} else {
|
||||
logger.debug("Could not find button component {} for id {}", component, integrationID);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Device to button map not populated");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable LutronHandler findThingHandler(@Nullable Integer integrationId) {
|
||||
if (integrationId != null) {
|
||||
return childHandlerMap.get(integrationId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable OGroupHandler findGroupHandler(int integrationId) {
|
||||
return groupHandlerMap.get(integrationId);
|
||||
}
|
||||
|
||||
private @Nullable Integer zoneToDevice(int zone) {
|
||||
synchronized (zoneMapsLock) {
|
||||
return zoneToDevice.get(zone);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Integer deviceToZone(@Nullable Integer device) {
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
synchronized (zoneMapsLock) {
|
||||
return deviceToZone.get(device);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendKeepAlive() {
|
||||
logger.trace("Sending keepalive query");
|
||||
sendCommand(new LeapCommand(Request.ping()));
|
||||
// Reconnect if no response is received within KEEPALIVE_TIMEOUT_SECONDS.
|
||||
reconnectTaskSchedule();
|
||||
}
|
||||
|
||||
private void reconnectTaskSchedule() {
|
||||
synchronized (keepAliveReconnectLock) {
|
||||
keepAliveReconnectJob = scheduler.schedule(this::reconnect, KEEPALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void reconnectTaskCancel(boolean interrupt) {
|
||||
synchronized (keepAliveReconnectLock) {
|
||||
ScheduledFuture<?> keepAliveReconnectJob = this.keepAliveReconnectJob;
|
||||
if (keepAliveReconnectJob != null) {
|
||||
logger.trace("Canceling scheduled reconnect job.");
|
||||
keepAliveReconnectJob.cancel(interrupt);
|
||||
this.keepAliveReconnectJob = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_COMMAND)) {
|
||||
if (command instanceof StringType) {
|
||||
sendCommand(new LeapCommand(command.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||
if (childHandler instanceof OGroupHandler) {
|
||||
// We need a different map for group things because the numbering is separate
|
||||
OGroupHandler handler = (OGroupHandler) childHandler;
|
||||
int groupId = handler.getIntegrationId();
|
||||
groupHandlerMap.put(groupId, handler);
|
||||
logger.trace("Registered group handler for ID {}", groupId);
|
||||
} else {
|
||||
LutronHandler handler = (LutronHandler) childHandler;
|
||||
int intId = handler.getIntegrationId();
|
||||
childHandlerMap.put(intId, handler);
|
||||
logger.trace("Registered child handler for ID {}", intId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
|
||||
if (childHandler instanceof OGroupHandler) {
|
||||
OGroupHandler handler = (OGroupHandler) childHandler;
|
||||
int groupId = handler.getIntegrationId();
|
||||
groupHandlerMap.remove(groupId);
|
||||
logger.trace("Unregistered group handler for ID {}", groupId);
|
||||
} else {
|
||||
LutronHandler handler = (LutronHandler) childHandler;
|
||||
int intId = handler.getIntegrationId();
|
||||
childHandlerMap.remove(intId);
|
||||
logger.trace("Unregistered child handler for ID {}", intId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
Future<?> asyncInitializeTask = this.asyncInitializeTask;
|
||||
if (asyncInitializeTask != null && !asyncInitializeTask.isDone()) {
|
||||
asyncInitializeTask.cancel(true); // Interrupt async init task if it isn't done yet
|
||||
}
|
||||
disconnect(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.lutron.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
|
||||
/**
|
||||
* Abstract base class for Lutron bridge handlers
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LutronBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
public LutronBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
public abstract void sendCommand(LutronCommandNew command);
|
||||
}
|
||||
@@ -14,9 +14,17 @@ package org.openhab.binding.lutron.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.DeviceCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.GroupCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronDuration;
|
||||
import org.openhab.binding.lutron.internal.protocol.ModeCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.SysvarCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.TimeclockCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
@@ -30,8 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||
* Base type for all Lutron thing handlers.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added additional commands and methods for status and state management
|
||||
*
|
||||
* @author Bob Adair - Added additional commands and methods for status and state management. Added TargetType to
|
||||
* LutronCommand for LEAP bridge.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LutronHandler extends BaseThingHandler {
|
||||
@@ -58,10 +66,10 @@ public abstract class LutronHandler extends BaseThingHandler {
|
||||
protected void thingOfflineNotify() {
|
||||
}
|
||||
|
||||
protected @Nullable IPBridgeHandler getBridgeHandler() {
|
||||
protected @Nullable LutronBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
|
||||
return bridge == null ? null : (IPBridgeHandler) bridge.getHandler();
|
||||
return bridge == null ? null : (LutronBridgeHandler) bridge.getHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,8 +87,8 @@ public abstract class LutronHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCommand(LutronCommand command) {
|
||||
IPBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
protected void sendCommand(LutronCommandNew command) {
|
||||
LutronBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR, "No bridge associated");
|
||||
@@ -90,58 +98,52 @@ public abstract class LutronHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected void output(Object... parameters) {
|
||||
protected void output(TargetType type, int action, @Nullable Number parameter, @Nullable LutronDuration fade,
|
||||
@Nullable LutronDuration delay) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.OUTPUT, getIntegrationId(), parameters));
|
||||
new OutputCommand(type, LutronOperation.EXECUTE, getIntegrationId(), action, parameter, fade, delay));
|
||||
}
|
||||
|
||||
protected void device(Object... parameters) {
|
||||
protected void queryOutput(TargetType type, int action) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.DEVICE, getIntegrationId(), parameters));
|
||||
new OutputCommand(type, LutronOperation.QUERY, getIntegrationId(), action, (Integer) null, null, null));
|
||||
}
|
||||
|
||||
protected void timeclock(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.TIMECLOCK, getIntegrationId(),
|
||||
parameters));
|
||||
protected void device(TargetType type, Integer component, @Nullable Integer leapComponent, Integer action,
|
||||
@Nullable Object parameter) {
|
||||
sendCommand(new DeviceCommand(type, LutronOperation.EXECUTE, getIntegrationId(), component, leapComponent,
|
||||
action, parameter));
|
||||
}
|
||||
|
||||
protected void greenMode(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MODE, getIntegrationId(), parameters));
|
||||
protected void queryDevice(TargetType type, Integer component, Integer action) {
|
||||
sendCommand(new DeviceCommand(type, LutronOperation.QUERY, getIntegrationId(), component, null, action, null));
|
||||
}
|
||||
|
||||
protected void sysvar(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SYSVAR, getIntegrationId(), parameters));
|
||||
protected void timeclock(Integer action, @Nullable Object parameter, @Nullable Boolean enable) {
|
||||
sendCommand(new TimeclockCommand(LutronOperation.EXECUTE, getIntegrationId(), action, parameter, enable));
|
||||
}
|
||||
|
||||
protected void shadegrp(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SHADEGRP, getIntegrationId(), parameters));
|
||||
protected void queryTimeclock(Integer action) {
|
||||
sendCommand(new TimeclockCommand(LutronOperation.QUERY, getIntegrationId(), action, null, null));
|
||||
}
|
||||
|
||||
protected void queryOutput(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.OUTPUT, getIntegrationId(), parameters));
|
||||
protected void sysvar(Integer action, Object parameter) {
|
||||
sendCommand(new SysvarCommand(LutronOperation.EXECUTE, getIntegrationId(), action, parameter));
|
||||
}
|
||||
|
||||
protected void queryDevice(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.DEVICE, getIntegrationId(), parameters));
|
||||
protected void querySysvar(Integer action) {
|
||||
sendCommand(new SysvarCommand(LutronOperation.QUERY, getIntegrationId(), action, null));
|
||||
}
|
||||
|
||||
protected void queryTimeclock(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.QUERY, LutronCommandType.TIMECLOCK, getIntegrationId(), parameters));
|
||||
protected void greenMode(Integer action, @Nullable Integer parameter) {
|
||||
sendCommand(new ModeCommand(LutronOperation.EXECUTE, getIntegrationId(), action, parameter));
|
||||
}
|
||||
|
||||
protected void queryGreenMode(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.MODE, getIntegrationId(), parameters));
|
||||
protected void queryGreenMode(Integer action) {
|
||||
sendCommand(new ModeCommand(LutronOperation.QUERY, getIntegrationId(), action, null));
|
||||
}
|
||||
|
||||
protected void querySysvar(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSVAR, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void queryShadegrp(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.QUERY, LutronCommandType.SHADEGRP, getIntegrationId(), parameters));
|
||||
protected void queryGroup(Integer action) {
|
||||
sendCommand(new GroupCommand(LutronOperation.QUERY, getIntegrationId(), action, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 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.lutron.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_GROUPSTATE;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.config.OGroupConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.GroupCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating occupancy group states.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OGroupHandler extends LutronHandler {
|
||||
private static final String STATE_OCCUPIED = "OCCUPIED";
|
||||
private static final String STATE_UNOCCUPIED = "UNOCCUPIED";
|
||||
private static final String STATE_UNKNOWN = "UNKNOWN";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OGroupHandler.class);
|
||||
|
||||
private @NonNullByDefault({}) OGroupConfig config;
|
||||
|
||||
public OGroupHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
if (this.config == null) {
|
||||
throw new IllegalStateException("handler not initialized");
|
||||
}
|
||||
return config.integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(OGroupConfig.class);
|
||||
if (config.integrationId <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"No valid integrationId configured");
|
||||
return;
|
||||
}
|
||||
logger.debug("Initializing Occupancy Group handler for integration ID {}", getIntegrationId());
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Occupancy Group {}", getIntegrationId());
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryGroup(GroupCommand.ACTION_GROUPSTATE);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_GROUPSTATE)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryGroup(GroupCommand.ACTION_GROUPSTATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_GROUPSTATE)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryGroup(GroupCommand.ACTION_GROUPSTATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
int state;
|
||||
|
||||
if (type == LutronCommandType.GROUP && parameters.length > 1
|
||||
&& GroupCommand.ACTION_GROUPSTATE.toString().equals(parameters[0])) {
|
||||
try {
|
||||
state = Integer.parseInt(parameters[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Error parsing response parameter: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
if (state == GroupCommand.STATE_GRP_OCCUPIED) {
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_OCCUPIED));
|
||||
} else if (state == GroupCommand.STATE_GRP_UNOCCUPIED) {
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_UNOCCUPIED));
|
||||
} else if (state == GroupCommand.STATE_GRP_UNKNOWN) {
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_UNKNOWN));
|
||||
} else {
|
||||
logger.debug("Invalid occupancy state received: {}", state);
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_UNKNOWN));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ package org.openhab.binding.lutron.internal.handler;
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_OCCUPANCYSTATUS;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.DeviceCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -33,10 +34,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OccupancySensorHandler extends LutronHandler {
|
||||
private static final String OCCUPIED_STATE_UPDATE = "2";
|
||||
private static final String STATE_OCCUPIED = "3";
|
||||
private static final String STATE_UNOCCUPIED = "4";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OccupancySensorHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@@ -82,10 +79,11 @@ public class OccupancySensorHandler extends LutronHandler {
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.DEVICE && parameters.length == 2 && OCCUPIED_STATE_UPDATE.equals(parameters[0])) {
|
||||
if (STATE_OCCUPIED.equals(parameters[1])) {
|
||||
if (type == LutronCommandType.DEVICE && parameters.length == 2
|
||||
&& DeviceCommand.OCCUPIED_STATE_COMPONENT.equals(parameters[0])) {
|
||||
if (DeviceCommand.STATE_OCCUPIED.equals(parameters[1])) {
|
||||
updateState(CHANNEL_OCCUPANCYSTATUS, OnOffType.ON);
|
||||
} else if (STATE_UNOCCUPIED.equals(parameters[1])) {
|
||||
} else if (DeviceCommand.STATE_UNOCCUPIED.equals(parameters[1])) {
|
||||
updateState(CHANNEL_OCCUPANCYSTATUS, OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,20 @@ public class PicoKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
switch (mod) {
|
||||
case "2B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_2B;
|
||||
break;
|
||||
case "2BRL":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_2BRL;
|
||||
break;
|
||||
case "3B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_3B;
|
||||
break;
|
||||
case "4B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_4B;
|
||||
break;
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming model 3BRL.", mod);
|
||||
@@ -53,6 +63,7 @@ public class PicoKeypadHandler extends BaseKeypadHandler {
|
||||
case "Generic":
|
||||
case "3BRL":
|
||||
buttonList = kp.getComponents("3BRL", ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_3BRL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
@@ -38,16 +40,12 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadeHandler extends LutronHandler {
|
||||
private static final Integer ACTION_ZONELEVEL = 1;
|
||||
private static final Integer ACTION_STARTRAISING = 2;
|
||||
private static final Integer ACTION_STARTLOWERING = 3;
|
||||
private static final Integer ACTION_STOP = 4;
|
||||
private static final Integer ACTION_POSITION_UPDATE = 32; // undocumented in integration protocol guide
|
||||
private static final Integer PARAMETER_POSITION_UPDATE = 2; // undocumented in integration protocol guide
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShadeHandler.class);
|
||||
|
||||
protected int integrationId;
|
||||
private boolean leap = false;
|
||||
|
||||
public ShadeHandler(Thing thing) {
|
||||
super(thing);
|
||||
@@ -68,6 +66,11 @@ public class ShadeHandler extends LutronHandler {
|
||||
integrationId = id.intValue();
|
||||
logger.debug("Initializing Shade handler for integration ID {}", id);
|
||||
|
||||
LutronBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler instanceof LeapBridgeHandler) {
|
||||
leap = true;
|
||||
}
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@@ -79,7 +82,8 @@ public class ShadeHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@@ -89,7 +93,7 @@ public class ShadeHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
// Refresh state when new item is linked.
|
||||
if (channelUID.getId().equals(CHANNEL_SHADELEVEL)) {
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,15 +102,27 @@ public class ShadeHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_SHADELEVEL)) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_ZONELEVEL, level, 0);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL, level, null, null);
|
||||
if (leap) {
|
||||
// LEAP may not send back a position update
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(level));
|
||||
}
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISING);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_STARTRAISING, null, null, null);
|
||||
if (leap) {
|
||||
// LEAP won't send a position update when fully open
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(100));
|
||||
}
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERING);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_STARTLOWERING, null, null, null);
|
||||
if (leap) {
|
||||
// LEAP won't send a position update when fully closed
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(0));
|
||||
}
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOP);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_STOP, null, null, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,14 +130,14 @@ public class ShadeHandler extends LutronHandler {
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length >= 2) {
|
||||
if (ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
if (OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
logger.trace("Shade {} received zone level: {}", getIntegrationId(), level);
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(level));
|
||||
} else if (ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
} else if (OutputCommand.ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
&& PARAMETER_POSITION_UPDATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
BigDecimal level = new BigDecimal(parameters[2]);
|
||||
logger.trace("Shade {} received position update: {}", getIntegrationId(), level);
|
||||
|
||||
@@ -17,7 +17,9 @@ import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -36,8 +38,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SwitchHandler extends LutronHandler {
|
||||
private static final Integer ACTION_ZONELEVEL = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SwitchHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@@ -67,7 +67,8 @@ public class SwitchHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@@ -77,9 +78,9 @@ public class SwitchHandler extends LutronHandler {
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
output(ACTION_ZONELEVEL, 100);
|
||||
output(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL, 100, null, null);
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
output(ACTION_ZONELEVEL, 0);
|
||||
output(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL, 0, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +93,7 @@ public class SwitchHandler extends LutronHandler {
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
&& OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
@@ -105,7 +106,7 @@ public class SwitchHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import java.math.BigDecimal;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.config.SysvarConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.SysvarCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -38,8 +39,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SysvarHandler extends LutronHandler {
|
||||
private static final Integer ACTION_GETSETSYSVAR = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SysvarHandler.class);
|
||||
|
||||
private @Nullable SysvarConfig config;
|
||||
@@ -80,7 +79,8 @@ public class SysvarHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
querySysvar(ACTION_GETSETSYSVAR); // handleUpdate() will set thing status to online when response arrives
|
||||
querySysvar(SysvarCommand.ACTION_GETSETSYSVAR);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@@ -90,7 +90,7 @@ public class SysvarHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_VARSTATE)) {
|
||||
// Refresh state when new item is linked.
|
||||
querySysvar(ACTION_GETSETSYSVAR);
|
||||
querySysvar(SysvarCommand.ACTION_GETSETSYSVAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class SysvarHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_VARSTATE)) {
|
||||
if (command instanceof Number) {
|
||||
int state = ((Number) command).intValue();
|
||||
sysvar(ACTION_GETSETSYSVAR, state);
|
||||
sysvar(SysvarCommand.ACTION_GETSETSYSVAR, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public class SysvarHandler extends LutronHandler {
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.SYSVAR && parameters.length > 1
|
||||
&& ACTION_GETSETSYSVAR.toString().equals(parameters[0])) {
|
||||
&& SysvarCommand.ACTION_GETSETSYSVAR.toString().equals(parameters[0])) {
|
||||
BigDecimal state = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
@@ -20,7 +20,8 @@ import java.util.Calendar;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.TimeclockCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@@ -40,14 +41,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TimeclockHandler extends LutronHandler {
|
||||
private static final Integer ACTION_CLOCKMODE = 1;
|
||||
private static final Integer ACTION_SUNRISE = 2;
|
||||
private static final Integer ACTION_SUNSET = 3;
|
||||
private static final Integer ACTION_EXECEVENT = 5;
|
||||
private static final Integer ACTION_SETEVENT = 6;
|
||||
private static final Integer EVENT_ENABLE = 1;
|
||||
private static final Integer EVENT_DISABLE = 2;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TimeclockHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@@ -81,7 +74,8 @@ public class TimeclockHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryTimeclock(ACTION_CLOCKMODE); // handleUpdate() will set thing status to online when response arrives
|
||||
queryTimeclock(TimeclockCommand.ACTION_CLOCKMODE);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@@ -91,7 +85,7 @@ public class TimeclockHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
logger.debug("Handling channel link request for timeclock {}", integrationId);
|
||||
if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
|
||||
queryTimeclock(ACTION_CLOCKMODE);
|
||||
queryTimeclock(TimeclockCommand.ACTION_CLOCKMODE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,42 +97,42 @@ public class TimeclockHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer mode = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_CLOCKMODE, mode);
|
||||
timeclock(TimeclockCommand.ACTION_CLOCKMODE, mode, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryTimeclock(ACTION_CLOCKMODE);
|
||||
queryTimeclock(TimeclockCommand.ACTION_CLOCKMODE);
|
||||
} else {
|
||||
logger.debug("Invalid command type for clockmode channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_EXECEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_EXECEVENT, index);
|
||||
timeclock(TimeclockCommand.ACTION_EXECEVENT, index, null);
|
||||
} else {
|
||||
logger.debug("Invalid command type for execevent channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_SUNRISE)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryTimeclock(ACTION_SUNRISE);
|
||||
queryTimeclock(TimeclockCommand.ACTION_SUNRISE);
|
||||
} else {
|
||||
logger.debug("Invalid command type for sunrise channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_SUNSET)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryTimeclock(ACTION_SUNSET);
|
||||
queryTimeclock(TimeclockCommand.ACTION_SUNSET);
|
||||
} else {
|
||||
logger.debug("Invalid command type for sunset channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_ENABLEEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_SETEVENT, index, EVENT_ENABLE);
|
||||
timeclock(TimeclockCommand.ACTION_SETEVENT, index, true);
|
||||
} else {
|
||||
logger.debug("Invalid command type for enableevent channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_DISABLEEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_SETEVENT, index, EVENT_DISABLE);
|
||||
timeclock(TimeclockCommand.ACTION_SETEVENT, index, false);
|
||||
} else {
|
||||
logger.debug("Invalid command type for disableevent channnel");
|
||||
}
|
||||
@@ -172,37 +166,37 @@ public class TimeclockHandler extends LutronHandler {
|
||||
logger.debug("Handling update received from timeclock {}", integrationId);
|
||||
|
||||
try {
|
||||
if (parameters.length >= 2 && ACTION_CLOCKMODE.toString().equals(parameters[0])) {
|
||||
if (parameters.length >= 2 && TimeclockCommand.ACTION_CLOCKMODE.toString().equals(parameters[0])) {
|
||||
Integer mode = Integer.valueOf(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
updateState(CHANNEL_CLOCKMODE, new DecimalType(mode));
|
||||
|
||||
} else if (parameters.length >= 2 && ACTION_SUNRISE.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 2 && TimeclockCommand.ACTION_SUNRISE.toString().equals(parameters[0])) {
|
||||
Calendar calendar = parseLutronTime(parameters[1]);
|
||||
if (calendar != null) {
|
||||
updateState(CHANNEL_SUNRISE,
|
||||
new DateTimeType(ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault())));
|
||||
}
|
||||
|
||||
} else if (parameters.length >= 2 && ACTION_SUNSET.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 2 && TimeclockCommand.ACTION_SUNSET.toString().equals(parameters[0])) {
|
||||
Calendar calendar = parseLutronTime(parameters[1]);
|
||||
if (calendar != null) {
|
||||
updateState(CHANNEL_SUNSET,
|
||||
new DateTimeType(ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault())));
|
||||
}
|
||||
|
||||
} else if (parameters.length >= 2 && ACTION_EXECEVENT.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 2 && TimeclockCommand.ACTION_EXECEVENT.toString().equals(parameters[0])) {
|
||||
Integer index = Integer.valueOf(parameters[1]);
|
||||
updateState(CHANNEL_EXECEVENT, new DecimalType(index));
|
||||
|
||||
} else if (parameters.length >= 3 && ACTION_SETEVENT.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 3 && TimeclockCommand.ACTION_SETEVENT.toString().equals(parameters[0])) {
|
||||
Integer index = Integer.valueOf(parameters[1]);
|
||||
Integer state = Integer.valueOf(parameters[2]);
|
||||
if (state.equals(EVENT_ENABLE)) {
|
||||
if (state.equals(TimeclockCommand.EVENT_ENABLE)) {
|
||||
updateState(CHANNEL_ENABLEEVENT, new DecimalType(index));
|
||||
} else if (state.equals(EVENT_DISABLE)) {
|
||||
} else if (state.equals(TimeclockCommand.EVENT_DISABLE)) {
|
||||
updateState(CHANNEL_DISABLEEVENT, new DecimalType(index));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -100,5 +101,6 @@ public class VirtualKeypadHandler extends BaseKeypadHandler {
|
||||
super(thing);
|
||||
// Mark all channels "Advanced" since most are unlikely to be used in any particular config
|
||||
advancedChannels = true;
|
||||
commandTargetType = TargetType.VIRTUALKEYPAD; // For the LEAP bridge
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
package org.openhab.binding.lutron.internal.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
@@ -26,6 +27,13 @@ import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigPico extends KeypadConfig {
|
||||
|
||||
// Button mappings for LEAP protocol
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_2B = Map.of(2, 1, 4, 2);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_2BRL = Map.of(2, 1, 4, 2, 5, 3, 6, 4);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_3B = Map.of(2, 1, 3, 2, 4, 3);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_4B = Map.of(8, 1, 9, 2, 10, 3, 11, 4);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_3BRL = Map.of(2, 1, 3, 2, 4, 3, 5, 4, 6, 5);
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
// Buttons for 2B, 2BRL, 3B, and 3BRL models
|
||||
BUTTON1(2, "button1", "Button 1", ComponentType.BUTTON),
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.CommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Lutron DEVICE command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceCommand extends LutronCommandNew {
|
||||
// keypad defs
|
||||
public static final Integer ACTION_PRESS = 3;
|
||||
public static final Integer ACTION_RELEASE = 4;
|
||||
public static final Integer ACTION_HOLD = 5;
|
||||
public static final Integer ACTION_LED_STATE = 9;
|
||||
public static final Integer LED_OFF = 0;
|
||||
public static final Integer LED_ON = 1;
|
||||
public static final Integer LED_FLASH = 2; // Same as 1 on RA2 keypads
|
||||
public static final Integer LED_RAPIDFLASH = 3; // Same as 1 on RA2 keypads
|
||||
|
||||
// occupancy sensor defs
|
||||
public static final String OCCUPIED_STATE_COMPONENT = "2";
|
||||
public static final String STATE_OCCUPIED = "3";
|
||||
public static final String STATE_UNOCCUPIED = "4";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DeviceCommand.class);
|
||||
|
||||
private final Integer component;
|
||||
private final @Nullable Integer leapComponent;
|
||||
private final Integer action;
|
||||
private final @Nullable Object parameter;
|
||||
|
||||
public DeviceCommand(TargetType targetType, LutronOperation operation, Integer integrationId, Integer component,
|
||||
@Nullable Integer leapComponent, Integer action, @Nullable Object parameter) {
|
||||
super(targetType, operation, LutronCommandType.DEVICE, integrationId);
|
||||
this.action = action;
|
||||
this.component = component;
|
||||
this.leapComponent = leapComponent;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(component);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
if (targetType == TargetType.KEYPAD) {
|
||||
if (leapComponent == null) {
|
||||
logger.debug("Ignoring device command. No leap component in command.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer integrationId = this.integrationId;
|
||||
Integer leapComponent = this.leapComponent; // make the broken null checker happy
|
||||
if (action.equals(DeviceCommand.ACTION_PRESS) && integrationId != null && leapComponent != null) {
|
||||
int button = bridgeHandler.getButton(integrationId, leapComponent);
|
||||
if (button > 0) {
|
||||
return new LeapCommand(Request.buttonCommand(button, CommandType.PRESSANDHOLD));
|
||||
}
|
||||
} else if (action.equals(DeviceCommand.ACTION_RELEASE) && integrationId != null && leapComponent != null) {
|
||||
int button = bridgeHandler.getButton(integrationId, leapComponent);
|
||||
if (button > 0) {
|
||||
return new LeapCommand(Request.buttonCommand(button, CommandType.RELEASE));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring device command with unsupported action.");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.VIRTUALKEYPAD) {
|
||||
if (action.equals(DeviceCommand.ACTION_PRESS)) {
|
||||
return new LeapCommand(Request.virtualButtonCommand(component, CommandType.PRESSANDRELEASE));
|
||||
} else if (!action.equals(DeviceCommand.ACTION_RELEASE)) {
|
||||
logger.debug("Ignoring device command with unsupported action.");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring device command with unsupported target type.");
|
||||
return null;
|
||||
}
|
||||
logger.debug("Ignoring unsupported device command.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Defines Lutron fan controller speed settings
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum FanSpeedType {
|
||||
@SerializedName("High")
|
||||
HIGH(100, "High"),
|
||||
@SerializedName("MediumHigh")
|
||||
MEDIUMHIGH(75, "MediumHigh"),
|
||||
@SerializedName("Medium")
|
||||
MEDIUM(50, "Medium"),
|
||||
@SerializedName("Low")
|
||||
LOW(25, "Low"),
|
||||
@SerializedName("Off")
|
||||
OFF(0, "Off");
|
||||
|
||||
/** Fan speed expressed as a percentage **/
|
||||
private final int speed;
|
||||
|
||||
/** Fan speed expressed as a String (used by LEAP) **/
|
||||
private final String leapValue;
|
||||
|
||||
FanSpeedType(int speed, String leapValue) {
|
||||
this.speed = speed;
|
||||
this.leapValue = leapValue;
|
||||
}
|
||||
|
||||
public int speed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public String leapValue() {
|
||||
return leapValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return leapValue;
|
||||
}
|
||||
|
||||
public static FanSpeedType toFanSpeedType(int percentage) {
|
||||
if (percentage == OFF.speed) {
|
||||
return FanSpeedType.OFF;
|
||||
} else if (percentage > OFF.speed && percentage <= LOW.speed) {
|
||||
return FanSpeedType.LOW;
|
||||
} else if (percentage > LOW.speed && percentage <= MEDIUM.speed) {
|
||||
return FanSpeedType.MEDIUM;
|
||||
} else if (percentage > MEDIUM.speed && percentage <= MEDIUMHIGH.speed) {
|
||||
return FanSpeedType.MEDIUMHIGH;
|
||||
} else {
|
||||
return FanSpeedType.HIGH;
|
||||
}
|
||||
}
|
||||
|
||||
public static FanSpeedType toFanSpeedType(String speedString) {
|
||||
for (FanSpeedType enumValue : FanSpeedType.values()) {
|
||||
if (enumValue.leapValue.equalsIgnoreCase(speedString)) {
|
||||
return enumValue;
|
||||
}
|
||||
}
|
||||
return OFF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron GROUP command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_GROUPSTATE = 3;
|
||||
public static final Integer STATE_GRP_OCCUPIED = 3;
|
||||
public static final Integer STATE_GRP_UNOCCUPIED = 4;
|
||||
public static final Integer STATE_GRP_UNKNOWN = 255;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Integer state;
|
||||
|
||||
/**
|
||||
* GroupCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param state
|
||||
*/
|
||||
public GroupCommand(LutronOperation operation, Integer integrationId, Integer action, @Nullable Integer state) {
|
||||
super(TargetType.GROUP, operation, LutronCommandType.GROUP, integrationId);
|
||||
this.action = action;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (state != null) {
|
||||
builder.append(',').append(state);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
if (action.equals(GroupCommand.ACTION_GROUPSTATE)) {
|
||||
// Get status for all occupancy groups because you can't query just one
|
||||
return new LeapCommand(Request.getOccupancyGroupStatus());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Generic LIP command for use inside bridge handler
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LIPCommand extends LutronCommandNew {
|
||||
private final Object[] parameters;
|
||||
|
||||
public LIPCommand(TargetType targetType, LutronOperation operation, LutronCommandType CommandType,
|
||||
@Nullable Integer integrationId, Object... parameters) {
|
||||
super(targetType, operation, CommandType, integrationId);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
if (integrationId != null) {
|
||||
builder.append(',').append(integrationId);
|
||||
}
|
||||
if (parameters != null) { // This CAN be null
|
||||
for (Object parameter : parameters) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
|
||||
public int getNumberParameter(int position) {
|
||||
if (parameters.length > position && parameters[position] instanceof Number) {
|
||||
Number num = (Number) parameters[position];
|
||||
return num.intValue();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid command parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
/**
|
||||
* Command to a Lutron integration access point.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class LutronCommand {
|
||||
private final LutronOperation operation;
|
||||
private final LutronCommandType type;
|
||||
private final int integrationId;
|
||||
private final Object[] parameters;
|
||||
|
||||
public LutronCommand(LutronOperation operation, LutronCommandType type, int integrationId, Object... parameters) {
|
||||
this.operation = operation;
|
||||
this.type = type;
|
||||
this.integrationId = integrationId;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public LutronCommandType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public int getIntegrationId() {
|
||||
return this.integrationId;
|
||||
}
|
||||
|
||||
public Object[] getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder().append(this.operation).append(this.type);
|
||||
|
||||
if (integrationId >= 0) {
|
||||
builder.append(',').append(this.integrationId);
|
||||
}
|
||||
|
||||
if (parameters != null) {
|
||||
for (Object parameter : parameters) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron command abstract base class
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LutronCommandNew {
|
||||
public final TargetType targetType;
|
||||
protected final LutronOperation operation;
|
||||
protected final LutronCommandType commandType;
|
||||
protected final @Nullable Integer integrationId;
|
||||
|
||||
public LutronCommandNew(TargetType targetType, LutronOperation operation, LutronCommandType type,
|
||||
@Nullable Integer integrationId) {
|
||||
this.targetType = targetType;
|
||||
this.operation = operation;
|
||||
this.commandType = type;
|
||||
this.integrationId = integrationId;
|
||||
}
|
||||
|
||||
public LutronCommandType getType() {
|
||||
return commandType;
|
||||
}
|
||||
|
||||
public LutronOperation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public @Nullable Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
public abstract String lipCommand();
|
||||
|
||||
public abstract @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone);
|
||||
}
|
||||
@@ -131,7 +131,15 @@ public class LutronDuration {
|
||||
}
|
||||
|
||||
public String asLeapString() {
|
||||
return ""; // TBD
|
||||
Integer seconds = this.seconds;
|
||||
if (seconds.equals(0) && hundredths > 0) {
|
||||
// use 1 second if interval is > 0 and < 1
|
||||
seconds = 1;
|
||||
} else if (hundredths >= 50) {
|
||||
// else apply normal rounding of hundredths
|
||||
seconds++;
|
||||
}
|
||||
return String.format("%02d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, (seconds % 60));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron MODE command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModeCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_STEP = 1;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Integer parameter;
|
||||
|
||||
public ModeCommand(LutronOperation operation, Integer integrationId, Integer action, @Nullable Integer parameter) {
|
||||
super(TargetType.GREENMODE, operation, LutronCommandType.MODE, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.CommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Lutron OUTPUT command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OutputCommand extends LutronCommandNew {
|
||||
// shade, blind, dimmer defs
|
||||
public static final Integer ACTION_ZONELEVEL = 1;
|
||||
public static final Integer ACTION_STARTRAISING = 2;
|
||||
public static final Integer ACTION_STARTLOWERING = 3;
|
||||
public static final Integer ACTION_STOP = 4;
|
||||
public static final Integer ACTION_POSITION_UPDATE = 32; // For shades/blinds. Undocumented in protocol guide.
|
||||
|
||||
// blind defs
|
||||
public static final Integer ACTION_LIFTLEVEL = 1;
|
||||
public static final Integer ACTION_TILTLEVEL = 9;
|
||||
public static final Integer ACTION_LIFTTILTLEVEL = 10;
|
||||
public static final Integer ACTION_STARTRAISINGTILT = 11;
|
||||
public static final Integer ACTION_STARTLOWERINGTILT = 12;
|
||||
public static final Integer ACTION_STOPTILT = 13;
|
||||
public static final Integer ACTION_STARTRAISINGLIFT = 14;
|
||||
public static final Integer ACTION_STARTLOWERINGLIFT = 15;
|
||||
public static final Integer ACTION_STOPLIFT = 16;
|
||||
|
||||
// cco defs
|
||||
public static final Integer ACTION_STATE = 1;
|
||||
public static final Integer ACTION_PULSE = 6;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OutputCommand.class);
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Number parameter;
|
||||
private final @Nullable LutronDuration fadeTime;
|
||||
private final @Nullable LutronDuration delayTime;
|
||||
private final FanSpeedType fanSpeed;
|
||||
|
||||
/**
|
||||
* OutputCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param parameter
|
||||
* @param fadeTime
|
||||
* @param delayTime
|
||||
*/
|
||||
public OutputCommand(TargetType targetType, LutronOperation operation, Integer integrationId, Integer action,
|
||||
@Nullable Number parameter, @Nullable LutronDuration fadeTime, @Nullable LutronDuration delayTime) {
|
||||
super(targetType, operation, LutronCommandType.OUTPUT, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
if (parameter != null) {
|
||||
this.fanSpeed = FanSpeedType.toFanSpeedType(parameter.intValue());
|
||||
} else {
|
||||
this.fanSpeed = FanSpeedType.OFF;
|
||||
}
|
||||
this.fadeTime = fadeTime;
|
||||
this.delayTime = delayTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* OutputCommand constructor for fan commands
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param fanSpeed
|
||||
* @param fadeTime
|
||||
* @param delayTime
|
||||
*/
|
||||
public OutputCommand(TargetType targetType, LutronOperation operation, Integer integrationId, Integer action,
|
||||
FanSpeedType fanSpeed, @Nullable LutronDuration fadeTime, @Nullable LutronDuration delayTime) {
|
||||
super(targetType, operation, LutronCommandType.OUTPUT, integrationId);
|
||||
this.action = action;
|
||||
this.fanSpeed = fanSpeed;
|
||||
this.parameter = fanSpeed.speed();
|
||||
this.fadeTime = fadeTime;
|
||||
this.delayTime = delayTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
|
||||
if (parameter != null && targetType == TargetType.CCO && action.equals(OutputCommand.ACTION_PULSE)) {
|
||||
builder.append(',').append(String.format(Locale.ROOT, "%.2f", parameter));
|
||||
} else if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
if (fadeTime != null) {
|
||||
builder.append(',').append(fadeTime);
|
||||
} else if (fadeTime == null && delayTime != null) {
|
||||
// must add 0 placeholder here in order to set delay time
|
||||
builder.append(',').append("0");
|
||||
}
|
||||
if (delayTime != null) {
|
||||
builder.append(',').append(delayTime);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
int zone;
|
||||
Number parameter = this.parameter;
|
||||
|
||||
if (leapZone == null) {
|
||||
return null;
|
||||
} else {
|
||||
zone = leapZone;
|
||||
}
|
||||
|
||||
if (operation == LutronOperation.QUERY) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL)) {
|
||||
return new LeapCommand(Request.getZoneStatus(zone));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported query action");
|
||||
return null;
|
||||
}
|
||||
} else if (operation == LutronOperation.EXECUTE) {
|
||||
if (targetType == TargetType.SWITCH) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL) && parameter != null) {
|
||||
return new LeapCommand(Request.goToLevel(zone, parameter.intValue()));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported switch action");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.DIMMER) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL) && parameter != null) {
|
||||
if (fadeTime == null && delayTime == null) {
|
||||
return new LeapCommand(Request.goToLevel(zone, parameter.intValue()));
|
||||
} else {
|
||||
LutronDuration fade = (fadeTime == null) ? new LutronDuration(0) : fadeTime;
|
||||
LutronDuration delay = (delayTime == null) ? new LutronDuration(0) : delayTime;
|
||||
return new LeapCommand(Request.goToDimmedLevel(zone, parameter.intValue(), fade.asLeapString(),
|
||||
delay.asLeapString()));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported dimmer action");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.FAN) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL)) {
|
||||
return new LeapCommand(Request.goToFanSpeed(zone, fanSpeed));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported fan action");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.SHADE) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL) && parameter != null) {
|
||||
return new LeapCommand(Request.goToLevel(zone, parameter.intValue()));
|
||||
} else if (action.equals(OutputCommand.ACTION_STARTRAISING)) {
|
||||
return new LeapCommand(Request.zoneCommand(zone, CommandType.RAISE));
|
||||
} else if (action.equals(OutputCommand.ACTION_STARTLOWERING)) {
|
||||
return new LeapCommand(Request.zoneCommand(zone, CommandType.LOWER));
|
||||
} else if (action.equals(OutputCommand.ACTION_STOP)) {
|
||||
return new LeapCommand(Request.zoneCommand(zone, CommandType.STOP));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported shade action");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported target type: {}", targetType);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported operation: {}", operation);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron SYSVAR command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SysvarCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_GETSETSYSVAR = 1;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Object parameter;
|
||||
|
||||
/**
|
||||
* SysvarCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param parameter
|
||||
*/
|
||||
public SysvarCommand(LutronOperation operation, Integer integrationId, Integer action, @Nullable Object parameter) {
|
||||
super(TargetType.SYSVAR, operation, LutronCommandType.SYSVAR, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null; // No equivalent LEAP command
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron TIMECLOCK command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TimeclockCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_CLOCKMODE = 1;
|
||||
public static final Integer ACTION_SUNRISE = 2;
|
||||
public static final Integer ACTION_SUNSET = 3;
|
||||
public static final Integer ACTION_EXECEVENT = 5;
|
||||
public static final Integer ACTION_SETEVENT = 6;
|
||||
public static final Integer EVENT_ENABLE = 1;
|
||||
public static final Integer EVENT_DISABLE = 2;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Object parameter;
|
||||
private final @Nullable Boolean enable;
|
||||
|
||||
/**
|
||||
* TimeclockCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param parameter
|
||||
* @param enable true = enable, false = disable
|
||||
*/
|
||||
public TimeclockCommand(LutronOperation operation, Integer integrationId, Integer action,
|
||||
@Nullable Object parameter, @Nullable Boolean enable) {
|
||||
super(TargetType.TIMECLOCK, operation, LutronCommandType.TIMECLOCK, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
if (enable != null) {
|
||||
builder.append(',');
|
||||
builder.append((enable) ? '1' : '2');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* Abstract base class for LEAP message body objects
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractMessageBody {
|
||||
|
||||
/**
|
||||
* Utility method to extract the int from a href String using the supplied Pattern.
|
||||
*
|
||||
* @return the int or 0 if unable to extract
|
||||
*/
|
||||
protected static int hrefNumber(Pattern pattern, @Nullable String href) {
|
||||
if (href == null) {
|
||||
return 0;
|
||||
}
|
||||
Matcher matcher = pattern.matcher(href);
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
return Integer.parseInt(matcher.group(1));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP CommandType enum
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum CommandType {
|
||||
@SerializedName("GoToDimmedLevel")
|
||||
GOTODIMMEDLEVEL("GoToDimmedLevel"),
|
||||
@SerializedName("GoToFanSpeed")
|
||||
GOTOFANSPEED("GoToFanSpeed"),
|
||||
@SerializedName("GoToLevel")
|
||||
GOTOLEVEL("GoToLevel"),
|
||||
@SerializedName("PressAndHold")
|
||||
PRESSANDHOLD("PressAndHold"),
|
||||
@SerializedName("PressAndRelease")
|
||||
PRESSANDRELEASE("PressAndRelease"),
|
||||
@SerializedName("Release")
|
||||
RELEASE("Release"),
|
||||
@SerializedName("ShadeLimitLower")
|
||||
SHADELIMITLOWER("ShadeLimitLower"),
|
||||
@SerializedName("ShadeLimitRaise")
|
||||
SHADELIMITRAISE("ShadeLimitRaise"),
|
||||
@SerializedName("Raise")
|
||||
RAISE("Raise"),
|
||||
@SerializedName("Lower")
|
||||
LOWER("Lower"),
|
||||
@SerializedName("Stop")
|
||||
STOP("Stop");
|
||||
|
||||
private final transient String string;
|
||||
|
||||
CommandType(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP CommuniqueType enum
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum CommuniqueType {
|
||||
// Requests
|
||||
@SerializedName("CreateRequest")
|
||||
CREATEREQUEST("CreateRequest"),
|
||||
@SerializedName("ReadRequest")
|
||||
READREQUEST("ReadRequest"),
|
||||
@SerializedName("UpdateRequest")
|
||||
UPDATEREQUEST("UpdateRequest"),
|
||||
@SerializedName("DeleteRequest")
|
||||
DELETEREQUEST("DeleteRequest"), // ?
|
||||
@SerializedName("SubscribeRequest")
|
||||
SUBSCRIBEREQUEST("SubscribeRequest"),
|
||||
@SerializedName("UnubscribeRequest")
|
||||
UNSUBSCRIBEREQUEST("UnubscribeRequest"),
|
||||
@SerializedName("Execute")
|
||||
EXECUTEREQUEST("Execute"),
|
||||
|
||||
// Responses
|
||||
@SerializedName("CreateResponse")
|
||||
CREATERESPONSE("CreateResponse"),
|
||||
@SerializedName("ReadResponse")
|
||||
READRESPONSE("ReadResponse"),
|
||||
@SerializedName("UpdateResponse")
|
||||
UPDATERESPONSE("UpdateResponse"),
|
||||
@SerializedName("DeleteResponse")
|
||||
DELETERESPONSE("DeleteResponse"), // ?
|
||||
@SerializedName("SubscribeResponse")
|
||||
SUBSCRIBERESPONSE("SubscribeResponse"),
|
||||
@SerializedName("UnsubscribeResponse")
|
||||
UNSUBSCRIBERESPONSE("UnsubscribeResponse"),
|
||||
@SerializedName("ExecuteResponse")
|
||||
EXECUTERESPONSE("ExecuteResponse"), // ?
|
||||
@SerializedName("ExceptionResponse")
|
||||
EXCEPTIONRESPONSE("ExceptionResponse");
|
||||
|
||||
private final transient String string;
|
||||
|
||||
CommuniqueType(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* LeapCommand represents a LEAP protocol command
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapCommand {
|
||||
private String command;
|
||||
|
||||
public LeapCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ExceptionDetail;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Header;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroupStatus;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Class responsible for parsing incoming LEAP messages
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapMessageParser {
|
||||
private final Logger logger = LoggerFactory.getLogger(LeapMessageParser.class);
|
||||
|
||||
private final Gson gson;
|
||||
private final LeapMessageParserCallbacks callback;
|
||||
|
||||
/**
|
||||
* LeapMessageParser Constructor
|
||||
*
|
||||
* @param callback Object implementing the LeapMessageParserCallbacks interface
|
||||
*/
|
||||
public LeapMessageParser(LeapMessageParserCallbacks callback) {
|
||||
gson = new GsonBuilder().create();
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and process a LEAP protocol message
|
||||
*
|
||||
* @param msg String containing the LEAP message
|
||||
*/
|
||||
public void handleMessage(String msg) {
|
||||
if (msg.trim().equals("")) {
|
||||
return; // Ignore empty lines
|
||||
}
|
||||
logger.trace("Received message: {}", msg);
|
||||
|
||||
try {
|
||||
JsonObject message = (JsonObject) new JsonParser().parse(msg);
|
||||
|
||||
if (!message.has("CommuniqueType")) {
|
||||
logger.debug("No CommuniqueType found in message: {}", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
String communiqueType = message.get("CommuniqueType").getAsString();
|
||||
// CommuniqueType type = CommuniqueType.valueOf(communiqueType);
|
||||
logger.debug("Received CommuniqueType: {}", communiqueType);
|
||||
callback.validMessageReceived(communiqueType);
|
||||
|
||||
switch (communiqueType) {
|
||||
case "CreateResponse":
|
||||
return;
|
||||
case "ReadResponse":
|
||||
handleReadResponseMessage(message);
|
||||
break;
|
||||
case "UpdateResponse":
|
||||
break;
|
||||
case "SubscribeResponse":
|
||||
// Subscribe responses can contain bodies with data
|
||||
handleReadResponseMessage(message);
|
||||
return;
|
||||
case "UnsubscribeResponse":
|
||||
return;
|
||||
case "ExceptionResponse":
|
||||
handleExceptionResponse(message);
|
||||
return;
|
||||
default:
|
||||
logger.debug("Unknown CommuniqueType received: {}", communiqueType);
|
||||
break;
|
||||
}
|
||||
} catch (JsonParseException e) {
|
||||
logger.debug("Error parsing message: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by handleMessage() to handle all LEAP ExceptionResponse messages.
|
||||
*
|
||||
* @param message LEAP message
|
||||
*/
|
||||
private void handleExceptionResponse(JsonObject message) {
|
||||
String detailMessage = "";
|
||||
|
||||
try {
|
||||
JsonObject header = message.get("Header").getAsJsonObject();
|
||||
Header headerObj = gson.fromJson(header, Header.class);
|
||||
|
||||
if (MessageBodyType.ExceptionDetail.toString().equalsIgnoreCase(headerObj.messageBodyType)
|
||||
&& message.has("Body")) {
|
||||
JsonObject body = message.get("Body").getAsJsonObject();
|
||||
ExceptionDetail exceptionDetail = gson.fromJson(body, ExceptionDetail.class);
|
||||
if (exceptionDetail != null) {
|
||||
detailMessage = exceptionDetail.message;
|
||||
}
|
||||
}
|
||||
logger.debug("Exception response received. Status: {} URL: {} Message: {}", headerObj.statusCode,
|
||||
headerObj.url, detailMessage);
|
||||
|
||||
} catch (JsonParseException | IllegalStateException e) {
|
||||
logger.debug("Exception response received. Error parsing exception message: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by handleMessage() to handle all LEAP ReadResponse and SubscribeResponse messages.
|
||||
*
|
||||
* @param message LEAP message
|
||||
*/
|
||||
private void handleReadResponseMessage(JsonObject message) {
|
||||
try {
|
||||
JsonObject header = message.get("Header").getAsJsonObject();
|
||||
Header headerObj = gson.fromJson(header, Header.class);
|
||||
|
||||
// if 204/NoContent response received for buttongroup request, create empty button map
|
||||
if (Request.BUTTON_GROUP_URL.equals(headerObj.url)
|
||||
&& Header.STATUS_NO_CONTENT.equalsIgnoreCase(headerObj.statusCode)) {
|
||||
callback.handleEmptyButtonGroupDefinition();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!header.has("MessageBodyType")) {
|
||||
logger.trace("No MessageBodyType in header");
|
||||
return;
|
||||
}
|
||||
String messageBodyType = header.get("MessageBodyType").getAsString();
|
||||
logger.trace("MessageBodyType: {}", messageBodyType);
|
||||
|
||||
if (!message.has("Body")) {
|
||||
logger.debug("No Body found in message");
|
||||
return;
|
||||
}
|
||||
JsonObject body = message.get("Body").getAsJsonObject();
|
||||
|
||||
switch (messageBodyType) {
|
||||
case "OnePingResponse":
|
||||
parseOnePingResponse(body);
|
||||
break;
|
||||
case "OneZoneStatus":
|
||||
parseOneZoneStatus(body);
|
||||
break;
|
||||
case "MultipleAreaDefinition":
|
||||
parseMultipleAreaDefinition(body);
|
||||
break;
|
||||
case "MultipleButtonGroupDefinition":
|
||||
parseMultipleButtonGroupDefinition(body);
|
||||
break;
|
||||
case "MultipleDeviceDefinition":
|
||||
parseMultipleDeviceDefinition(body);
|
||||
break;
|
||||
case "MultipleOccupancyGroupDefinition":
|
||||
parseMultipleOccupancyGroupDefinition(body);
|
||||
break;
|
||||
case "MultipleOccupancyGroupStatus":
|
||||
parseMultipleOccupancyGroupStatus(body);
|
||||
break;
|
||||
case "MultipleVirtualButtonDefinition":
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown MessageBodyType received: {}", messageBodyType);
|
||||
break;
|
||||
}
|
||||
} catch (JsonParseException | IllegalStateException e) {
|
||||
logger.debug("Error parsing message: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable <T extends AbstractMessageBody> T parseBodySingle(JsonObject messageBody, String memberName,
|
||||
Class<T> type) {
|
||||
try {
|
||||
if (messageBody.has(memberName)) {
|
||||
JsonObject jsonObject = messageBody.get(memberName).getAsJsonObject();
|
||||
T obj = gson.fromJson(jsonObject, type);
|
||||
return obj;
|
||||
} else {
|
||||
logger.debug("Member name {} not found in JSON message", memberName);
|
||||
return null;
|
||||
}
|
||||
} catch (IllegalStateException | JsonSyntaxException e) {
|
||||
logger.debug("Error parsing JSON message: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends AbstractMessageBody> List<T> parseBodyMultiple(JsonObject messageBody, String memberName,
|
||||
Class<T> type) {
|
||||
List<T> objList = new LinkedList<T>();
|
||||
try {
|
||||
if (messageBody.has(memberName)) {
|
||||
JsonArray jsonArray = messageBody.get(memberName).getAsJsonArray();
|
||||
|
||||
for (JsonElement element : jsonArray) {
|
||||
JsonObject jsonObject = element.getAsJsonObject();
|
||||
T obj = gson.fromJson(jsonObject, type);
|
||||
objList.add(obj);
|
||||
}
|
||||
return objList;
|
||||
} else {
|
||||
logger.debug("Member name {} not found in JSON message", memberName);
|
||||
return objList;
|
||||
}
|
||||
} catch (IllegalStateException | JsonSyntaxException e) {
|
||||
logger.debug("Error parsing JSON message: {}", e.getMessage());
|
||||
return objList;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseOnePingResponse(JsonObject messageBody) {
|
||||
logger.debug("Ping response received");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a OneZoneStatus message body. Calls handleZoneUpdate() to dispatch zone updates.
|
||||
*/
|
||||
private void parseOneZoneStatus(JsonObject messageBody) {
|
||||
ZoneStatus zoneStatus = parseBodySingle(messageBody, "ZoneStatus", ZoneStatus.class);
|
||||
if (zoneStatus != null) {
|
||||
callback.handleZoneUpdate(zoneStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleAreaDefinition message body.
|
||||
*/
|
||||
private void parseMultipleAreaDefinition(JsonObject messageBody) {
|
||||
logger.trace("Parsing area list");
|
||||
List<Area> areaList = parseBodyMultiple(messageBody, "Areas", Area.class);
|
||||
callback.handleMultipleAreaDefinition(areaList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleOccupancyGroupDefinition message body.
|
||||
*/
|
||||
private void parseMultipleOccupancyGroupDefinition(JsonObject messageBody) {
|
||||
logger.trace("Parsing occupancy group list");
|
||||
List<OccupancyGroup> oGroupList = parseBodyMultiple(messageBody, "OccupancyGroups", OccupancyGroup.class);
|
||||
callback.handleMultipleOccupancyGroupDefinition(oGroupList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleOccupancyGroupStatus message body and updates occupancy status.
|
||||
*/
|
||||
private void parseMultipleOccupancyGroupStatus(JsonObject messageBody) {
|
||||
logger.trace("Parsing occupancy group status list");
|
||||
List<OccupancyGroupStatus> statusList = parseBodyMultiple(messageBody, "OccupancyGroupStatuses",
|
||||
OccupancyGroupStatus.class);
|
||||
for (OccupancyGroupStatus status : statusList) {
|
||||
int groupNumber = status.getOccupancyGroup();
|
||||
if (groupNumber > 0) {
|
||||
logger.debug("OccupancyGroup: {} Status: {}", groupNumber, status.occupancyStatus);
|
||||
callback.handleGroupUpdate(groupNumber, status.occupancyStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleDeviceDefinition message body and loads the zoneToDevice and deviceToZone maps. Also passes the
|
||||
* device data on to the discovery service and calls setBridgeProperties() with the hub's device entry.
|
||||
*/
|
||||
private void parseMultipleDeviceDefinition(JsonObject messageBody) {
|
||||
List<Device> deviceList = parseBodyMultiple(messageBody, "Devices", Device.class);
|
||||
callback.handleMultipleDeviceDefintion(deviceList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a MultipleButtonGroupDefinition message body and load the results into deviceButtonMap.
|
||||
*/
|
||||
private void parseMultipleButtonGroupDefinition(JsonObject messageBody) {
|
||||
List<ButtonGroup> buttonGroupList = parseBodyMultiple(messageBody, "ButtonGroups", ButtonGroup.class);
|
||||
callback.handleMultipleButtonGroupDefinition(buttonGroupList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
|
||||
|
||||
/**
|
||||
* Interface defining callback routines used by LeapMessageParser
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface LeapMessageParserCallbacks {
|
||||
|
||||
public void validMessageReceived(String communiqueType);
|
||||
|
||||
public void handleEmptyButtonGroupDefinition();
|
||||
|
||||
public void handleZoneUpdate(ZoneStatus zoneStatus);
|
||||
|
||||
public void handleGroupUpdate(int groupNumber, String occupancyStatus);
|
||||
|
||||
public void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList);
|
||||
|
||||
public void handleMultipleDeviceDefintion(List<Device> deviceList);
|
||||
|
||||
public void handleMultipleAreaDefinition(List<Area> areaList);
|
||||
|
||||
public void handleMultipleOccupancyGroupDefinition(List<OccupancyGroup> oGroupList);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* LEAP MessageBodyType enum
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum MessageBodyType {
|
||||
ExceptionDetail("ExceptionDetail"),
|
||||
MultipleAffectedZoneDefinition("MultipleAffectedZoneDefinition"),
|
||||
MultipleAreaDefinition("MultipleAreaDefinition"),
|
||||
MultipleButtonDefinition("MultipleButtonDefinition"),
|
||||
MultipleButtonGroupDefinition("MultipleButtonGroupDefinition"),
|
||||
MultipleDeviceDefinition("MultipleDeviceDefinition"),
|
||||
MultipleDeviceStatus("MultipleDeviceStatus"),
|
||||
MultipleOccupancyGroupDefinition("MultipleOccupancyGroupDefinition"),
|
||||
MultipleOccupancyGroupStatus("MultipleOccupancyGroupStatus"),
|
||||
MultiplePresetAssignmentDefinition("MultiplePresetAssignmentDefinition"),
|
||||
MultipleProgrammingModelDefinition("MultipleProgrammingModelDefinition"),
|
||||
MultipleServerDefinition("MultipleServerDefinition"),
|
||||
MultipleServiceDefinition("MultipleServiceDefinition"),
|
||||
MultipleTimeclockDefinition("MultipleTimeclockDefinition"),
|
||||
MultipleVirtualButtonDefinition("MultipleVirtualButtonDefinition"),
|
||||
MultipleZoneDefinition("MultipleZoneDefinition"),
|
||||
MultipleZoneStatus("MultipleZoneStatus"),
|
||||
OneAffectedZoneDefinition("OneAffectedZoneDefinition"),
|
||||
OneAlexaDataSummaryDefinition("OneAlexaDataSummaryDefinition"),
|
||||
OneAreaDefinition("OneAreaDefinition"),
|
||||
OneAreaLoadSheddingDefinition("OneAreaLoadSheddingDefinition"),
|
||||
OneButtonDefinition("OneButtonDefinition"),
|
||||
OneButtonGroupDefinition("OneButtonGroupDefinition"),
|
||||
OneDeviceDefinition("OneDeviceDefinition"),
|
||||
OneDeviceRulesDefinition("OneDeviceRulesDefinition"),
|
||||
OneDeviceStatus("OneDeviceStatus"),
|
||||
OneGoogleHomeDataSummaryDefinition("OneGoogleHomeDataSummaryDefinition"),
|
||||
OneLinkNodeDefinition("OneLinkNodeDefinition"),
|
||||
OneLIPIdListDefinition("OneLIPIdListDefinition"),
|
||||
OneNetworkInterfaceDefinition("OneNetworkInterfaceDefinition"),
|
||||
OneOccupancyGroupDefinition("OneOccupancyGroupDefinition"),
|
||||
OnePairingListDefinition("OnePairingListDefinition"),
|
||||
OnePingResponse("OnePingResponse"),
|
||||
OnePresetAssignmentDefinition("OnePresetAssignmentDefinition"),
|
||||
OnePresetDefinition("OnePresetDefinition"),
|
||||
OneProgrammingModelDefinition("OneProgrammingModelDefinition"),
|
||||
OneProjectDefinition("OneProjectDefinition"),
|
||||
OneServerDefinition("OneServerDefinition"),
|
||||
OneServiceDefinition("OneServiceDefinition"),
|
||||
OneSystemDefinition("OneSystemDefinition"),
|
||||
OneTimeclockEventRulesDefinition("OneTimeclockEventRulesDefinition"),
|
||||
OneVirtualButtonDefinition("OneVirtualButtonDefinition"),
|
||||
OneZoneDefinition("OneZoneDefinition"),
|
||||
OneZoneStatus("OneZoneStatus");
|
||||
|
||||
private final transient String string;
|
||||
|
||||
MessageBodyType(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
|
||||
/**
|
||||
* Contains static methods for constructing LEAP messages
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Request {
|
||||
public static final String BUTTON_GROUP_URL = "/buttongroup";
|
||||
|
||||
public static String goToLevel(int zone, int value) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"}," + "\"Body\": {" + "\"Command\": {"
|
||||
+ "\"CommandType\": \"GoToLevel\"," + "\"Parameter\": [{\"Type\": \"Level\", \"Value\": %d}]}}}";
|
||||
return String.format(request, zone, value);
|
||||
}
|
||||
|
||||
/** fadeTime must be in the format hh:mm:ss **/
|
||||
public static String goToDimmedLevel(int zone, int value, String fadeTime) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"},\"Body\": {\"Command\": {"
|
||||
+ "\"CommandType\": \"GoToDimmedLevel\","
|
||||
+ "\"DimmedLevelParameters\": {\"Level\": %d, \"FadeTime\": \"%s\"}}}}";
|
||||
return String.format(request, zone, value, fadeTime);
|
||||
}
|
||||
|
||||
/** fadeTime and delayTime must be in the format hh:mm:ss **/
|
||||
public static String goToDimmedLevel(int zone, int value, String fadeTime, String delayTime) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"},\"Body\": {\"Command\": {"
|
||||
+ "\"CommandType\": \"GoToDimmedLevel\","
|
||||
+ "\"DimmedLevelParameters\": {\"Level\": %d, \"FadeTime\": \"%s\", \"DelayTime\": \"%s\"}}}}";
|
||||
return String.format(request, zone, value, fadeTime, delayTime);
|
||||
}
|
||||
|
||||
public static String goToFanSpeed(int zone, FanSpeedType fanSpeed) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"}," + "\"Body\": {"
|
||||
+ "\"Command\": {\"CommandType\": \"GoToFanSpeed\","
|
||||
+ "\"FanSpeedParameters\": {\"FanSpeed\": \"%s\"}}}}";
|
||||
return String.format(request, zone, fanSpeed.leapValue());
|
||||
}
|
||||
|
||||
public static String buttonCommand(int button, CommandType command) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/button/%d/commandprocessor\"},"
|
||||
+ "\"Body\": {\"Command\": {\"CommandType\": \"%s\"}}}";
|
||||
return String.format(request, button, command.toString());
|
||||
}
|
||||
|
||||
public static String virtualButtonCommand(int virtualbutton, CommandType command) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/virtualbutton/%d/commandprocessor\"},"
|
||||
+ "\"Body\": {\"Command\": {\"CommandType\": \"%s\"}}}";
|
||||
return String.format(request, virtualbutton, command.toString());
|
||||
}
|
||||
|
||||
public static String zoneCommand(int zone, CommandType commandType) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"}," + "\"Body\": {" + "\"Command\": {"
|
||||
+ "\"CommandType\": \"%s\"}}}";
|
||||
return String.format(request, zone, commandType.toString());
|
||||
}
|
||||
|
||||
public static String request(CommuniqueType cType, String url) {
|
||||
String request = "{\"CommuniqueType\": \"%s\",\"Header\": {\"Url\": \"%s\"}}";
|
||||
return String.format(request, cType.toString(), url);
|
||||
}
|
||||
|
||||
public static String ping() {
|
||||
return request(CommuniqueType.READREQUEST, "/server/1/status/ping");
|
||||
}
|
||||
|
||||
public static String getDevices() {
|
||||
return request(CommuniqueType.READREQUEST, "/device");
|
||||
}
|
||||
|
||||
public static String getVirtualButtons() {
|
||||
return request(CommuniqueType.READREQUEST, "/virtualbutton");
|
||||
}
|
||||
|
||||
public static String getButtonGroups() {
|
||||
return request(CommuniqueType.READREQUEST, BUTTON_GROUP_URL);
|
||||
}
|
||||
|
||||
public static String getAreas() {
|
||||
return request(CommuniqueType.READREQUEST, "/area");
|
||||
}
|
||||
|
||||
public static String getOccupancyGroups() {
|
||||
return request(CommuniqueType.READREQUEST, "/occupancygroup");
|
||||
}
|
||||
|
||||
public static String getZoneStatus(int zone) {
|
||||
return request(CommuniqueType.READREQUEST, String.format("/zone/%d/status", zone));
|
||||
}
|
||||
|
||||
public static String getOccupancyGroupStatus() {
|
||||
return request(CommuniqueType.READREQUEST, "/occupancygroup/status");
|
||||
}
|
||||
|
||||
public static String subscribeOccupancyGroupStatus() {
|
||||
return request(CommuniqueType.SUBSCRIBEREQUEST, "/occupancygroup/status");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP AffectedZone Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class AffectedZone {
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
@SerializedName("Zone")
|
||||
public Href zone = new Href();
|
||||
|
||||
public AffectedZone() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP Area object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Area extends AbstractMessageBody {
|
||||
public static final Pattern AREA_HREF_PATTERN = Pattern.compile("/area/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("Name")
|
||||
public String name;
|
||||
|
||||
@SerializedName("Parent")
|
||||
public Href parent;
|
||||
|
||||
// @SerializedName("Category")
|
||||
// public Category category;
|
||||
|
||||
@SerializedName("AssociatedDevices")
|
||||
public Href[] associatedDevices;
|
||||
|
||||
@SerializedName("AssociatedOccupancyGroups")
|
||||
public Href[] associatedOccupancyGroups;
|
||||
|
||||
@SerializedName("LoadShedding")
|
||||
public Href loadShedding;
|
||||
|
||||
@SerializedName("OccupancySettings")
|
||||
public Href occupancySettings;
|
||||
|
||||
@SerializedName("OccupancySensorSettings")
|
||||
public Href occupancySensorSettings;
|
||||
|
||||
@SerializedName("DaylightingGainSettings")
|
||||
public Href daylightingGainSettings;
|
||||
|
||||
public Area() {
|
||||
}
|
||||
|
||||
public int getArea() {
|
||||
return hrefNumber(AREA_HREF_PATTERN, href);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP ButtonGroup Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class ButtonGroup extends AbstractMessageBody {
|
||||
public static final Pattern BUTTONGROUP_HREF_PATTERN = Pattern.compile("/buttongroup/([0-9]+)");
|
||||
public static final Pattern BUTTON_HREF_PATTERN = Pattern.compile("/button/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
@SerializedName("Parent") // device href
|
||||
public Href parent = new Href();
|
||||
@SerializedName("Buttons")
|
||||
public Href[] buttons;
|
||||
@SerializedName("AffectedZones")
|
||||
public AffectedZone[] affectedZones;
|
||||
@SerializedName("SortOrder")
|
||||
public Integer sortOrder;
|
||||
@SerializedName("StopIfMoving")
|
||||
public String stopIfMoving; // Enabled or Disabled
|
||||
@SerializedName("ProgrammingType")
|
||||
public String programmingType; // Column
|
||||
|
||||
public ButtonGroup() {
|
||||
}
|
||||
|
||||
public int getButtonGroup() {
|
||||
return hrefNumber(BUTTONGROUP_HREF_PATTERN, href);
|
||||
}
|
||||
|
||||
public int getParentDevice() {
|
||||
if (parent != null && parent.href != null) {
|
||||
return hrefNumber(Device.DEVICE_HREF_PATTERN, parent.href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Integer> getButtonList() {
|
||||
LinkedList<Integer> buttonNumList = new LinkedList<>();
|
||||
for (Href button : buttons) {
|
||||
int buttonNum = hrefNumber(BUTTON_HREF_PATTERN, button.href);
|
||||
buttonNumList.add(buttonNum);
|
||||
}
|
||||
return buttonNumList;
|
||||
}
|
||||
}
|
||||
@@ -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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP Device Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Device extends AbstractMessageBody {
|
||||
public static final Pattern DEVICE_HREF_PATTERN = Pattern.compile("/device/([0-9]+)");
|
||||
private static final Pattern ZONE_HREF_PATTERN = Pattern.compile("/zone/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("Name")
|
||||
public String name;
|
||||
|
||||
@SerializedName("FullyQualifiedName")
|
||||
public String[] fullyQualifiedName;
|
||||
|
||||
@SerializedName("Parent")
|
||||
public Href parent = new Href();
|
||||
|
||||
@SerializedName("SerialNumber")
|
||||
public String serialNumber;
|
||||
|
||||
@SerializedName("ModelNumber")
|
||||
public String modelNumber;
|
||||
|
||||
@SerializedName("DeviceType")
|
||||
public String deviceType;
|
||||
|
||||
@SerializedName("LocalZones")
|
||||
public Href[] localZones;
|
||||
|
||||
@SerializedName("AssociatedArea")
|
||||
public Href associatedArea = new Href();
|
||||
|
||||
@SerializedName("OccupancySensors")
|
||||
public Href[] occupancySensors;
|
||||
|
||||
@SerializedName("LinkNodes")
|
||||
public Href[] linkNodes;
|
||||
|
||||
@SerializedName("DeviceRules")
|
||||
public Href[] deviceRules;
|
||||
|
||||
@SerializedName("RepeaterProperties")
|
||||
public RepeaterProperties repeaterProperties;
|
||||
|
||||
@SerializedName("FirmwareImage")
|
||||
public FirmwareImage firmwareImage;
|
||||
|
||||
public class FirmwareImage {
|
||||
@SerializedName("Firmware")
|
||||
public Firmware firmware;
|
||||
@SerializedName("Installed")
|
||||
public Installed installed;
|
||||
}
|
||||
|
||||
public class Firmware {
|
||||
@SerializedName("DisplayName")
|
||||
public String displayName;
|
||||
}
|
||||
|
||||
public class Installed {
|
||||
@SerializedName("Year")
|
||||
public int year;
|
||||
@SerializedName("Month")
|
||||
public int month;
|
||||
@SerializedName("Day")
|
||||
public int day;
|
||||
@SerializedName("Hour")
|
||||
public int hour;
|
||||
@SerializedName("Minute")
|
||||
public int minute;
|
||||
@SerializedName("Second")
|
||||
public int second;
|
||||
@SerializedName("Utc")
|
||||
public String utc;
|
||||
}
|
||||
|
||||
public class RepeaterProperties {
|
||||
@SerializedName("IsRepeater")
|
||||
public boolean isRepeater;
|
||||
}
|
||||
|
||||
public Device() {
|
||||
}
|
||||
|
||||
public int getDevice() {
|
||||
return hrefNumber(DEVICE_HREF_PATTERN, href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the zone number of the first zone listed in LocalZones.
|
||||
* Currently devices should only have one zone listed.
|
||||
*/
|
||||
public int getZone() {
|
||||
if (localZones != null && localZones.length > 0) {
|
||||
return hrefNumber(ZONE_HREF_PATTERN, localZones[0].href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public String getFullyQualifiedName() {
|
||||
if (fullyQualifiedName != null && fullyQualifiedName.length > 0) {
|
||||
return String.join(" ", fullyQualifiedName);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP ExceptionDetail object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class ExceptionDetail extends AbstractMessageBody {
|
||||
@SerializedName("Message")
|
||||
public String message = "";
|
||||
|
||||
public ExceptionDetail() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP message header
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Header {
|
||||
public static final String STATUS_NO_CONTENT = "204 NoContent";
|
||||
public static final String STATUS_OK = "200 OK";
|
||||
|
||||
@SerializedName("MessageBodyType")
|
||||
public String messageBodyType;
|
||||
@SerializedName("StatusCode")
|
||||
public String statusCode;
|
||||
@SerializedName("Url")
|
||||
public String url;
|
||||
|
||||
public Header() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Href {
|
||||
@SerializedName("href")
|
||||
public String href = "";
|
||||
|
||||
public Href() {
|
||||
}
|
||||
|
||||
public Href(String href) {
|
||||
this.href = href;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP OccupancyGroup object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class OccupancyGroup extends AbstractMessageBody {
|
||||
public static final Pattern OGROUP_HREF_PATTERN = Pattern.compile("/occupancygroup/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("AssociatedSensors")
|
||||
public OccupancySensor[] associatedSensors;
|
||||
|
||||
@SerializedName("AssociatedAreas")
|
||||
public AreaHref[] associatedAreas;
|
||||
|
||||
@SerializedName("ProgrammingType")
|
||||
public String programmingType;
|
||||
|
||||
@SerializedName("ProgrammingModel")
|
||||
public Href programmingModel;
|
||||
|
||||
public class AreaHref {
|
||||
@SerializedName("Area")
|
||||
public Href area;
|
||||
|
||||
public int getAreaNumber() {
|
||||
return hrefNumber(Area.AREA_HREF_PATTERN, area.href);
|
||||
}
|
||||
}
|
||||
|
||||
public OccupancyGroup() {
|
||||
}
|
||||
|
||||
public int getOccupancyGroup() {
|
||||
return hrefNumber(OGROUP_HREF_PATTERN, href);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP OccupancyGroupStatus object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class OccupancyGroupStatus extends AbstractMessageBody {
|
||||
public static final Pattern OGROUP_HREF_PATTERN = Pattern.compile("/occupancygroup/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("OccupancyGroup")
|
||||
public Href occupancyGroup;
|
||||
|
||||
@SerializedName("OccupancyStatus")
|
||||
public String occupancyStatus; // Occupied, Unoccupied, or Unknown
|
||||
|
||||
public OccupancyGroupStatus() {
|
||||
}
|
||||
|
||||
public int getOccupancyGroup() {
|
||||
if (occupancyGroup != null && occupancyGroup.href != null) {
|
||||
return hrefNumber(OGROUP_HREF_PATTERN, occupancyGroup.href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class OccupancySensor {
|
||||
@SerializedName("OccupancySensor")
|
||||
public Href occupancySensor;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP ZoneStatus Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class ZoneStatus extends AbstractMessageBody {
|
||||
public static final Pattern ZONE_HREF_PATTERN = Pattern.compile("/zone/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href = "";
|
||||
@SerializedName("Level")
|
||||
public int level; // 0-100
|
||||
@SerializedName("SwitchedLevel")
|
||||
public String switchedLevel = ""; // "On" or "Off"
|
||||
@SerializedName("FanSpeed")
|
||||
public FanSpeedType fanSpeed;
|
||||
@SerializedName("Zone")
|
||||
public Href zone = new Href();;
|
||||
@SerializedName("StatusAccuracy")
|
||||
public String statusAccuracy = ""; // "Good" or ??
|
||||
|
||||
public ZoneStatus() {
|
||||
}
|
||||
|
||||
public int getZone() {
|
||||
if (zone != null) {
|
||||
return hrefNumber(ZONE_HREF_PATTERN, zone.href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean statusAccuracyGood() {
|
||||
return "Good".equals(statusAccuracy);
|
||||
}
|
||||
|
||||
public boolean switchedLevelOn() {
|
||||
return "On".equals(switchedLevel);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.protocol;
|
||||
package org.openhab.binding.lutron.internal.protocol.lip;
|
||||
|
||||
/**
|
||||
* Type of command in the Lutron integration protocol.
|
||||
@@ -10,17 +10,18 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.protocol;
|
||||
package org.openhab.binding.lutron.internal.protocol.lip;
|
||||
|
||||
/**
|
||||
* Requested operation of a command to the Lutron integration protocol.
|
||||
* Requested operation of a command in the Lutron integration protocol.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*
|
||||
*/
|
||||
public enum LutronOperation {
|
||||
EXECUTE("#"),
|
||||
QUERY("?");
|
||||
QUERY("?"),
|
||||
RESPONSE("~");
|
||||
|
||||
private final String operationChar;
|
||||
|
||||
@@ -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.lutron.internal.protocol.lip;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Target device type enum. Used to annotate LutronCommand objects so the LEAP bridge can translate them.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum TargetType {
|
||||
BLIND,
|
||||
BRIDGE,
|
||||
CCO,
|
||||
DIMMER,
|
||||
FAN,
|
||||
GREENMODE,
|
||||
GROUP,
|
||||
KEYPAD,
|
||||
SHADE,
|
||||
SWITCH,
|
||||
SYSVAR,
|
||||
TIMECLOCK,
|
||||
VIRTUALKEYPAD;
|
||||
}
|
||||
@@ -74,6 +74,8 @@ public class DbXmlInfoReader {
|
||||
xstream.alias("Area", Area.class);
|
||||
xstream.aliasField("Name", Area.class, "name");
|
||||
xstream.useAttributeFor(Area.class, "name");
|
||||
xstream.aliasField("IntegrationID", Area.class, "integrationId");
|
||||
xstream.useAttributeFor(Area.class, "integrationId");
|
||||
xstream.aliasField("DeviceGroups", Area.class, "deviceNodes");
|
||||
xstream.aliasField("Outputs", Area.class, "outputs");
|
||||
xstream.aliasField("Areas", Area.class, "areas");
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<bridge-type id="ipbridge">
|
||||
<label>Lutron IP Access Point</label>
|
||||
<description>Ethernet access point to Lutron lighting control system</description>
|
||||
<description>A Lutron controller using Lutron Integration Protocol (LIP) over TCP/IP</description>
|
||||
<properties>
|
||||
<property name="vendor">Lutron</property>
|
||||
</properties>
|
||||
@@ -57,9 +57,79 @@
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<bridge-type id="leapbridge">
|
||||
<label>Lutron LEAP Access Point</label>
|
||||
<description>A Lutron controller using LEAP protocol over TCP/IP</description>
|
||||
|
||||
<channels>
|
||||
<channel id="command" typeId="command-type"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Lutron</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>IP or Host Name</label>
|
||||
<description>The IP or host name of the Lutron integration access point</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer">
|
||||
<label>TCP Port</label>
|
||||
<description>The bridge TCP port</description>
|
||||
<default>8081</default>
|
||||
</parameter>
|
||||
<parameter name="keystore" type="text" required="true">
|
||||
<label>Keystore File</label>
|
||||
<description>Java keystore containing caseta key and certs</description>
|
||||
</parameter>
|
||||
<parameter name="keystorePassword" type="text">
|
||||
<context>password</context>
|
||||
<label>Keystore Password</label>
|
||||
<description>Password for the keystore file</description>
|
||||
</parameter>
|
||||
<parameter name="certValidate" type="boolean" required="false">
|
||||
<label>Validate SSL Certificates</label>
|
||||
<description>Validate server SSL certificate</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="reconnect" type="integer" min="1" max="60" unit="min">
|
||||
<label>Reconnect Interval</label>
|
||||
<description>The period in minutes that the handler will wait between connection attempts</description>
|
||||
<unitLabel>minutes</unitLabel>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="heartbeat" type="integer" min="1" max="60" unit="min">
|
||||
<label>Keepalive Heartbeat Interval</label>
|
||||
<description>The period in minutes between connection heartbeat checks</description>
|
||||
<unitLabel>minutes</unitLabel>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="delay" type="integer" min="0" max="250" unit="ms">
|
||||
<label>Send Delay</label>
|
||||
<description>The delay in milliseconds between sending integration commands (for throttling)</description>
|
||||
<unitLabel>ms</unitLabel>
|
||||
<default>0</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<channel-type id="command-type" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>LEAP Command</label>
|
||||
<description>LEAP command to send (for debugging)</description>
|
||||
</channel-type>
|
||||
|
||||
<thing-type id="dimmer">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Maestro Dimmer</label>
|
||||
@@ -102,9 +172,34 @@
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="fan">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Fan Speed Controller</label>
|
||||
<description>Controls ceiling fans</description>
|
||||
|
||||
<channels>
|
||||
<channel id="fanspeed" typeId="fanControl"/>
|
||||
<channel id="fanlevel" typeId="lightDimmer"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>integrationId</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="integrationId" type="integer" required="true">
|
||||
<label>Integration ID</label>
|
||||
<description>Address of fan controller in the Lutron system</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shade">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Sivoia QS Shade</label>
|
||||
@@ -160,6 +255,7 @@
|
||||
<thing-type id="switch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Maestro Switch</label>
|
||||
@@ -307,6 +403,29 @@
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="ogroup">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Occupancy Group</label>
|
||||
<description>Shows state of occupancy sensor group</description>
|
||||
|
||||
<channels>
|
||||
<channel id="groupstate" typeId="groupState"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>integrationId</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="integrationId" type="integer" required="true">
|
||||
<label>Integration ID</label>
|
||||
<description>Occupancy group number</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="keypad">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
@@ -484,6 +603,7 @@
|
||||
<thing-type id="pico">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Pico Keypad</label>
|
||||
@@ -601,6 +721,7 @@
|
||||
<thing-type id="virtualkeypad">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Virtual Keypad</label>
|
||||
@@ -617,8 +738,8 @@
|
||||
<label>Virtual Keypad Type</label>
|
||||
<description>System type for virtual keypad</description>
|
||||
<options>
|
||||
<option value="Caseta">Caseta Scene Buttons</option>
|
||||
<option value="Other">HomeWorks/RA 2/Other</option>
|
||||
<option value="Caseta">Caseta/RA2 Select Scene Buttons</option>
|
||||
<option value="Other">HomeWorks/RadioRA 2/Other</option>
|
||||
</options>
|
||||
<default>Other</default>
|
||||
</parameter>
|
||||
@@ -1172,6 +1293,33 @@
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fanControl">
|
||||
<item-type>String</item-type>
|
||||
<label>Fan Speed</label>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="OFF">Off</option>
|
||||
<option value="LOW">Low</option>
|
||||
<option value="MEDIUM">Medium</option>
|
||||
<option value="MEDIUMHIGH">MediumHigh</option>
|
||||
<option value="HIGH">High</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="groupState">
|
||||
<item-type>String</item-type>
|
||||
<label>Occupancy State</label>
|
||||
<category>Motion</category>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="OCCUPIED">Occupied</option>
|
||||
<option value="UNOCCUPIED">Unoccupied</option>
|
||||
<option value="UNKNOWN">Unknown</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="button">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Keypad Button</label>
|
||||
|
||||
Reference in New Issue
Block a user