added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.lutron-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-lutron" description="Lutron Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.lutron/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -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.action;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.DimmerHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronDuration;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DimmerActions} defines thing actions for DimmerHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "lutron")
|
||||
@NonNullByDefault
|
||||
public class DimmerActions implements ThingActions, IDimmerActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(DimmerActions.class);
|
||||
|
||||
private @Nullable DimmerHandler handler;
|
||||
|
||||
public DimmerActions() {
|
||||
logger.trace("Lutron Dimmer actions service created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof DimmerHandler) {
|
||||
this.handler = (DimmerHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setLevel dimmer thing action
|
||||
*/
|
||||
@Override
|
||||
@RuleAction(label = "setLevel", description = "Send set level command with fade and delay times")
|
||||
public void setLevel(
|
||||
@ActionInput(name = "level", label = "Dimmer Level", description = "New dimmer level (0-100)") @Nullable Double level,
|
||||
@ActionInput(name = "fadeTime", label = "Fade Time", description = "Time to fade to new level (seconds)") @Nullable Double fadeTime,
|
||||
@ActionInput(name = "delayTime", label = "Delay Time", description = "Delay before starting fade (seconds)") @Nullable Double delayTime) {
|
||||
DimmerHandler dimmerHandler = handler;
|
||||
if (dimmerHandler == null) {
|
||||
logger.debug("Handler not set for Dimmer thing actions.");
|
||||
return;
|
||||
}
|
||||
if (level == null) {
|
||||
logger.debug("Ignoring setLevel command due to null level value.");
|
||||
return;
|
||||
}
|
||||
if (fadeTime == null) {
|
||||
logger.debug("Ignoring setLevel command due to null value for fadeTime.");
|
||||
return;
|
||||
}
|
||||
if (delayTime == null) {
|
||||
logger.debug("Ignoring setLevel command due to null value for delayTime.");
|
||||
return;
|
||||
}
|
||||
|
||||
Double lightLevel = level;
|
||||
if (lightLevel > 100.0) {
|
||||
lightLevel = 100.0;
|
||||
} else if (lightLevel < 0.0) {
|
||||
lightLevel = 0.0;
|
||||
}
|
||||
try {
|
||||
dimmerHandler.setLightLevel(new BigDecimal(lightLevel).setScale(2, BigDecimal.ROUND_HALF_UP),
|
||||
new LutronDuration(fadeTime), new LutronDuration(delayTime));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("Ignoring setLevel command due to illegal argument exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static setLevel method for Rules DSL backward compatibility
|
||||
*/
|
||||
public static void setLevel(@Nullable ThingActions actions, @Nullable Double level, @Nullable Double fadeTime,
|
||||
@Nullable Double delayTime) {
|
||||
invokeMethodOf(actions).setLevel(level, fadeTime, delayTime); // Replace when core issue #1536 is fixed
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only necessary to work around a bug in openhab-core (issue #1536). It should be removed once that is
|
||||
* resolved.
|
||||
*/
|
||||
private static IDimmerActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(DimmerActions.class.getName())) {
|
||||
if (actions instanceof IDimmerActions) {
|
||||
return (IDimmerActions) actions;
|
||||
} else {
|
||||
return (IDimmerActions) Proxy.newProxyInstance(IDimmerActions.class.getClassLoader(),
|
||||
new Class[] { IDimmerActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of DimmerActions");
|
||||
}
|
||||
}
|
||||
@@ -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.action;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IDimmerActions} interface defines the interface for all thing actions supported by the dimmer thing.
|
||||
* This is only necessary to work around a bug in openhab-core (issue #1536). It should be removed once that is
|
||||
* resolved.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IDimmerActions {
|
||||
|
||||
public void setLevel(@Nullable Double level, @Nullable Double fadeTime, @Nullable Double delayTime);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
|
||||
/**
|
||||
* The {@link KeypadComponent} interface is used to access enums describing the possible components
|
||||
* in a given keypad model.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface KeypadComponent {
|
||||
|
||||
public int id();
|
||||
|
||||
public String channel();
|
||||
|
||||
public String description();
|
||||
|
||||
public ComponentType type();
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link LutronBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LutronBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "lutron";
|
||||
|
||||
// Bridge Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_IPBRIDGE = new ThingTypeUID(BINDING_ID, "ipbridge");
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
|
||||
public static final ThingTypeUID THING_TYPE_SHADE = new ThingTypeUID(BINDING_ID, "shade");
|
||||
public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch");
|
||||
public static final ThingTypeUID THING_TYPE_OCCUPANCYSENSOR = new ThingTypeUID(BINDING_ID, "occupancysensor");
|
||||
public static final ThingTypeUID THING_TYPE_KEYPAD = new ThingTypeUID(BINDING_ID, "keypad");
|
||||
public static final ThingTypeUID THING_TYPE_TTKEYPAD = new ThingTypeUID(BINDING_ID, "ttkeypad");
|
||||
public static final ThingTypeUID THING_TYPE_INTLKEYPAD = new ThingTypeUID(BINDING_ID, "intlkeypad");
|
||||
public static final ThingTypeUID THING_TYPE_PICO = new ThingTypeUID(BINDING_ID, "pico");
|
||||
public static final ThingTypeUID THING_TYPE_VIRTUALKEYPAD = new ThingTypeUID(BINDING_ID, "virtualkeypad");
|
||||
public static final ThingTypeUID THING_TYPE_VCRX = new ThingTypeUID(BINDING_ID, "vcrx");
|
||||
public static final ThingTypeUID THING_TYPE_CCO = new ThingTypeUID(BINDING_ID, "cco");
|
||||
public static final ThingTypeUID THING_TYPE_CCO_PULSED = new ThingTypeUID(BINDING_ID, "ccopulsed");
|
||||
public static final ThingTypeUID THING_TYPE_CCO_MAINTAINED = new ThingTypeUID(BINDING_ID, "ccomaintained");
|
||||
public static final ThingTypeUID THING_TYPE_TIMECLOCK = new ThingTypeUID(BINDING_ID, "timeclock");
|
||||
public static final ThingTypeUID THING_TYPE_GREENMODE = new ThingTypeUID(BINDING_ID, "greenmode");
|
||||
public static final ThingTypeUID THING_TYPE_QSIO = new ThingTypeUID(BINDING_ID, "qsio");
|
||||
public static final ThingTypeUID THING_TYPE_GRAFIKEYEKEYPAD = new ThingTypeUID(BINDING_ID, "grafikeyekeypad");
|
||||
public static final ThingTypeUID THING_TYPE_BLIND = new ThingTypeUID(BINDING_ID, "blind");
|
||||
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");
|
||||
|
||||
// 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_CLOCKMODE = "clockmode";
|
||||
public static final String CHANNEL_SUNRISE = "sunrise";
|
||||
public static final String CHANNEL_SUNSET = "sunset";
|
||||
public static final String CHANNEL_EXECEVENT = "execevent";
|
||||
public static final String CHANNEL_ENABLEEVENT = "enableevent";
|
||||
public static final String CHANNEL_DISABLEEVENT = "disableevent";
|
||||
public static final String CHANNEL_STEP = "step";
|
||||
public static final String CHANNEL_BLINDLIFTLEVEL = "blindliftlevel";
|
||||
public static final String CHANNEL_BLINDTILTLEVEL = "blindtiltlevel";
|
||||
public static final String CHANNEL_VARSTATE = "varstate";
|
||||
|
||||
// Bridge config properties (used by discovery service)
|
||||
public static final String HOST = "ipAddress";
|
||||
public static final String USER = "user";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String SERIAL_NUMBER = "serialNumber";
|
||||
public static final String DISCOVERY_FILE = "discoveryFile";
|
||||
|
||||
public static final String PROPERTY_PRODFAM = "productFamily";
|
||||
public static final String PROPERTY_PRODTYP = "productType";
|
||||
|
||||
// Thing config properties
|
||||
public static final String INTEGRATION_ID = "integrationId";
|
||||
|
||||
// CCO config properties
|
||||
public static final String CCO_TYPE = "outputType";
|
||||
public static final String CCO_TYPE_PULSED = "Pulsed";
|
||||
public static final String CCO_TYPE_MAINTAINED = "Maintained";
|
||||
public static final String DEFAULT_PULSE = "pulseLength";
|
||||
|
||||
// GreenMode config properties
|
||||
public static final String POLL_INTERVAL = "pollInterval";
|
||||
|
||||
// Blind types
|
||||
public static final String BLIND_TYPE_PARAMETER = "type";
|
||||
public static final String BLIND_TYPE_SHEER = "Sheer";
|
||||
public static final String BLIND_TYPE_VENETIAN = "Venetian";
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.lutron.internal.discovery.LutronDeviceDiscoveryService;
|
||||
import org.openhab.binding.lutron.internal.grxprg.GrafikEyeHandler;
|
||||
import org.openhab.binding.lutron.internal.grxprg.PrgBridgeHandler;
|
||||
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.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.MaintainedCcoHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.OccupancySensorHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.PalladiomKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.PicoKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.PulsedCcoHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.QSIOHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.ShadeHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.SwitchHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.SysvarHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.TabletopKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.TimeclockHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.VcrxHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.VirtualKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.WciHandler;
|
||||
import org.openhab.binding.lutron.internal.hw.HwConstants;
|
||||
import org.openhab.binding.lutron.internal.hw.HwDimmerHandler;
|
||||
import org.openhab.binding.lutron.internal.hw.HwSerialBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.radiora.RadioRAConstants;
|
||||
import org.openhab.binding.lutron.internal.radiora.handler.PhantomButtonHandler;
|
||||
import org.openhab.binding.lutron.internal.radiora.handler.RS232Handler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LutronHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added bridge discovery service registration/removal
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.lutron")
|
||||
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()));
|
||||
|
||||
// Used by the HwDiscoveryService
|
||||
public static final Set<ThingTypeUID> HW_DISCOVERABLE_DEVICE_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Collections.singleton(HwConstants.THING_TYPE_HWDIMMER));
|
||||
|
||||
// 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()));
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronHandlerFactory.class);
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public LutronHandlerFactory(final @Reference SerialPortManager serialPortManager,
|
||||
@Reference HttpClientFactory httpClientFactory) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)
|
||||
|| DISCOVERABLE_DEVICE_TYPES_UIDS.contains(thingTypeUID)
|
||||
|| HW_DISCOVERABLE_DEVICE_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegMap = new HashMap<>();
|
||||
// Marked as Nullable only to fix incorrect redundant null check complaints after adding null annotations
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_IPBRIDGE)) {
|
||||
IPBridgeHandler bridgeHandler = new IPBridgeHandler((Bridge) thing);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (thingTypeUID.equals(THING_TYPE_DIMMER)) {
|
||||
return new DimmerHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SHADE)) {
|
||||
return new ShadeHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SWITCH)) {
|
||||
return new SwitchHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_CCO)) {
|
||||
return new CcoHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_CCO_PULSED)) {
|
||||
return new PulsedCcoHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_CCO_MAINTAINED)) {
|
||||
return new MaintainedCcoHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_OCCUPANCYSENSOR)) {
|
||||
return new OccupancySensorHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_KEYPAD)) {
|
||||
return new KeypadHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_TTKEYPAD)) {
|
||||
return new TabletopKeypadHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_INTLKEYPAD)) {
|
||||
return new IntlKeypadHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_PICO)) {
|
||||
return new PicoKeypadHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_GRAFIKEYEKEYPAD)) {
|
||||
return new GrafikEyeKeypadHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_PALLADIOMKEYPAD)) {
|
||||
return new PalladiomKeypadHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_VIRTUALKEYPAD)) {
|
||||
return new VirtualKeypadHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_VCRX)) {
|
||||
return new VcrxHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_WCI)) {
|
||||
return new WciHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_TIMECLOCK)) {
|
||||
return new TimeclockHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_GREENMODE)) {
|
||||
return new GreenModeHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_QSIO)) {
|
||||
return new QSIOHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_BLIND)) {
|
||||
return new BlindHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SYSVAR)) {
|
||||
return new SysvarHandler(thing);
|
||||
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_PRGBRIDGE)) {
|
||||
return new PrgBridgeHandler((Bridge) thing);
|
||||
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_GRAFIKEYE)) {
|
||||
return new GrafikEyeHandler(thing);
|
||||
} else if (thingTypeUID.equals(RadioRAConstants.THING_TYPE_RS232)) {
|
||||
return new RS232Handler((Bridge) thing, serialPortManager);
|
||||
} else if (thingTypeUID.equals(RadioRAConstants.THING_TYPE_DIMMER)) {
|
||||
return new org.openhab.binding.lutron.internal.radiora.handler.DimmerHandler(thing);
|
||||
} else if (thingTypeUID.equals(RadioRAConstants.THING_TYPE_SWITCH)) {
|
||||
return new org.openhab.binding.lutron.internal.radiora.handler.SwitchHandler(thing);
|
||||
} else if (thingTypeUID.equals(RadioRAConstants.THING_TYPE_PHANTOM)) {
|
||||
return new PhantomButtonHandler(thing);
|
||||
} else if (thingTypeUID.equals(HwConstants.THING_TYPE_HWSERIALBRIDGE)) {
|
||||
return new HwSerialBridgeHandler((Bridge) thing, serialPortManager);
|
||||
} else if (thingTypeUID.equals(HwConstants.THING_TYPE_HWDIMMER)) {
|
||||
return new HwDimmerHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof IPBridgeHandler) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegMap.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
logger.debug("Unregistering discovery service.");
|
||||
serviceReg.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a discovery service for an IP bridge handler.
|
||||
*
|
||||
* @param bridgeHandler bridge handler for which to register the discovery service
|
||||
*/
|
||||
private synchronized void registerDiscoveryService(IPBridgeHandler bridgeHandler) {
|
||||
logger.debug("Registering discovery service.");
|
||||
LutronDeviceDiscoveryService discoveryService = new LutronDeviceDiscoveryService(bridgeHandler, httpClient);
|
||||
bridgeHandler.setDiscoveryService(discoveryService);
|
||||
discoveryServiceRegMap.put(bridgeHandler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.BlindHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BlindConfig {
|
||||
public int integrationId = 0;
|
||||
public @Nullable String type;
|
||||
}
|
||||
@@ -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.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Configuration settings for a {@link org.openhab.binding.lutron.internal.handler.DimmerHandler}.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DimmerConfig {
|
||||
private static final int DEFAULT_FADE = 1;
|
||||
private static final int DEFAULT_ONLEVEL = 100;
|
||||
private static final boolean DEFAULT_ONTOLAST = false;
|
||||
|
||||
public int integrationId;
|
||||
public BigDecimal fadeInTime = new BigDecimal(DEFAULT_FADE);
|
||||
public BigDecimal fadeOutTime = new BigDecimal(DEFAULT_FADE);
|
||||
public BigDecimal onLevel = new BigDecimal(DEFAULT_ONLEVEL);
|
||||
public Boolean onToLast = new Boolean(DEFAULT_ONTOLAST);
|
||||
}
|
||||
@@ -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.config;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* Configuration settings for an {@link org.openhab.binding.lutron.internal.handler.IPBridgeHandler}.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added reconnect, heartbeat and discoveryFile parameters
|
||||
*/
|
||||
public class IPBridgeConfig {
|
||||
public String ipAddress;
|
||||
public String user;
|
||||
public String password;
|
||||
public String discoveryFile;
|
||||
public int reconnect;
|
||||
public int heartbeat;
|
||||
public int delay = 0;
|
||||
|
||||
public boolean sameConnectionParameters(IPBridgeConfig config) {
|
||||
return StringUtils.equals(ipAddress, config.ipAddress) && StringUtils.equals(user, config.user)
|
||||
&& StringUtils.equals(password, config.password) && (reconnect == config.reconnect)
|
||||
&& (heartbeat == config.heartbeat) && (delay == config.delay);
|
||||
}
|
||||
}
|
||||
@@ -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.SysvarHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SysvarConfig {
|
||||
public int integrationId;
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/**
|
||||
* 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.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.util.InputStreamResponseListener;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.lutron.internal.LutronHandlerFactory;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.Area;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.Component;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.Device;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.DeviceGroup;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.DeviceNode;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.DeviceType;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.GreenMode;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.Output;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.OutputType;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.Project;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.Timeclock;
|
||||
import org.openhab.binding.lutron.internal.handler.IPBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfig;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigGrafikEye;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigIntlSeetouch;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigPalladiom;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigPico;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigSeetouch;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigTabletopSeetouch;
|
||||
import org.openhab.binding.lutron.internal.xml.DbXmlInfoReader;
|
||||
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.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LutronDeviceDiscoveryService} finds all devices paired with Lutron bridges by retrieving the
|
||||
* configuration XML from them via HTTP.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added support for more output devices and keypads, VCRX, repeater virtual buttons,
|
||||
* Timeclock, and Green Mode. Added option to read XML from file. Switched to jetty HTTP client for better
|
||||
* exception handling. Added keypad model discovery.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private static final int DECLARATION_MAX_LEN = 80;
|
||||
private static final long HTTP_REQUEST_TIMEOUT = 60; // seconds
|
||||
private static final int DISCOVERY_SERVICE_TIMEOUT = 90; // seconds
|
||||
|
||||
private static final String XML_DECLARATION_START = "<?xml";
|
||||
private static final Pattern XML_DECLARATION_PATTERN = Pattern.compile(XML_DECLARATION_START,
|
||||
Pattern.LITERAL | Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronDeviceDiscoveryService.class);
|
||||
|
||||
private IPBridgeHandler bridgeHandler;
|
||||
private DbXmlInfoReader dbXmlInfoReader = new DbXmlInfoReader();
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private @Nullable Future<?> scanTask;
|
||||
|
||||
public LutronDeviceDiscoveryService(IPBridgeHandler bridgeHandler, HttpClient httpClient)
|
||||
throws IllegalArgumentException {
|
||||
super(LutronHandlerFactory.DISCOVERABLE_DEVICE_TYPES_UIDS, DISCOVERY_SERVICE_TIMEOUT);
|
||||
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void startScan() {
|
||||
if (scanTask == null || scanTask.isDone()) {
|
||||
scanTask = scheduler.submit(this::asyncDiscoveryTask);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void asyncDiscoveryTask() {
|
||||
try {
|
||||
readDeviceDatabase();
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception scanning for devices: {}", e.getMessage(), e);
|
||||
|
||||
if (scanListener != null) {
|
||||
scanListener.onErrorOccurred(null); // null so it won't log a stack trace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readDeviceDatabase() {
|
||||
Project project = null;
|
||||
|
||||
if (bridgeHandler == null || bridgeHandler.getIPBridgeConfig() == null) {
|
||||
logger.debug("Unable to get bridge config. Exiting.");
|
||||
return;
|
||||
}
|
||||
String discFileName = bridgeHandler.getIPBridgeConfig().discoveryFile;
|
||||
String address = "http://" + bridgeHandler.getIPBridgeConfig().ipAddress + "/DbXmlInfo.xml";
|
||||
|
||||
if (discFileName == null || discFileName.isEmpty()) {
|
||||
// Read XML from bridge via HTTP
|
||||
logger.trace("Sending http request for {}", address);
|
||||
InputStreamResponseListener listener = new InputStreamResponseListener();
|
||||
Response response = null;
|
||||
|
||||
// Use response stream instead of doing it the simple synchronous way because the response can be very large
|
||||
httpClient.newRequest(address).method(HttpMethod.GET).timeout(HTTP_REQUEST_TIMEOUT, TimeUnit.SECONDS)
|
||||
.header(HttpHeader.ACCEPT, "text/html").header(HttpHeader.ACCEPT_CHARSET, "utf-8").send(listener);
|
||||
|
||||
try {
|
||||
response = listener.get(HTTP_REQUEST_TIMEOUT, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.info("Exception getting HTTP response: {}", e.getMessage());
|
||||
}
|
||||
|
||||
if (response != null && response.getStatus() == HttpStatus.OK_200) {
|
||||
logger.trace("Received good http response.");
|
||||
|
||||
try (InputStream responseStream = listener.getInputStream();
|
||||
InputStreamReader xmlStreamReader = new InputStreamReader(responseStream,
|
||||
StandardCharsets.UTF_8);
|
||||
BufferedReader xmlBufReader = new BufferedReader(xmlStreamReader)) {
|
||||
flushPrePrologLines(xmlBufReader);
|
||||
|
||||
project = dbXmlInfoReader.readFromXML(xmlBufReader);
|
||||
if (project == null) {
|
||||
logger.info("Failed to parse XML project file from {}", address);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.info("IOException while processing XML project file: {}", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
if (response != null) {
|
||||
logger.info("Received HTTP error response: {} {}", response.getStatus(), response.getReason());
|
||||
} else {
|
||||
logger.info("No response for HTTP request.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Read XML from file
|
||||
File xmlFile = new File(discFileName);
|
||||
|
||||
try (BufferedReader xmlReader = Files.newBufferedReader(xmlFile.toPath(), StandardCharsets.UTF_8)) {
|
||||
flushPrePrologLines(xmlReader);
|
||||
|
||||
project = dbXmlInfoReader.readFromXML(xmlReader);
|
||||
if (project == null) {
|
||||
logger.info("Could not process XML project file {}", discFileName);
|
||||
}
|
||||
} catch (IOException | SecurityException e) {
|
||||
logger.info("Exception reading XML project file {} : {}", discFileName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (project != null) {
|
||||
Stack<String> locationContext = new Stack<>();
|
||||
|
||||
for (Area area : project.getAreas()) {
|
||||
processArea(area, locationContext);
|
||||
}
|
||||
for (Timeclock timeclock : project.getTimeclocks()) {
|
||||
processTimeclocks(timeclock, locationContext);
|
||||
}
|
||||
for (GreenMode greenMode : project.getGreenModes()) {
|
||||
processGreenModes(greenMode, locationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes any lines or characters before the start of the XML declaration in the supplied BufferedReader.
|
||||
*
|
||||
* @param xmlReader BufferedReader source of the XML document
|
||||
* @throws IOException
|
||||
*/
|
||||
private void flushPrePrologLines(BufferedReader xmlReader) throws IOException {
|
||||
String inLine = null;
|
||||
xmlReader.mark(DECLARATION_MAX_LEN);
|
||||
boolean foundXmlDec = false;
|
||||
|
||||
while (!foundXmlDec && (inLine = xmlReader.readLine()) != null) {
|
||||
Matcher matcher = XML_DECLARATION_PATTERN.matcher(inLine);
|
||||
if (matcher.find()) {
|
||||
foundXmlDec = true;
|
||||
xmlReader.reset();
|
||||
if (matcher.start() > 0) {
|
||||
logger.trace("Discarding {} characters.", matcher.start());
|
||||
xmlReader.skip(matcher.start());
|
||||
}
|
||||
} else {
|
||||
logger.trace("Discarding line: {}", inLine);
|
||||
xmlReader.mark(DECLARATION_MAX_LEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processArea(Area area, Stack<String> context) {
|
||||
context.push(area.getName());
|
||||
|
||||
for (DeviceNode deviceNode : area.getDeviceNodes()) {
|
||||
if (deviceNode instanceof DeviceGroup) {
|
||||
processDeviceGroup((DeviceGroup) deviceNode, context);
|
||||
} else if (deviceNode instanceof Device) {
|
||||
processDevice((Device) deviceNode, context);
|
||||
}
|
||||
}
|
||||
|
||||
for (Output output : area.getOutputs()) {
|
||||
processOutput(output, context);
|
||||
}
|
||||
|
||||
for (Area subarea : area.getAreas()) {
|
||||
processArea(subarea, context);
|
||||
}
|
||||
|
||||
context.pop();
|
||||
}
|
||||
|
||||
private void processDeviceGroup(DeviceGroup deviceGroup, Stack<String> context) {
|
||||
context.push(deviceGroup.getName());
|
||||
|
||||
for (Device device : deviceGroup.getDevices()) {
|
||||
processDevice(device, context);
|
||||
}
|
||||
|
||||
context.pop();
|
||||
}
|
||||
|
||||
private void processDevice(Device device, Stack<String> context) {
|
||||
List<Integer> buttons;
|
||||
KeypadConfig kpConfig;
|
||||
String kpModel;
|
||||
|
||||
DeviceType type = device.getDeviceType();
|
||||
|
||||
if (type != null) {
|
||||
String label = generateLabel(context, device.getName());
|
||||
|
||||
switch (type) {
|
||||
case MOTION_SENSOR:
|
||||
notifyDiscovery(THING_TYPE_OCCUPANCYSENSOR, device.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case SEETOUCH_KEYPAD:
|
||||
case HYBRID_SEETOUCH_KEYPAD:
|
||||
kpConfig = new KeypadConfigSeetouch();
|
||||
discoverKeypad(device, label, THING_TYPE_KEYPAD, "seeTouch Keypad", kpConfig);
|
||||
break;
|
||||
|
||||
case INTERNATIONAL_SEETOUCH_KEYPAD:
|
||||
kpConfig = new KeypadConfigIntlSeetouch();
|
||||
discoverKeypad(device, label, THING_TYPE_INTLKEYPAD, "International seeTouch Keypad", kpConfig);
|
||||
break;
|
||||
|
||||
case SEETOUCH_TABLETOP_KEYPAD:
|
||||
kpConfig = new KeypadConfigTabletopSeetouch();
|
||||
discoverKeypad(device, label, THING_TYPE_TTKEYPAD, "Tabletop seeTouch Keypad", kpConfig);
|
||||
break;
|
||||
|
||||
case PALLADIOM_KEYPAD:
|
||||
kpConfig = new KeypadConfigPalladiom();
|
||||
discoverKeypad(device, label, THING_TYPE_PALLADIOMKEYPAD, "Palladiom Keypad", kpConfig);
|
||||
break;
|
||||
|
||||
case PICO_KEYPAD:
|
||||
kpConfig = new KeypadConfigPico();
|
||||
discoverKeypad(device, label, THING_TYPE_PICO, "Pico Keypad", kpConfig);
|
||||
break;
|
||||
|
||||
case VISOR_CONTROL_RECEIVER:
|
||||
notifyDiscovery(THING_TYPE_VCRX, device.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case WCI:
|
||||
notifyDiscovery(THING_TYPE_WCI, device.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case MAIN_REPEATER:
|
||||
notifyDiscovery(THING_TYPE_VIRTUALKEYPAD, device.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case QS_IO_INTERFACE:
|
||||
notifyDiscovery(THING_TYPE_QSIO, device.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case GRAFIK_EYE_QS:
|
||||
buttons = getComponentIdList(device.getComponents(), ComponentType.BUTTON);
|
||||
// remove button IDs >= 300 which the handler does not recognize
|
||||
List<Integer> buttonsCopy = new ArrayList<>(buttons);
|
||||
for (Integer c : buttonsCopy) {
|
||||
if (c >= 300) {
|
||||
buttons.remove(Integer.valueOf(c));
|
||||
}
|
||||
}
|
||||
kpConfig = new KeypadConfigGrafikEye();
|
||||
kpModel = kpConfig.determineModelFromComponentIds(buttons);
|
||||
if (kpModel == null) {
|
||||
logger.info("Unable to determine model of GrafikEye Keypad {} with button IDs: {}",
|
||||
device.getIntegrationId(), buttons);
|
||||
notifyDiscovery(THING_TYPE_GRAFIKEYEKEYPAD, device.getIntegrationId(), label);
|
||||
} else {
|
||||
logger.debug("Found GrafikEye keypad {} model: {}", device.getIntegrationId(), kpModel);
|
||||
notifyDiscovery(THING_TYPE_GRAFIKEYEKEYPAD, device.getIntegrationId(), label, "model", kpModel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unrecognized device type {}", device.getType());
|
||||
}
|
||||
}
|
||||
|
||||
private void discoverKeypad(Device device, String label, ThingTypeUID ttUid, String description,
|
||||
KeypadConfig kpConfig) {
|
||||
List<Integer> buttons = getComponentIdList(device.getComponents(), ComponentType.BUTTON);
|
||||
String kpModel = kpConfig.determineModelFromComponentIds(buttons);
|
||||
if (kpModel == null) {
|
||||
logger.info("Unable to determine model of {} {} with button IDs: {}", description,
|
||||
device.getIntegrationId(), buttons);
|
||||
notifyDiscovery(ttUid, device.getIntegrationId(), label);
|
||||
} else {
|
||||
logger.debug("Found {} {} model: {}", description, device.getIntegrationId(), kpModel);
|
||||
notifyDiscovery(ttUid, device.getIntegrationId(), label, "model", kpModel);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> getComponentIdList(List<Component> clist, ComponentType ctype) {
|
||||
List<Integer> returnList = new LinkedList<>();
|
||||
for (Component c : clist) {
|
||||
if (c.getComponentType() == ctype) {
|
||||
returnList.add(c.getComponentNumber());
|
||||
}
|
||||
}
|
||||
return returnList;
|
||||
}
|
||||
|
||||
private void processOutput(Output output, Stack<String> context) {
|
||||
OutputType type = output.getOutputType();
|
||||
|
||||
if (type != null) {
|
||||
String label = generateLabel(context, output.getName());
|
||||
|
||||
switch (type) {
|
||||
case INC:
|
||||
case MLV:
|
||||
case ELV:
|
||||
case DALI:
|
||||
case ECO_SYSTEM_FLUORESCENT:
|
||||
case FLUORESCENT_DB:
|
||||
case ZERO_TO_TEN:
|
||||
case AUTO_DETECT:
|
||||
case CEILING_FAN_TYPE:
|
||||
notifyDiscovery(THING_TYPE_DIMMER, output.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case NON_DIM:
|
||||
case NON_DIM_INC:
|
||||
case NON_DIM_ELV:
|
||||
case RELAY_LIGHTING:
|
||||
notifyDiscovery(THING_TYPE_SWITCH, output.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case CCO_PULSED:
|
||||
notifyDiscovery(THING_TYPE_CCO, output.getIntegrationId(), label, CCO_TYPE, CCO_TYPE_PULSED);
|
||||
break;
|
||||
|
||||
case CCO_MAINTAINED:
|
||||
notifyDiscovery(THING_TYPE_CCO, output.getIntegrationId(), label, CCO_TYPE, CCO_TYPE_MAINTAINED);
|
||||
break;
|
||||
|
||||
case SYSTEM_SHADE:
|
||||
case MOTOR:
|
||||
notifyDiscovery(THING_TYPE_SHADE, output.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case SHEER_BLIND:
|
||||
notifyDiscovery(THING_TYPE_BLIND, output.getIntegrationId(), label, BLIND_TYPE_PARAMETER,
|
||||
BLIND_TYPE_SHEER);
|
||||
break;
|
||||
|
||||
case VENETIAN_BLIND:
|
||||
notifyDiscovery(THING_TYPE_BLIND, output.getIntegrationId(), label, BLIND_TYPE_PARAMETER,
|
||||
BLIND_TYPE_VENETIAN);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unrecognized output type {}", output.getType());
|
||||
}
|
||||
}
|
||||
|
||||
private void processTimeclocks(Timeclock timeclock, Stack<String> context) {
|
||||
String label = generateLabel(context, timeclock.getName());
|
||||
notifyDiscovery(THING_TYPE_TIMECLOCK, timeclock.getIntegrationId(), label);
|
||||
}
|
||||
|
||||
private void processGreenModes(GreenMode greenmode, Stack<String> context) {
|
||||
String label = generateLabel(context, greenmode.getName());
|
||||
notifyDiscovery(THING_TYPE_GREENMODE, greenmode.getIntegrationId(), label);
|
||||
}
|
||||
|
||||
private void notifyDiscovery(ThingTypeUID thingTypeUID, @Nullable Integer integrationId, String label,
|
||||
@Nullable String propName, @Nullable Object propValue) {
|
||||
if (integrationId == null) {
|
||||
logger.info("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.debug("Discovered {}", uid);
|
||||
}
|
||||
|
||||
private void notifyDiscovery(ThingTypeUID thingTypeUID, Integer integrationId, String label) {
|
||||
notifyDiscovery(thingTypeUID, integrationId, label, null, null);
|
||||
}
|
||||
|
||||
private String generateLabel(Stack<String> context, String deviceName) {
|
||||
return String.join(" ", context) + " " + deviceName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 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.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LutronMcastBridgeDiscoveryService} finds RadioRA 2 Main Repeaters and HomeWorks QS
|
||||
* Processors on the network using multicast.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Renamed and added bridge properties
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.lutron")
|
||||
public class LutronMcastBridgeDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private static final int SCAN_INTERVAL_MINUTES = 30;
|
||||
private static final int SCAN_TIMEOUT_MS = 2000;
|
||||
|
||||
private static final Set<ThingTypeUID> BRIDGE_TYPE_UID = Collections.singleton(THING_TYPE_IPBRIDGE);
|
||||
|
||||
private static final String GROUP_ADDRESS = "224.0.37.42";
|
||||
private static final byte[] QUERY_DATA = "<LUTRON=1>".getBytes(StandardCharsets.US_ASCII);
|
||||
private static final int QUERY_DEST_PORT = 2647;
|
||||
private static final Pattern BRIDGE_PROP_PATTERN = Pattern.compile("<([^=>]+)=([^>]*)>");
|
||||
private static final String PRODFAM_RA2 = "RadioRA2";
|
||||
private static final String PRODFAM_HWQS = "Gulliver";
|
||||
|
||||
private static final String DEFAULT_LABEL = "RadioRA2 MainRepeater";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronMcastBridgeDiscoveryService.class);
|
||||
|
||||
private @Nullable ScheduledFuture<?> scanTask;
|
||||
private @Nullable ScheduledFuture<?> backgroundScan;
|
||||
|
||||
public LutronMcastBridgeDiscoveryService() {
|
||||
super(BRIDGE_TYPE_UID, 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
this.scanTask = scheduler.schedule(new RepeaterScanner(), 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopScan() {
|
||||
super.stopScan();
|
||||
|
||||
if (this.scanTask != null) {
|
||||
this.scanTask.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abortScan() {
|
||||
super.abortScan();
|
||||
|
||||
if (this.scanTask != null) {
|
||||
this.scanTask.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
if (this.backgroundScan == null) {
|
||||
this.backgroundScan = scheduler.scheduleWithFixedDelay(new RepeaterScanner(), 1, SCAN_INTERVAL_MINUTES,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
if (this.backgroundScan != null) {
|
||||
this.backgroundScan.cancel(true);
|
||||
this.backgroundScan = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class RepeaterScanner implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
queryForRepeaters();
|
||||
} catch (InterruptedException e) {
|
||||
logger.info("Bridge device scan interrupted");
|
||||
} catch (IOException e) {
|
||||
logger.warn("Communication error during bridge scan: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void queryForRepeaters() throws IOException, InterruptedException {
|
||||
logger.debug("Scanning for Lutron bridge devices using multicast");
|
||||
|
||||
InetAddress group = InetAddress.getByName(GROUP_ADDRESS);
|
||||
|
||||
try (MulticastSocket socket = new MulticastSocket()) {
|
||||
socket.setSoTimeout(SCAN_TIMEOUT_MS);
|
||||
socket.joinGroup(group);
|
||||
|
||||
try {
|
||||
// Try to ensure that joinGroup has taken effect. Without this delay, the query
|
||||
// packet ends up going out before the group join.
|
||||
Thread.sleep(1000);
|
||||
|
||||
socket.send(new DatagramPacket(QUERY_DATA, QUERY_DATA.length, group, QUERY_DEST_PORT));
|
||||
|
||||
byte[] buf = new byte[4096];
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||
|
||||
try {
|
||||
while (!Thread.interrupted()) {
|
||||
socket.receive(packet);
|
||||
createBridge(packet);
|
||||
}
|
||||
|
||||
logger.info("Bridge device scan interrupted");
|
||||
} catch (SocketTimeoutException e) {
|
||||
logger.trace(
|
||||
"Timed out waiting for multicast response. Presumably all bridge devices have already responded.");
|
||||
}
|
||||
} finally {
|
||||
socket.leaveGroup(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createBridge(DatagramPacket packet) {
|
||||
// Check response for the list of properties reported by the device. At a
|
||||
// minimum the IP address and serial number are needed in order to create
|
||||
// the bridge.
|
||||
String data = new String(packet.getData(), packet.getOffset(), packet.getLength(),
|
||||
StandardCharsets.US_ASCII);
|
||||
|
||||
Matcher matcher = BRIDGE_PROP_PATTERN.matcher(data);
|
||||
Map<String, @Nullable String> bridgeProperties = new HashMap<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
bridgeProperties.put(matcher.group(1), matcher.group(2));
|
||||
logger.trace("Bridge property: {} : {}", matcher.group(1), matcher.group(2));
|
||||
}
|
||||
|
||||
String ipAddress = bridgeProperties.get("IPADDR");
|
||||
String serialNumber = bridgeProperties.get("SERNUM");
|
||||
String productFamily = bridgeProperties.get("PRODFAM");
|
||||
String productType = bridgeProperties.get("PRODTYPE");
|
||||
String codeVersion = bridgeProperties.get("CODEVER");
|
||||
String macAddress = bridgeProperties.get("MACADDR");
|
||||
|
||||
if (ipAddress != null && !ipAddress.trim().isEmpty() && serialNumber != null
|
||||
&& !serialNumber.trim().isEmpty()) {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
properties.put(HOST, ipAddress);
|
||||
properties.put(SERIAL_NUMBER, serialNumber);
|
||||
|
||||
if (PRODFAM_RA2.equals(productFamily)) {
|
||||
properties.put(PROPERTY_PRODFAM, "RadioRA 2");
|
||||
} else if (PRODFAM_HWQS.equals(productFamily)) {
|
||||
properties.put(PROPERTY_PRODFAM, "HomeWorks QS");
|
||||
} else {
|
||||
if (productFamily != null) {
|
||||
properties.put(PROPERTY_PRODFAM, productFamily);
|
||||
}
|
||||
}
|
||||
|
||||
if (productType != null && !productType.trim().isEmpty()) {
|
||||
properties.put(PROPERTY_PRODTYP, productType);
|
||||
}
|
||||
if (codeVersion != null && !codeVersion.trim().isEmpty()) {
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, codeVersion);
|
||||
}
|
||||
if (macAddress != null && !macAddress.trim().isEmpty()) {
|
||||
properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
|
||||
}
|
||||
|
||||
ThingUID uid = new ThingUID(THING_TYPE_IPBRIDGE, serialNumber);
|
||||
String label = generateLabel(productFamily, productType);
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(label).withProperties(properties)
|
||||
.withRepresentationProperty(SERIAL_NUMBER).build();
|
||||
|
||||
thingDiscovered(result);
|
||||
|
||||
logger.debug("Discovered Lutron bridge device {}", uid);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateLabel(@Nullable String productFamily, @Nullable String productType) {
|
||||
if (productFamily != null && !productFamily.trim().isEmpty() && productType != null
|
||||
&& !productType.trim().isEmpty()) {
|
||||
return productFamily + " " + productType;
|
||||
}
|
||||
|
||||
return DEFAULT_LABEL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 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.net.InetAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
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.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
@NonNullByDefault
|
||||
public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipant {
|
||||
|
||||
// Lutron mDNS service <app>.<protocol>.<servicedomain>
|
||||
private static final String LUTRON_MDNS_SERVICE_TYPE = "_lutron._tcp.local.";
|
||||
|
||||
private static final String PRODFAM_CASETA = "Caseta";
|
||||
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";
|
||||
|
||||
private static final Pattern HOSTNAME_REGEX = Pattern.compile("lutron-([0-9a-f]+)\\."); // ex: lutron-01f1529a.local
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronMdnsBridgeDiscoveryService.class);
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Collections.singleton(THING_TYPE_IPBRIDGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return LUTRON_MDNS_SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||
if (!service.hasData()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String nice = service.getNiceTextString();
|
||||
String qualifiedName = service.getQualifiedName();
|
||||
|
||||
InetAddress[] ipAddresses = service.getInetAddresses();
|
||||
String devclass = service.getPropertyString("DEVCLASS");
|
||||
String codever = service.getPropertyString("CODEVER");
|
||||
String macaddr = service.getPropertyString("MACADDR");
|
||||
|
||||
logger.debug("Lutron mDNS bridge discovery notified of Lutron mDNS service: {}", nice);
|
||||
logger.trace("Lutron mDNS service qualifiedName: {}", qualifiedName);
|
||||
logger.trace("Lutron mDNS service ipAddresses: {} ({})", ipAddresses, ipAddresses.length);
|
||||
logger.trace("Lutron mDNS service property DEVCLASS: {}", devclass);
|
||||
logger.trace("Lutron mDNS service property CODEVER: {}", codever);
|
||||
logger.trace("Lutron mDNS service property MACADDR: {}", macaddr);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
String label = DEFAULT_LABEL;
|
||||
|
||||
if (ipAddresses.length < 1) {
|
||||
return null;
|
||||
}
|
||||
if (ipAddresses.length > 1) {
|
||||
logger.debug("Multiple addresses found for discovered Lutron device. Using only the first.");
|
||||
}
|
||||
properties.put(HOST, ipAddresses[0].getHostAddress());
|
||||
|
||||
String bridgeHostName = ipAddresses[0].getHostName();
|
||||
logger.debug("Lutron mDNS bridge hostname: {}", bridgeHostName);
|
||||
|
||||
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;
|
||||
} else if (DEVCLASS_RA2_SELECT.equals(devclass)) {
|
||||
properties.put(PROPERTY_PRODFAM, PRODFAM_RA2_SELECT);
|
||||
properties.put(PROPERTY_PRODTYP, PRODTYP_RA2_SELECT);
|
||||
label = PRODFAM_RA2_SELECT + " " + PRODTYP_RA2_SELECT;
|
||||
} else if (DEVCLASS_CONNECT_BRIDGE.equals(devclass)) {
|
||||
logger.debug("Lutron Connect Bridge discovered. Ignoring.");
|
||||
return null;
|
||||
} 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
|
||||
}
|
||||
|
||||
if (!bridgeHostName.equals(ipAddresses[0].getHostAddress())) {
|
||||
label = label + " " + bridgeHostName;
|
||||
}
|
||||
|
||||
if (codever != null) {
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, codever);
|
||||
}
|
||||
|
||||
if (macaddr != null) {
|
||||
properties.put(Thing.PROPERTY_MAC_ADDRESS, macaddr);
|
||||
}
|
||||
|
||||
String sn = getSerial(service);
|
||||
if (sn != null) {
|
||||
logger.trace("Lutron mDNS bridge serial number: {}", sn);
|
||||
properties.put(SERIAL_NUMBER, sn);
|
||||
} else {
|
||||
logger.debug("Unable to determine serial number of discovered Lutron bridge device.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ThingUID uid = getThingUID(service);
|
||||
if (uid != null) {
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(label).withProperties(properties)
|
||||
.withRepresentationProperty(SERIAL_NUMBER).build();
|
||||
logger.debug("Discovered Lutron bridge device via mDNS {}", uid);
|
||||
return result;
|
||||
} else {
|
||||
logger.trace("Failed to create uid for discovered Lutron bridge device");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||
String serial = getSerial(service);
|
||||
if (serial == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ThingUID(THING_TYPE_IPBRIDGE, serial);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the device serial number for the mDNS service by extracting it from the hostname.
|
||||
* Used as unique thing representation property.
|
||||
*
|
||||
* @param service Lutron mDNS service
|
||||
* @return String containing serial number, or null if it cannot be determined
|
||||
*/
|
||||
private @Nullable String getSerial(ServiceInfo service) {
|
||||
InetAddress[] ipAddresses = service.getInetAddresses();
|
||||
if (ipAddresses.length < 1) {
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = HOSTNAME_REGEX.matcher(ipAddresses[0].getHostName());
|
||||
boolean matched = matcher.find();
|
||||
String serialnum = null;
|
||||
|
||||
if (matched) {
|
||||
serialnum = matcher.group(1);
|
||||
}
|
||||
if (matched && serialnum != null && !serialnum.isEmpty()) {
|
||||
return serialnum;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class represents a location defined in the Lutron system. Areas are organized
|
||||
* hierarchically and can represent an entire house, a room in the house, or a specific
|
||||
* location within a room.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public class Area {
|
||||
private String name;
|
||||
private List<DeviceNode> deviceNodes;
|
||||
private List<Output> outputs;
|
||||
private List<Area> areas;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<DeviceNode> getDeviceNodes() {
|
||||
return deviceNodes != null ? deviceNodes : Collections.<DeviceNode> emptyList();
|
||||
}
|
||||
|
||||
public List<Output> getOutputs() {
|
||||
return outputs != null ? outputs : Collections.<Output> emptyList();
|
||||
}
|
||||
|
||||
public List<Area> getAreas() {
|
||||
return areas != null ? areas : Collections.<Area> emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
/**
|
||||
* A component of an input device in a Lutron system. Generally each component of
|
||||
* the device maps to a channel of the device thing.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public class Component {
|
||||
private Integer componentNumber;
|
||||
private String type;
|
||||
|
||||
public Integer getComponentNumber() {
|
||||
return componentNumber;
|
||||
}
|
||||
|
||||
public ComponentType getComponentType() {
|
||||
try {
|
||||
return ComponentType.valueOf(this.type);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.discovery.project;
|
||||
|
||||
/**
|
||||
* Component type in a Lutron device.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public enum ComponentType {
|
||||
BUTTON,
|
||||
CCI,
|
||||
LED,
|
||||
SCENE_CONTROLLER
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An input device in a Lutron system such as a keypad or occupancy sensor.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public class Device implements DeviceNode {
|
||||
private String name;
|
||||
private Integer integrationId;
|
||||
private String type;
|
||||
private List<Component> components;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public DeviceType getDeviceType() {
|
||||
try {
|
||||
return DeviceType.valueOf(this.type);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Component> getComponents() {
|
||||
return components != null ? components : Collections.<Component> emptyList();
|
||||
}
|
||||
}
|
||||
@@ -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.discovery.project;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A group of input devices in the Lutron system.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public class DeviceGroup implements DeviceNode {
|
||||
private String name;
|
||||
private List<Device> devices;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<Device> getDevices() {
|
||||
return devices != null ? devices : Collections.<Device> emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
/**
|
||||
* Marker interface representing either an input device or a group of input
|
||||
* devices.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public interface DeviceNode {
|
||||
}
|
||||
@@ -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.discovery.project;
|
||||
|
||||
/**
|
||||
* Type of input device in a Lutron system.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public enum DeviceType {
|
||||
GRAFIK_EYE_QS,
|
||||
HYBRID_SEETOUCH_KEYPAD,
|
||||
INTERNATIONAL_SEETOUCH_KEYPAD,
|
||||
MAIN_REPEATER,
|
||||
MOTION_SENSOR,
|
||||
PALLADIOM_KEYPAD,
|
||||
PICO_KEYPAD,
|
||||
QS_IO_INTERFACE,
|
||||
SEETOUCH_KEYPAD,
|
||||
SEETOUCH_TABLETOP_KEYPAD,
|
||||
VISOR_CONTROL_RECEIVER,
|
||||
WCI
|
||||
}
|
||||
@@ -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.discovery.project;
|
||||
|
||||
/**
|
||||
* A Green Mode subsystem in a Lutron RadioRA2 controller
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class GreenMode {
|
||||
|
||||
private String name;
|
||||
private Integer integrationId;
|
||||
|
||||
public String getName() {
|
||||
// There may be no name in the XML document
|
||||
return name != null ? name : "Green Mode Subsystem";
|
||||
}
|
||||
|
||||
public Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
/**
|
||||
* An output device in a Lutron system such as a switch or dimmer.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
public class Output {
|
||||
private String name;
|
||||
private Integer integrationId;
|
||||
private String type;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public OutputType getOutputType() {
|
||||
try {
|
||||
return OutputType.valueOf(this.type);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
/**
|
||||
* Type of output device in a Lutron system.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added additional output types
|
||||
*/
|
||||
public enum OutputType {
|
||||
AUTO_DETECT,
|
||||
CCO_MAINTAINED,
|
||||
CCO_PULSED,
|
||||
CEILING_FAN_TYPE,
|
||||
DALI,
|
||||
ECO_SYSTEM_FLUORESCENT,
|
||||
ELV,
|
||||
FLUORESCENT_DB,
|
||||
INC,
|
||||
MLV,
|
||||
MOTOR,
|
||||
NON_DIM,
|
||||
NON_DIM_ELV,
|
||||
NON_DIM_INC,
|
||||
RELAY_LIGHTING,
|
||||
SHEER_BLIND,
|
||||
SYSTEM_SHADE,
|
||||
VENETIAN_BLIND,
|
||||
ZERO_TO_TEN,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class represents a Lutron system and the topology of device things within
|
||||
* that system.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added Timeclock and Green Mode support
|
||||
*/
|
||||
public class Project {
|
||||
private String appVersion;
|
||||
private String xmlVersion;
|
||||
private List<Area> areas;
|
||||
private List<Timeclock> timeclocks;
|
||||
private List<GreenMode> greenmodes;
|
||||
|
||||
public String getAppVersion() {
|
||||
return appVersion;
|
||||
}
|
||||
|
||||
public String getXmlVersion() {
|
||||
return xmlVersion;
|
||||
}
|
||||
|
||||
public List<Area> getAreas() {
|
||||
return areas != null ? areas : Collections.<Area> emptyList();
|
||||
}
|
||||
|
||||
public List<Timeclock> getTimeclocks() {
|
||||
return timeclocks != null ? timeclocks : Collections.<Timeclock> emptyList();
|
||||
}
|
||||
|
||||
public List<GreenMode> getGreenModes() {
|
||||
return greenmodes != null ? greenmodes : Collections.<GreenMode> emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.project;
|
||||
|
||||
/**
|
||||
* A Timeclock subsystem in a Lutron RadioRA2 or HWQS controller
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Timeclock {
|
||||
|
||||
private String name;
|
||||
private Integer integrationId;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 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.grxprg;
|
||||
|
||||
/**
|
||||
* Configuration class for the Grafik Eye controlled by the PRG interface
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
public class GrafikEyeConfig {
|
||||
/**
|
||||
* The control unit identifier
|
||||
*/
|
||||
private int controlUnit;
|
||||
|
||||
/**
|
||||
* The default fade for the unit
|
||||
*/
|
||||
private int fade;
|
||||
|
||||
/**
|
||||
* The zones designated as shades as parsed
|
||||
*/
|
||||
private boolean[] shades = new boolean[8];
|
||||
|
||||
/**
|
||||
* A string representing if the shade configuration was invalid. Will be null if valid, not-null if invalid
|
||||
*/
|
||||
private String shadeError;
|
||||
|
||||
/**
|
||||
* Polling time (in seconds) to refresh state for the unit.
|
||||
*/
|
||||
private int polling;
|
||||
|
||||
/**
|
||||
* Validates the configuration. Ensures the control unit. fade and shadeError are valid.
|
||||
*
|
||||
* @return a non-null text if invalid (explaining why), a null if valid
|
||||
*/
|
||||
String validate() {
|
||||
if (controlUnit < 1 || controlUnit > 8) {
|
||||
return "controlUnit must be between 1-8";
|
||||
}
|
||||
|
||||
if (fade < 0 || fade > 3600) {
|
||||
return "fade must be between 0-3600";
|
||||
}
|
||||
|
||||
if (shadeError != null) {
|
||||
return shadeError;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Control Unit identifier
|
||||
*
|
||||
* @return the control unit identifier
|
||||
*/
|
||||
public int getControlUnit() {
|
||||
return controlUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the control unit identifier
|
||||
*
|
||||
* @param controlUnit the control unit identifier
|
||||
*/
|
||||
public void setControlUnit(int controlUnit) {
|
||||
this.controlUnit = controlUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default fade
|
||||
*
|
||||
* @return the default fade
|
||||
*/
|
||||
public int getFade() {
|
||||
return fade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default fade
|
||||
*
|
||||
* @param fade the default fade
|
||||
*/
|
||||
public void setFade(int fade) {
|
||||
this.fade = fade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if the zone is a shade zone or not. If zone number is invalid, false will be returned.
|
||||
*
|
||||
* @param zone the zone number
|
||||
* @return true if designated as a shade, false otherwise
|
||||
*/
|
||||
boolean isShadeZone(int zone) {
|
||||
if (zone >= 1 && zone <= shades.length) {
|
||||
return shades[zone - 1];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma formatted list of shade zones
|
||||
*
|
||||
* @returna non-null, non-empty comma delimited list of shade zones
|
||||
*/
|
||||
public String getShadeZones() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int z = 0; z < shades.length; z++) {
|
||||
if (shades[z]) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append((z + 1));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the shade zones from a comma delimited list (ex: "2,3,4")
|
||||
*
|
||||
* @param shadeZones a, possibly null, list of zones
|
||||
*/
|
||||
public void setShadeZones(String shadeZones) {
|
||||
shadeError = null;
|
||||
|
||||
for (int zone = 0; zone < 8; zone++) {
|
||||
shades[zone] = false;
|
||||
}
|
||||
|
||||
if (shadeZones != null) {
|
||||
for (String shadeZone : shadeZones.split(",")) {
|
||||
try {
|
||||
final int zone = Integer.parseInt(shadeZone);
|
||||
if (zone >= 1 && zone <= 8) {
|
||||
shades[zone - 1] = true;
|
||||
} else {
|
||||
shadeError = "Shade zone must be between 1-8: " + zone + " - ignoring";
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
shadeError = "Unknown shade zone (can't parse to numeric): " + shadeZone + " - ignoring";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the polling (in seconds) to refresh state
|
||||
*
|
||||
* @return the polling (in seconds) to refresh state
|
||||
*/
|
||||
public int getPolling() {
|
||||
return polling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the polling (in seconds) to refresh state
|
||||
*
|
||||
* @param polling the polling (in seconds) to refresh state
|
||||
*/
|
||||
public void setPolling(int polling) {
|
||||
this.polling = polling;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/**
|
||||
* 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.grxprg;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.NullArgumentException;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link BaseThingHandler} is responsible for handling a specific grafik eye unit (identified by it's control
|
||||
* number). This handler is responsible for handling the commands and management for a single grafik eye unit.
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
public class GrafikEyeHandler extends BaseThingHandler {
|
||||
|
||||
/**
|
||||
* Logger used by this class
|
||||
*/
|
||||
private Logger logger = LoggerFactory.getLogger(GrafikEyeHandler.class);
|
||||
|
||||
/**
|
||||
* Cached instance of the {@link GrafikEyeConfig}. Will be null if disconnected.
|
||||
*/
|
||||
private GrafikEyeConfig config = null;
|
||||
|
||||
/**
|
||||
* The current fade for the grafik eye (only used when setting zone intensity). Will initially be set from
|
||||
* configuration.
|
||||
*/
|
||||
private int fade = 0;
|
||||
|
||||
/**
|
||||
* The polling job to poll the actual state of the grafik eye
|
||||
*/
|
||||
private ScheduledFuture<?> pollingJob;
|
||||
|
||||
/**
|
||||
* Constructs the handler from the {@link org.openhab.core.thing.Thing}
|
||||
*
|
||||
* @param thing a non-null {@link org.openhab.core.thing.Thing} the handler is for
|
||||
*/
|
||||
public GrafikEyeHandler(Thing thing) {
|
||||
super(thing);
|
||||
|
||||
if (thing == null) {
|
||||
throw new IllegalArgumentException("thing cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Handles commands to specific channels. This implementation will offload much of its work to the
|
||||
* {@link PrgProtocolHandler}. Basically we validate the type of command for the channel then call the
|
||||
* {@link PrgProtocolHandler} to handle the actual protocol. Special use case is the {@link RefreshType}
|
||||
* where we call {{@link #handleRefresh(String)} to handle a refresh of the specific channel (which in turn calls
|
||||
* {@link PrgProtocolHandler} to handle the actual refresh
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
handleRefresh(channelUID.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
// Ignore any command if not online
|
||||
return;
|
||||
}
|
||||
|
||||
String id = channelUID.getId();
|
||||
|
||||
if (id == null) {
|
||||
logger.warn("Called with a null channel id - ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (id.equals(PrgConstants.CHANNEL_SCENE)) {
|
||||
if (command instanceof DecimalType) {
|
||||
final int scene = ((DecimalType) command).intValue();
|
||||
getProtocolHandler().selectScene(config.getControlUnit(), scene);
|
||||
} else {
|
||||
logger.error("Received a SCENE command with a non DecimalType: {}", command);
|
||||
}
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SCENELOCK)) {
|
||||
if (command instanceof OnOffType) {
|
||||
getProtocolHandler().setSceneLock(config.getControlUnit(), command == OnOffType.ON);
|
||||
} else {
|
||||
logger.error("Received a SCENELOCK command with a non OnOffType: {}", command);
|
||||
}
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SCENESEQ)) {
|
||||
if (command instanceof OnOffType) {
|
||||
getProtocolHandler().setSceneSequence(config.getControlUnit(), command == OnOffType.ON);
|
||||
} else {
|
||||
logger.error("Received a SCENESEQ command with a non OnOffType: {}", command);
|
||||
}
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_ZONELOCK)) {
|
||||
if (command instanceof OnOffType) {
|
||||
getProtocolHandler().setZoneLock(config.getControlUnit(), command == OnOffType.ON);
|
||||
} else {
|
||||
logger.error("Received a ZONELOCK command with a non OnOffType: {}", command);
|
||||
}
|
||||
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_ZONELOWER)) {
|
||||
final Integer zone = getTrailingNbr(id, PrgConstants.CHANNEL_ZONELOWER);
|
||||
|
||||
if (zone != null) {
|
||||
getProtocolHandler().setZoneLower(config.getControlUnit(), zone);
|
||||
}
|
||||
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_ZONERAISE)) {
|
||||
final Integer zone = getTrailingNbr(id, PrgConstants.CHANNEL_ZONERAISE);
|
||||
|
||||
if (zone != null) {
|
||||
getProtocolHandler().setZoneRaise(config.getControlUnit(), zone);
|
||||
}
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_ZONEFADE)) {
|
||||
if (command instanceof DecimalType) {
|
||||
setFade(((DecimalType) command).intValue());
|
||||
} else {
|
||||
logger.error("Received a ZONEFADE command with a non DecimalType: {}", command);
|
||||
}
|
||||
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_ZONEINTENSITY)) {
|
||||
final Integer zone = getTrailingNbr(id, PrgConstants.CHANNEL_ZONEINTENSITY);
|
||||
|
||||
if (zone != null) {
|
||||
if (command instanceof PercentType) {
|
||||
final int intensity = ((PercentType) command).intValue();
|
||||
getProtocolHandler().setZoneIntensity(config.getControlUnit(), zone, fade, intensity);
|
||||
} else if (command instanceof OnOffType) {
|
||||
getProtocolHandler().setZoneIntensity(config.getControlUnit(), zone, fade,
|
||||
command == OnOffType.ON ? 100 : 0);
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
getProtocolHandler().setZoneIntensity(config.getControlUnit(), zone, fade,
|
||||
command == IncreaseDecreaseType.INCREASE);
|
||||
} else {
|
||||
logger.error("Received a ZONEINTENSITY command with a non DecimalType: {}", command);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_ZONESHADE)) {
|
||||
final Integer zone = getTrailingNbr(id, PrgConstants.CHANNEL_ZONESHADE);
|
||||
|
||||
if (zone != null) {
|
||||
if (command instanceof PercentType) {
|
||||
logger.info("PercentType is not suppored by QED shades");
|
||||
} else if (command == StopMoveType.MOVE) {
|
||||
logger.info("StopMoveType.Move is not suppored by QED shades");
|
||||
} else if (command == StopMoveType.STOP) {
|
||||
getProtocolHandler().setZoneIntensity(config.getControlUnit(), zone, fade, 0);
|
||||
} else if (command instanceof UpDownType) {
|
||||
getProtocolHandler().setZoneIntensity(config.getControlUnit(), zone, fade,
|
||||
command == UpDownType.UP ? 1 : 2);
|
||||
} else {
|
||||
logger.error("Received a ZONEINTENSITY command with a non DecimalType: {}", command);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.error("Unknown/Unsupported Channel id: {}", id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that handles the {@link RefreshType} command specifically. Calls the {@link PrgProtocolHandler} to
|
||||
* handle the actual refresh based on the channel id.
|
||||
*
|
||||
* @param id a non-null, possibly empty channel id to refresh
|
||||
*/
|
||||
private void handleRefresh(String id) {
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id.equals(PrgConstants.CHANNEL_SCENE)) {
|
||||
getProtocolHandler().refreshScene();
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_ZONEINTENSITY)) {
|
||||
getProtocolHandler().refreshZoneIntensity(config.getControlUnit());
|
||||
} else if (id.equals(PrgConstants.CHANNEL_ZONEFADE)) {
|
||||
updateState(PrgConstants.CHANNEL_ZONEFADE, new DecimalType(fade));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the trailing number from the channel id (which usually represents the zone number).
|
||||
*
|
||||
* @param id a non-null, possibly empty channel id
|
||||
* @param channelConstant a non-null, non-empty channel id constant to use in the parse.
|
||||
* @return the trailing number or null if a parse exception occurs
|
||||
*/
|
||||
private Integer getTrailingNbr(String id, String channelConstant) {
|
||||
try {
|
||||
return Integer.parseInt(id.substring(channelConstant.length()));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Unknown channel port #: {}", id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the thing - basically calls {@link #internalInitialize()} to do the work
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
cancelPolling();
|
||||
internalInitialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set's the unit to offline and attempts to reinitialize via {@link #internalInitialize()}
|
||||
*/
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
cancelPolling();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_CONFIGURATION_PENDING);
|
||||
this.thing = thing;
|
||||
internalInitialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the bridge goes offline, cancels the polling and goes offline. If the bridge goes online, will attempt to
|
||||
* re-initialize via {@link #internalInitialize()}
|
||||
*/
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
cancelPolling();
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||
internalInitialize();
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the grafik eye. Essentially validates the {@link GrafikEyeConfig}, updates the status to online and
|
||||
* starts a status refresh job
|
||||
*/
|
||||
private void internalInitialize() {
|
||||
config = getThing().getConfiguration().as(GrafikEyeConfig.class);
|
||||
|
||||
if (config == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
|
||||
return;
|
||||
}
|
||||
|
||||
final String configErr = config.validate();
|
||||
if (configErr != null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configErr);
|
||||
return;
|
||||
}
|
||||
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge == null || !(bridge.getHandler() instanceof PrgBridgeHandler)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"GrafikEye must have a parent PRG Bridge");
|
||||
return;
|
||||
}
|
||||
|
||||
final ThingHandler handler = bridge.getHandler();
|
||||
if (handler.getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
setFade(config.getFade());
|
||||
|
||||
cancelPolling();
|
||||
pollingJob = this.scheduler.scheduleWithFixedDelay(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final ThingStatus status = getThing().getStatus();
|
||||
if (status == ThingStatus.ONLINE && config != null) {
|
||||
getProtocolHandler().refreshState(config.getControlUnit());
|
||||
}
|
||||
}
|
||||
}, 1, config.getPolling(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to cancel our polling if we are currently polling
|
||||
*/
|
||||
private void cancelPolling() {
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PrgProtocolHandler} to use
|
||||
*
|
||||
* @return a non-null {@link PrgProtocolHandler} to use
|
||||
*/
|
||||
private PrgProtocolHandler getProtocolHandler() {
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge == null || !(bridge.getHandler() instanceof PrgBridgeHandler)) {
|
||||
throw new NullArgumentException("Cannot have a Grafix Eye thing outside of the PRG bridge");
|
||||
}
|
||||
|
||||
final PrgProtocolHandler handler = ((PrgBridgeHandler) bridge.getHandler()).getProtocolHandler();
|
||||
if (handler == null) {
|
||||
throw new NullArgumentException("No protocol handler set in the PrgBridgeHandler!");
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the control unit for this handler
|
||||
*
|
||||
* @return the control unit
|
||||
*/
|
||||
int getControlUnit() {
|
||||
return config.getControlUnit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if the given zone is a shade. Off loads it's work to
|
||||
* {@link GrafikEyeConfig#isShadeZone(int)}
|
||||
*
|
||||
* @param zone a zone to check
|
||||
* @return true if a shade zone, false otherwise
|
||||
*/
|
||||
boolean isShade(int zone) {
|
||||
return config == null ? false : config.isShadeZone(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to expose the ability to change state outside of the class
|
||||
*
|
||||
* @param channelId the channel id
|
||||
* @param state the new state
|
||||
*/
|
||||
void stateChanged(String channelId, State state) {
|
||||
updateState(channelId, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set the fade level. Will store the fade and update its state.
|
||||
*
|
||||
* @param fade the new fade
|
||||
*/
|
||||
private void setFade(int fade) {
|
||||
if (fade < 0 || fade > 3600) {
|
||||
throw new IllegalArgumentException("fade must be between 1-3600");
|
||||
}
|
||||
|
||||
this.fade = fade;
|
||||
updateState(PrgConstants.CHANNEL_ZONEFADE, new DecimalType(this.fade));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.grxprg;
|
||||
|
||||
/**
|
||||
* Configuration class for the GRX-PRG/GRX-CI-PRG bridge
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
public class PrgBridgeConfig {
|
||||
|
||||
/**
|
||||
* IP Address (or host name) of switch
|
||||
*/
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* The username to log in with
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* Polling time (in seconds) to attempt a reconnect if the socket session has failed
|
||||
*/
|
||||
private int retryPolling;
|
||||
|
||||
/**
|
||||
* Returns the IP address or host name of the switch
|
||||
*
|
||||
* @return the IP address or host name of the swtich
|
||||
*/
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the IP address or host name of the switch
|
||||
*
|
||||
* @param ipAddress the IP Address or host name of the switch
|
||||
*/
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the username used to login with
|
||||
*
|
||||
* @return the username used to login with
|
||||
*/
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username used to login with
|
||||
*
|
||||
* @param userName the username used to login with
|
||||
*/
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the polling (in seconds) to reconnect
|
||||
*
|
||||
* @return the polling (in seconds) to reconnect
|
||||
*/
|
||||
public int getRetryPolling() {
|
||||
return retryPolling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the polling (in seconds) to reconnect
|
||||
*
|
||||
* @param retryPolling the polling (in seconds to reconnect)
|
||||
*/
|
||||
public void setRetryPolling(int retryPolling) {
|
||||
this.retryPolling = retryPolling;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* 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.grxprg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PrgBridgeHandler} is responsible for handling all bridge interactions. This includes management of the
|
||||
* connection and processing of any commands (thru the {@link PrgProtocolHandler}).
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
public class PrgBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(PrgBridgeHandler.class);
|
||||
|
||||
/**
|
||||
* The {@link PrgProtocolHandler} that handles the actual protocol. Will never be null
|
||||
*/
|
||||
private PrgProtocolHandler protocolHandler;
|
||||
|
||||
/**
|
||||
* The {@link SocketSession} to the physical devices. Will never be null
|
||||
*/
|
||||
private SocketSession session;
|
||||
|
||||
/**
|
||||
* The retry connection event. Null if not retrying.
|
||||
*/
|
||||
private ScheduledFuture<?> retryConnectionJob;
|
||||
|
||||
/**
|
||||
* Constructs the handler from the {@link Bridge}. Simply calls the super constructor with the {@link Bridge},
|
||||
* creates the session (unconnected) and the protocol handler.
|
||||
*
|
||||
* @param bridge a non-null {@link Bridge} the handler is for
|
||||
*/
|
||||
public PrgBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
|
||||
if (bridge == null) {
|
||||
throw new IllegalArgumentException("thing cannot be null");
|
||||
}
|
||||
|
||||
final PrgBridgeConfig config = getPrgBridgeConfig();
|
||||
session = new SocketSession(config.getIpAddress(), 23);
|
||||
|
||||
protocolHandler = new PrgProtocolHandler(session, new PrgHandlerCallback() {
|
||||
@Override
|
||||
public void stateChanged(String channelId, State state) {
|
||||
updateState(channelId, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
|
||||
updateStatus(status, detail, msg);
|
||||
|
||||
if (status != ThingStatus.ONLINE) {
|
||||
disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(int controlUnit, String channelId, State state) {
|
||||
getGrafikEyeHandler(controlUnit).stateChanged(channelId, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShade(int controlUnit, int zone) {
|
||||
return getGrafikEyeHandler(controlUnit).isShade(zone);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to retrieve the {@link PrgProtocolHandler} used by the bridge
|
||||
*
|
||||
* @return a non-null protocol handler to use
|
||||
*/
|
||||
PrgProtocolHandler getProtocolHandler() {
|
||||
return protocolHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to retrieve the {@link GrafikEyeHandler} for a given control unit number. If not found, an
|
||||
* IllegalArgumentException will be thrown.
|
||||
*
|
||||
* @param controlUnit a control number to retrieve
|
||||
* @return a non-null {@link GrafikEyeHandler}
|
||||
* @throws IllegalArgumentException if the {@link GrafikEyeHandler} for the given controlUnit was not found.
|
||||
*/
|
||||
private GrafikEyeHandler getGrafikEyeHandler(int controlUnit) {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
if (thing.getHandler() instanceof GrafikEyeHandler) {
|
||||
final GrafikEyeHandler handler = (GrafikEyeHandler) thing.getHandler();
|
||||
if (handler.getControlUnit() == controlUnit) {
|
||||
return handler;
|
||||
}
|
||||
} else {
|
||||
logger.warn("Should not be a non-GrafikEyeHandler as a thing to this bridge - ignoring: {}", thing);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Could not find a GrafikEyeHandler for control unit : " + controlUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Handles commands to specific channels. This implementation will offload much of its work to the
|
||||
* {@link PrgProtocolHandler}. Basically we validate the type of command for the channel then call the
|
||||
* {@link PrgProtocolHandler} to handle the actual protocol. Special use case is the {@link RefreshType}
|
||||
* where we call {{@link #handleRefresh(String)} to handle a refresh of the specific channel (which in turn calls
|
||||
* {@link PrgProtocolHandler} to handle the actual refresh
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
handleRefresh(channelUID.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
// // Ignore any command if not online
|
||||
// return;
|
||||
// }
|
||||
|
||||
String id = channelUID.getId();
|
||||
|
||||
if (id == null) {
|
||||
logger.warn("Called with a null channel id - ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (id.equals(PrgConstants.CHANNEL_ZONELOWERSTOP)) {
|
||||
protocolHandler.setZoneLowerStop();
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_ZONERAISESTOP)) {
|
||||
protocolHandler.setZoneRaiseStop();
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_TIMECLOCK)) {
|
||||
if (command instanceof DateTimeType) {
|
||||
final ZonedDateTime zdt = ((DateTimeType) command).getZonedDateTime();
|
||||
protocolHandler.setTime(GregorianCalendar.from(zdt));
|
||||
} else {
|
||||
logger.error("Received a TIMECLOCK channel command with a non DateTimeType: {}", command);
|
||||
}
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_SCHEDULE)) {
|
||||
if (command instanceof DecimalType) {
|
||||
final int schedule = ((DecimalType) command).intValue();
|
||||
protocolHandler.selectSchedule(schedule);
|
||||
} else {
|
||||
logger.error("Received a SCHEDULE channel command with a non DecimalType: {}", command);
|
||||
}
|
||||
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCESTART)) {
|
||||
protocolHandler.startSuperSequence();
|
||||
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCEPAUSE)) {
|
||||
protocolHandler.pauseSuperSequence();
|
||||
} else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCERESUME)) {
|
||||
protocolHandler.resumeSuperSequence();
|
||||
|
||||
} else {
|
||||
logger.error("Unknown/Unsupported Channel id: {}", id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that handles the {@link RefreshType} command specifically. Calls the {@link PrgProtocolHandler} to
|
||||
* handle the actual refresh based on the channel id.
|
||||
*
|
||||
* @param id a non-null, possibly empty channel id to refresh
|
||||
*/
|
||||
private void handleRefresh(String id) {
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id.equals(PrgConstants.CHANNEL_TIMECLOCK)) {
|
||||
protocolHandler.refreshTime();
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SCHEDULE)) {
|
||||
protocolHandler.refreshSchedule();
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SUNRISE)) {
|
||||
protocolHandler.refreshSunriseSunset();
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SUNSET)) {
|
||||
protocolHandler.refreshSunriseSunset();
|
||||
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCESTATUS)) {
|
||||
protocolHandler.reportSuperSequenceStatus();
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTSTEP)) {
|
||||
protocolHandler.reportSuperSequenceStatus();
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTMIN)) {
|
||||
protocolHandler.reportSuperSequenceStatus();
|
||||
} else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTSEC)) {
|
||||
protocolHandler.reportSuperSequenceStatus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Initializes the handler. This initialization will read/validate the configuration and will attempt to connect to
|
||||
* the switch (via {{@link #retryConnect()}.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
final PrgBridgeConfig config = getPrgBridgeConfig();
|
||||
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.getIpAddress() == null || config.getIpAddress().trim().length() == 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"IP Address/Host Name of GRX-PRG/GRX-CI-PRG is missing from configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try initial connection in a scheduled task
|
||||
this.scheduler.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
connect();
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to connect to the PRG unit. If successfully connect, the {@link PrgProtocolHandler#login()} will be
|
||||
* called to log into the unit. If a connection cannot be established (or login failed), the connection attempt will
|
||||
* be retried later (via {@link #retryConnect()})
|
||||
*/
|
||||
private void connect() {
|
||||
final PrgBridgeConfig config = getPrgBridgeConfig();
|
||||
|
||||
String response = "Server is offline - will try to reconnect later";
|
||||
try {
|
||||
logger.info("Attempting connection ...");
|
||||
session.connect();
|
||||
|
||||
response = protocolHandler.login(config.getUserName());
|
||||
if (response == null) {
|
||||
if (config != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Exception during connection attempt", e);
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, response);
|
||||
retryConnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to disconnect from the session and will optionally retry the connection attempt.
|
||||
*
|
||||
* @param retryConnection true to retry connection attempts after the disconnect
|
||||
*/
|
||||
private void disconnect(boolean retryConnection) {
|
||||
try {
|
||||
session.disconnect();
|
||||
} catch (IOException e) {
|
||||
// ignore - we don't care
|
||||
}
|
||||
|
||||
if (retryConnection) {
|
||||
retryConnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries the connection attempt - schedules a job in {@link PrgBridgeConfig#getRetryPolling()} seconds to
|
||||
* call the {@link #connect()} method. If a retry attempt is pending, the request is ignored.
|
||||
*/
|
||||
private void retryConnect() {
|
||||
if (retryConnectionJob == null) {
|
||||
final PrgBridgeConfig config = getPrgBridgeConfig();
|
||||
if (config != null) {
|
||||
logger.info("Will try to reconnect in {} seconds", config.getRetryPolling());
|
||||
retryConnectionJob = this.scheduler.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
retryConnectionJob = null;
|
||||
connect();
|
||||
}
|
||||
}, config.getRetryPolling(), TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
logger.debug("RetryConnection called when a retry connection is pending - ignoring request");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplu gets the {@link PrgBridgeConfig} from the {@link Thing} and will set the status to offline if not
|
||||
* found.
|
||||
*
|
||||
* @return a possible null {@link PrgBridgeConfig}
|
||||
*/
|
||||
private PrgBridgeConfig getPrgBridgeConfig() {
|
||||
final PrgBridgeConfig config = getThing().getConfiguration().as(PrgBridgeConfig.class);
|
||||
|
||||
if (config == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Disposes of the handler. Will simply call {@link #disconnect(boolean)} to disconnect and NOT retry the
|
||||
* connection
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
disconnect(false);
|
||||
}
|
||||
}
|
||||
@@ -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.grxprg;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.LutronBindingConstants;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* Defines common constants, which are used across the whole binding.
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PrgConstants {
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_PRGBRIDGE = new ThingTypeUID(LutronBindingConstants.BINDING_ID,
|
||||
"prgbridge");
|
||||
public static final ThingTypeUID THING_TYPE_GRAFIKEYE = new ThingTypeUID(LutronBindingConstants.BINDING_ID,
|
||||
"grafikeye");
|
||||
|
||||
// Channels for the PRG Interface
|
||||
static final String CHANNEL_BUTTONPRESS = "buttonpress";
|
||||
static final String CHANNEL_ZONELOWERSTOP = "zonelowerstop";
|
||||
static final String CHANNEL_ZONERAISESTOP = "zoneraisestop";
|
||||
static final String CHANNEL_TIMECLOCK = "timeclock";
|
||||
static final String CHANNEL_SCHEDULE = "schedule";
|
||||
static final String CHANNEL_SUNRISE = "sunrise";
|
||||
static final String CHANNEL_SUNSET = "sunset";
|
||||
static final String CHANNEL_SUPERSEQUENCESTART = "ssstart";
|
||||
static final String CHANNEL_SUPERSEQUENCEPAUSE = "sspause";
|
||||
static final String CHANNEL_SUPERSEQUENCERESUME = "ssresume";
|
||||
|
||||
static final String CHANNEL_SUPERSEQUENCESTATUS = "ssstatus";
|
||||
static final String CHANNEL_SUPERSEQUENCENEXTSTEP = "ssnextstep";
|
||||
static final String CHANNEL_SUPERSEQUENCENEXTMIN = "ssnextminute";
|
||||
static final String CHANNEL_SUPERSEQUENCENEXTSEC = "ssnextsecond";
|
||||
|
||||
// Channels for the Grafik Eye
|
||||
static final String CHANNEL_SCENE = "scene";
|
||||
static final String CHANNEL_SCENELOCK = "scenelock";
|
||||
static final String CHANNEL_SCENESEQ = "sceneseq";
|
||||
static final String CHANNEL_ZONELOCK = "zonelock";
|
||||
static final String CHANNEL_ZONELOWER = "zonelower";
|
||||
static final String CHANNEL_ZONERAISE = "zoneraise";
|
||||
static final String CHANNEL_ZONEFADE = "zonefade";
|
||||
static final String CHANNEL_ZONEINTENSITY = "zoneintensity";
|
||||
static final String CHANNEL_ZONESHADE = "zoneshade";
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.grxprg;
|
||||
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
*
|
||||
* A callback to {@link PrgBridgeHandler} that will be used by the {@link PrgProtocolHandler} to update the status and
|
||||
* state of the {@link PrgBridgeHandler} or a specific {@link GrafikEyeHandler}
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*
|
||||
*/
|
||||
interface PrgHandlerCallback {
|
||||
/**
|
||||
* Callback to the {@link PrgBridgeHandler} to update the status of the {@link Bridge}
|
||||
*
|
||||
* @param status a non-null {@link org.openhab.core.thing.ThingStatus}
|
||||
* @param detail a non-null {@link org.openhab.core.thing.ThingStatusDetail}
|
||||
* @param msg a possibly null, possibly empty message
|
||||
*/
|
||||
void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg);
|
||||
|
||||
/**
|
||||
* Callback to the {@link PrgBridgeHandler} to update the state of an item
|
||||
*
|
||||
* @param channelId the non-null, non-empty channel id
|
||||
* @param state the new non-null {@State}
|
||||
*/
|
||||
void stateChanged(String channelId, State state);
|
||||
|
||||
/**
|
||||
* Callback to the {@link PrgBridgeHandler} to update the state of an item in a specific {@link GrafikEyeHandler}.
|
||||
*
|
||||
* @param controlUnit the control unit identifier to update
|
||||
* @param channelId the non-null, non-empty channel id
|
||||
* @param state the new non-null {@State}
|
||||
*/
|
||||
void stateChanged(int controlUnit, String channelId, State state);
|
||||
|
||||
/**
|
||||
* Callback to the {@link PrgBridgeHandler} to determine if the specific zone on a specific control unit is a shade
|
||||
* or not
|
||||
*
|
||||
* @param controlUnit the control unit identifier
|
||||
* @param zone the zone identify
|
||||
* @return true if a shade zone, false otherwise
|
||||
*/
|
||||
boolean isShade(int controlUnit, int zone);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* 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.grxprg;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents a restartable socket connection to the underlying telnet session with an GRX-PRG/GRX-CI-PRG. Commands can
|
||||
* be sent via {@link #sendCommand(String)} and responses will be received on the {@link SocketSessionCallback}
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
public class SocketSession {
|
||||
private final Logger logger = LoggerFactory.getLogger(SocketSession.class);
|
||||
|
||||
/**
|
||||
* The host/ip address to connect to
|
||||
*/
|
||||
private final String host;
|
||||
|
||||
/**
|
||||
* The port to connect to
|
||||
*/
|
||||
private final int port;
|
||||
|
||||
/**
|
||||
* The actual socket being used. Will be null if not connected
|
||||
*/
|
||||
private Socket client;
|
||||
|
||||
/**
|
||||
* The writer to the {@link #client}. Will be null if not connected
|
||||
*/
|
||||
private PrintStream writer;
|
||||
|
||||
/**
|
||||
* The reader from the {@link #client}. Will be null if not connected
|
||||
*/
|
||||
private BufferedReader reader;
|
||||
|
||||
/**
|
||||
* The {@link ResponseReader} that will be used to read from {@link #reader}
|
||||
*/
|
||||
private final ResponseReader responseReader = new ResponseReader();
|
||||
|
||||
/**
|
||||
* The responses read from the {@link #responseReader}
|
||||
*/
|
||||
private final BlockingQueue<Object> responsesQueue = new ArrayBlockingQueue<>(50);
|
||||
|
||||
/**
|
||||
* The dispatcher of responses from {@link #responsesQueue}
|
||||
*/
|
||||
private final Dispatcher dispatcher = new Dispatcher();
|
||||
|
||||
/**
|
||||
* The {@link SocketSessionCallback} that the {@link #dispatcher} will call
|
||||
*/
|
||||
private AtomicReference<SocketSessionCallback> callback = new AtomicReference<>(null);
|
||||
|
||||
/**
|
||||
* Creates the socket session from the given host and port
|
||||
*
|
||||
* @param host a non-null, non-empty host/ip address
|
||||
* @param port the port number between 1 and 65535
|
||||
*/
|
||||
public SocketSession(String host, int port) {
|
||||
if (host == null || host.trim().length() == 0) {
|
||||
throw new IllegalArgumentException("Host cannot be null or empty");
|
||||
}
|
||||
|
||||
if (port < 1 || port > 65535) {
|
||||
throw new IllegalArgumentException("Port must be between 1 and 65535");
|
||||
}
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SocketSessionCallback} to use when calling back the
|
||||
* responses that have been received.
|
||||
*
|
||||
* @param callback a non-null {@link SocketSessionCallback} to use
|
||||
*/
|
||||
public void setCallback(SocketSessionCallback callback) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback cannot be null");
|
||||
}
|
||||
this.callback.set(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to connect to the {@link #host} on port {@link #port}. If we are current connected, will
|
||||
* {@link #disconnect()} first. Once connected, the {@link #writer} and {@link #reader} will be created, the
|
||||
* {@link #dispatcher} and {@link #responseReader} will be started.
|
||||
*
|
||||
* @throws java.io.IOException if an exception occurs during the connection attempt
|
||||
*/
|
||||
public void connect() throws IOException {
|
||||
disconnect();
|
||||
|
||||
client = new Socket(host, port);
|
||||
client.setKeepAlive(true);
|
||||
client.setSoTimeout(1000); // allow reader to check to see if it should stop every 1 second
|
||||
|
||||
logger.debug("Connecting to {}:{}", host, port);
|
||||
writer = new PrintStream(client.getOutputStream());
|
||||
reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
|
||||
|
||||
new Thread(responseReader).start();
|
||||
new Thread(dispatcher).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the {@link #host} if we are {@link #isConnected()}. The {@link #writer}, {@link #reader} and
|
||||
* {@link #client}
|
||||
* will be closed and set to null. The {@link #dispatcher} and {@link #responseReader} will be stopped, the
|
||||
* {@link #callback} will be nulled and the {@link #responsesQueue} will be cleared.
|
||||
*
|
||||
* @throws java.io.IOException if an exception occurs during the disconnect attempt
|
||||
*/
|
||||
public void disconnect() throws IOException {
|
||||
if (isConnected()) {
|
||||
logger.debug("Disconnecting from {}:{}", host, port);
|
||||
|
||||
dispatcher.stopRunning();
|
||||
responseReader.stopRunning();
|
||||
|
||||
writer.close();
|
||||
writer = null;
|
||||
|
||||
reader.close();
|
||||
reader = null;
|
||||
|
||||
client.close();
|
||||
client = null;
|
||||
|
||||
callback.set(null);
|
||||
responsesQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we are connected ({@link #client} is not null and is connected)
|
||||
*
|
||||
* @return true if connected, false otherwise
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return client != null && client.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the specified command to the underlying socket
|
||||
*
|
||||
* @param command a non-null, non-empty command
|
||||
* @throws java.io.IOException an exception that occurred while sending
|
||||
*/
|
||||
public synchronized void sendCommand(String command) throws IOException {
|
||||
if (command == null) {
|
||||
throw new IllegalArgumentException("command cannot be null");
|
||||
}
|
||||
|
||||
if (!isConnected()) {
|
||||
throw new IOException("Cannot send message - disconnected");
|
||||
}
|
||||
|
||||
logger.debug("Sending Command: '{}'", command);
|
||||
writer.println(command + "\n"); // as pre spec - each command must have a newline
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the runnable that will read from the socket and add messages to the responses queue (to be processed by
|
||||
* the dispatcher)
|
||||
*
|
||||
* @author Tim Roberts
|
||||
*
|
||||
*/
|
||||
private class ResponseReader implements Runnable {
|
||||
|
||||
/**
|
||||
* Whether the reader is currently rRunning
|
||||
*/
|
||||
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Locking to allow proper shutdown of the reader
|
||||
*/
|
||||
private final Lock rLock = new ReentrantLock();
|
||||
private final Condition rRunning = rLock.newCondition();
|
||||
|
||||
/**
|
||||
* Stops the reader. Will wait 5 seconds for the runnable to stop (should stop within 1 second based on the
|
||||
* setSOTimeout)
|
||||
*/
|
||||
public void stopRunning() {
|
||||
rLock.lock();
|
||||
try {
|
||||
if (isRunning.getAndSet(false)) {
|
||||
if (!rRunning.await(5, TimeUnit.SECONDS)) {
|
||||
logger.warn("Waited too long for dispatcher to finish");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// shouldn't happen
|
||||
} finally {
|
||||
rLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the logic to read from the socket until {@link #isRunning} is false. A 'response' is anything that ends
|
||||
* with a carriage-return/newline combo. Additionally, the special "login" prompts are
|
||||
* treated as responses for purposes of logging in.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
final StringBuilder sb = new StringBuilder(100);
|
||||
int c;
|
||||
|
||||
isRunning.set(true);
|
||||
responsesQueue.clear();
|
||||
|
||||
while (isRunning.get()) {
|
||||
try {
|
||||
// if reader is null, sleep and try again
|
||||
if (reader == null) {
|
||||
Thread.sleep(250);
|
||||
continue;
|
||||
}
|
||||
|
||||
c = reader.read();
|
||||
if (c == -1) {
|
||||
responsesQueue.put(new IOException("server closed connection"));
|
||||
isRunning.set(false);
|
||||
break;
|
||||
}
|
||||
final char ch = (char) c;
|
||||
sb.append(ch);
|
||||
if (ch == '\n' || ch == ' ') {
|
||||
final String str = sb.toString();
|
||||
if (str.endsWith("\r\n") || str.endsWith("login: ")) {
|
||||
sb.setLength(0);
|
||||
final String response = str.substring(0, str.length() - 2);
|
||||
logger.debug("Received response: {}", response);
|
||||
responsesQueue.put(response);
|
||||
}
|
||||
}
|
||||
// logger.debug(">>> reading: " + sb + ":" + (int) ch);
|
||||
} catch (SocketTimeoutException e) {
|
||||
// do nothing - we expect this (setSOTimeout) to check the _isReading
|
||||
} catch (InterruptedException e) {
|
||||
// Do nothing - probably shutting down
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
isRunning.set(false);
|
||||
responsesQueue.put(e);
|
||||
} catch (InterruptedException e1) {
|
||||
// Do nothing - probably shutting down
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rLock.lock();
|
||||
try {
|
||||
rRunning.signalAll();
|
||||
} finally {
|
||||
rLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The dispatcher runnable is responsible for reading the response queue and dispatching it to the current callable.
|
||||
* Since the dispatcher is ONLY started when a callable is set, responses may pile up in the queue and be dispatched
|
||||
* when a callable is set. Unlike the socket reader, this can be assigned to another thread (no state outside of the
|
||||
* class).
|
||||
*
|
||||
* @author Tim Roberts
|
||||
*/
|
||||
private class Dispatcher implements Runnable {
|
||||
|
||||
/**
|
||||
* Whether the dispatcher is rRunning or not
|
||||
*/
|
||||
private final AtomicBoolean dispatcherRunning = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Locking to allow proper shutdown of the reader
|
||||
*/
|
||||
private final Lock dLock = new ReentrantLock();
|
||||
private final Condition dRunning = dLock.newCondition();
|
||||
|
||||
/**
|
||||
* Stops the reader. Will wait 5 seconds for the runnable to stop (should stop within 1 second based on the poll
|
||||
* timeout below)
|
||||
*/
|
||||
public void stopRunning() {
|
||||
dLock.lock();
|
||||
try {
|
||||
if (dispatcherRunning.getAndSet(false)) {
|
||||
if (!dRunning.await(5, TimeUnit.SECONDS)) {
|
||||
logger.warn("Waited too long for dispatcher to finish");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// do nothing
|
||||
} finally {
|
||||
dLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the logic to dispatch any responses to the current callback until {@link #isRunning} is false.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
dispatcherRunning.set(true);
|
||||
while (dispatcherRunning.get()) {
|
||||
try {
|
||||
final SocketSessionCallback ssCallback = callback.get();
|
||||
|
||||
// if callback is null, we don't want to start dispatching yet.
|
||||
if (ssCallback == null) {
|
||||
Thread.sleep(250);
|
||||
continue;
|
||||
}
|
||||
|
||||
final Object response = responsesQueue.poll(1, TimeUnit.SECONDS);
|
||||
|
||||
if (response != null) {
|
||||
if (response instanceof String) {
|
||||
try {
|
||||
logger.debug("Dispatching response: {}", response);
|
||||
ssCallback.responseReceived((String) response);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Exception occurred processing the response '{}': ", response, e);
|
||||
}
|
||||
} else if (response instanceof Exception) {
|
||||
logger.debug("Dispatching exception: {}", response);
|
||||
ssCallback.responseException((Exception) response);
|
||||
} else {
|
||||
logger.error("Unknown response class: {}", response);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
dLock.lock();
|
||||
try {
|
||||
// Signal that we are done
|
||||
dRunning.signalAll();
|
||||
} finally {
|
||||
dLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.grxprg;
|
||||
|
||||
/**
|
||||
* Interface defining a callback from {@link SocketSession} when a response was received (or an exception occurred)
|
||||
*
|
||||
* @author Tim Roberts - Initial contribution
|
||||
*/
|
||||
public interface SocketSessionCallback {
|
||||
/**
|
||||
* Called when a command has completed with the response for the command
|
||||
*
|
||||
* @param response a non-null, possibly empty response
|
||||
*/
|
||||
public void responseReceived(String response);
|
||||
|
||||
/**
|
||||
* Called when a command finished with an exception
|
||||
*
|
||||
* @param e a non-null exception
|
||||
*/
|
||||
public void responseException(Exception e);
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
/**
|
||||
* 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.BINDING_ID;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
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.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract class providing common definitions and methods for derived keypad classes
|
||||
*
|
||||
* @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<>();
|
||||
|
||||
protected int integrationId;
|
||||
protected String model;
|
||||
protected Boolean autoRelease;
|
||||
protected Boolean advancedChannels = false;
|
||||
|
||||
protected Map<Integer, String> componentChannelMap = new HashMap<>(50);
|
||||
|
||||
protected abstract void configureComponents(String model);
|
||||
|
||||
private final Object asyncInitLock = new Object();
|
||||
|
||||
protected KeypadConfig kp;
|
||||
|
||||
public BaseKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if keypad component with the specified id is a LED. Keypad handlers which do not use a KeypadConfig
|
||||
* object must override this to provide their own test.
|
||||
*
|
||||
* @param id The component id.
|
||||
* @return True if the component is a LED.
|
||||
*/
|
||||
protected boolean isLed(int id) {
|
||||
return kp.isLed(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if keypad component with the specified id is a button. Keypad handlers which do not use a KeypadConfig
|
||||
* object must override this to provide their own test.
|
||||
*
|
||||
* @param id The component id.
|
||||
* @return True if the component is a button.
|
||||
*/
|
||||
protected boolean isButton(int id) {
|
||||
return kp.isButton(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if keypad component with the specified id is a CCI. Keypad handlers which do not use a KeypadConfig
|
||||
* object must override this to provide their own test.
|
||||
*
|
||||
* @param id The component id.
|
||||
* @return True if the component is a CCI.
|
||||
*/
|
||||
protected boolean isCCI(int id) {
|
||||
return kp.isCCI(id);
|
||||
}
|
||||
|
||||
protected void configureChannels() {
|
||||
Channel channel;
|
||||
ChannelTypeUID channelTypeUID;
|
||||
ChannelUID channelUID;
|
||||
|
||||
logger.debug("Configuring channels for keypad {}", integrationId);
|
||||
|
||||
List<Channel> channelList = new ArrayList<>();
|
||||
List<Channel> existingChannels = getThing().getChannels();
|
||||
|
||||
if (existingChannels != null && !existingChannels.isEmpty()) {
|
||||
// Clear existing channels
|
||||
logger.debug("Clearing existing channels for keypad {}", integrationId);
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withChannels(channelList);
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
|
||||
// add channels for buttons
|
||||
for (KeypadComponent component : buttonList) {
|
||||
channelTypeUID = new ChannelTypeUID(BINDING_ID, advancedChannels ? "buttonAdvanced" : "button");
|
||||
channelUID = new ChannelUID(getThing().getUID(), component.channel());
|
||||
channel = ChannelBuilder.create(channelUID, "Switch").withType(channelTypeUID)
|
||||
.withLabel(component.description()).build();
|
||||
channelList.add(channel);
|
||||
}
|
||||
|
||||
// add channels for LEDs
|
||||
for (KeypadComponent component : ledList) {
|
||||
channelTypeUID = new ChannelTypeUID(BINDING_ID, advancedChannels ? "ledIndicatorAdvanced" : "ledIndicator");
|
||||
channelUID = new ChannelUID(getThing().getUID(), component.channel());
|
||||
channel = ChannelBuilder.create(channelUID, "Switch").withType(channelTypeUID)
|
||||
.withLabel(component.description()).build();
|
||||
channelList.add(channel);
|
||||
}
|
||||
|
||||
// add channels for CCIs (for VCRX or eventually HomeWorks CCI)
|
||||
for (KeypadComponent component : cciList) {
|
||||
channelTypeUID = new ChannelTypeUID(BINDING_ID, "cciState");
|
||||
channelUID = new ChannelUID(getThing().getUID(), component.channel());
|
||||
channel = ChannelBuilder.create(channelUID, "Contact").withType(channelTypeUID)
|
||||
.withLabel(component.description()).build();
|
||||
channelList.add(channel);
|
||||
}
|
||||
|
||||
thingBuilder.withChannels(channelList);
|
||||
updateThing(thingBuilder.build());
|
||||
logger.debug("Done configuring channels for keypad {}", integrationId);
|
||||
}
|
||||
|
||||
protected ChannelUID channelFromComponent(int component) {
|
||||
String channel = null;
|
||||
|
||||
// Get channel string from Lutron component ID using HashBiMap
|
||||
channel = componentChannelMap.get(component);
|
||||
if (channel == null) {
|
||||
logger.debug("Unknown component {}", component);
|
||||
}
|
||||
return channel == null ? null : new ChannelUID(getThing().getUID(), channel);
|
||||
}
|
||||
|
||||
protected Integer componentFromChannel(ChannelUID channelUID) {
|
||||
return componentChannelMap.entrySet().stream().filter(e -> e.getValue().equals(channelUID.getId()))
|
||||
.map(Entry::getKey).findAny().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Number id = (Number) getThing().getConfiguration().get("integrationId");
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
|
||||
return;
|
||||
}
|
||||
integrationId = id.intValue();
|
||||
|
||||
logger.debug("Initializing Keypad Handler for integration ID {}", id);
|
||||
|
||||
model = (String) getThing().getConfiguration().get("model");
|
||||
if (model != null) {
|
||||
model = model.toUpperCase();
|
||||
if (model.contains("-")) {
|
||||
// strip off system prefix if model is of the form "system-model"
|
||||
String[] modelSplit = model.split("-", 2);
|
||||
model = modelSplit[1];
|
||||
}
|
||||
}
|
||||
|
||||
Boolean arParam = (Boolean) getThing().getConfiguration().get("autorelease");
|
||||
autoRelease = arParam == null ? true : arParam;
|
||||
|
||||
// schedule a thread to finish initialization asynchronously since it can take several seconds
|
||||
scheduler.schedule(this::asyncInitialize, 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void asyncInitialize() {
|
||||
synchronized (asyncInitLock) {
|
||||
logger.debug("Async init thread staring for keypad handler {}", integrationId);
|
||||
|
||||
buttonList.clear(); // in case we are re-initializing
|
||||
ledList.clear();
|
||||
cciList.clear();
|
||||
componentChannelMap.clear();
|
||||
|
||||
configureComponents(model);
|
||||
|
||||
// load the channel-id map
|
||||
for (KeypadComponent component : buttonList) {
|
||||
componentChannelMap.put(component.id(), component.channel());
|
||||
}
|
||||
for (KeypadComponent component : ledList) {
|
||||
componentChannelMap.put(component.id(), component.channel());
|
||||
}
|
||||
for (KeypadComponent component : cciList) {
|
||||
componentChannelMap.put(component.id(), component.channel());
|
||||
}
|
||||
|
||||
configureChannels();
|
||||
|
||||
initDeviceState();
|
||||
|
||||
logger.debug("Async init thread finishing for keypad handler {}", integrationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initDeviceState() {
|
||||
synchronized (asyncInitLock) {
|
||||
logger.debug("Initializing device state for Keypad {}", integrationId);
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
if (ledList.isEmpty()) {
|
||||
// Device with no LEDs has nothing to query. Assume it is online if bridge is online.
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
// Query LED states. Method handleUpdate() will set thing status to online when response arrives.
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, Command command) {
|
||||
logger.debug("Handling command {} for channel {}", command, channelUID);
|
||||
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
if (channel == null) {
|
||||
logger.warn("Command received on invalid channel {} for device {}", channelUID, getThing().getUID());
|
||||
return;
|
||||
}
|
||||
|
||||
Integer componentID = componentFromChannel(channelUID);
|
||||
if (componentID == null) {
|
||||
logger.warn("Command received on invalid channel {} for device {}", channelUID, getThing().getUID());
|
||||
return;
|
||||
}
|
||||
|
||||
// For LEDs, handle RefreshType and OnOffType commands
|
||||
if (isLed(componentID)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryDevice(componentID, ACTION_LED_STATE);
|
||||
} else if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
device(componentID, ACTION_LED_STATE, LED_ON);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
device(componentID, ACTION_LED_STATE, LED_OFF);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid command {} received for channel {} device {}", command, channelUID,
|
||||
getThing().getUID());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For buttons, handle OnOffType commands
|
||||
if (isButton(componentID)) {
|
||||
if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
device(componentID, ACTION_PRESS);
|
||||
if (autoRelease) {
|
||||
device(componentID, ACTION_RELEASE);
|
||||
}
|
||||
} else if (command == OnOffType.OFF) {
|
||||
device(componentID, ACTION_RELEASE);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid command type {} received for channel {} device {}", command, channelUID,
|
||||
getThing().getUID());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Contact channels for CCIs are read-only, so ignore commands
|
||||
if (isCCI(componentID)) {
|
||||
logger.debug("Invalid command type {} received for channel {} device {}", command, channelUID,
|
||||
getThing().getUID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
logger.debug("Linking keypad {} channel {}", integrationId, channelUID.getId());
|
||||
|
||||
Integer id = componentFromChannel(channelUID);
|
||||
if (id == null) {
|
||||
logger.warn("Unrecognized channel ID {} linked", channelUID.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// if this channel is for an LED, query the Lutron controller for the current state
|
||||
if (isLed(id)) {
|
||||
queryDevice(id, ACTION_LED_STATE);
|
||||
}
|
||||
// Button and CCI state can't be queried, only monitored for updates.
|
||||
// Init button state to OFF on channel init.
|
||||
if (isButton(id)) {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
}
|
||||
// Leave CCI channel state undefined on channel init.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
logger.trace("Handling command {} {} from keypad {}", type, parameters, integrationId);
|
||||
if (type == LutronCommandType.DEVICE && parameters.length >= 2) {
|
||||
int component;
|
||||
|
||||
try {
|
||||
component = Integer.parseInt(parameters[0]);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("Invalid component {} in keypad update event message", parameters[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelUID channelUID = channelFromComponent(component);
|
||||
|
||||
if (channelUID != null) {
|
||||
if (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])) {
|
||||
updateState(channelUID, OnOffType.ON);
|
||||
} else if (LED_OFF.toString().equals(parameters[2])) {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
}
|
||||
} else if (ACTION_PRESS.toString().equals(parameters[1])) {
|
||||
if (isButton(component)) {
|
||||
updateState(channelUID, OnOffType.ON);
|
||||
if (autoRelease) {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
}
|
||||
} else { // component is CCI
|
||||
updateState(channelUID, OpenClosedType.CLOSED);
|
||||
}
|
||||
} else if (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])) {
|
||||
updateState(channelUID, OnOffType.OFF); // Signal a release if we receive a hold code as we will not
|
||||
// get a subsequent release.
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unable to determine channel for component {} in keypad update event message",
|
||||
parameters[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 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.openhab.binding.lutron.internal.config.BlindConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
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 with Lutron blinds
|
||||
*
|
||||
* @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
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BlindHandler.class);
|
||||
|
||||
private BlindConfig config;
|
||||
|
||||
public BlindHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
if (config == null) {
|
||||
throw new IllegalStateException("handler configuration not initialized");
|
||||
}
|
||||
return config.integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getThing().getConfiguration().as(BlindConfig.class);
|
||||
if (config.integrationId <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId configured");
|
||||
return;
|
||||
}
|
||||
if (config.type == null || (!(BLIND_TYPE_SHEER.equalsIgnoreCase(config.type))
|
||||
&& !(BLIND_TYPE_VENETIAN.equalsIgnoreCase(config.type)))) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Parameter type not set to valid value");
|
||||
return;
|
||||
}
|
||||
String blindType = config.type;
|
||||
if (BLIND_TYPE_SHEER.equalsIgnoreCase(blindType)) {
|
||||
tiltMax = 50;
|
||||
}
|
||||
logger.debug("Initializing Blind handler with type {} for integration ID {}", blindType, config.integrationId);
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Shade {}", 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(ACTION_LIFTLEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
// Refresh state when new item is linked.
|
||||
if (channelUID.getId().equals(CHANNEL_BLINDLIFTLEVEL)) {
|
||||
queryOutput(ACTION_LIFTLEVEL);
|
||||
} else if (channelUID.getId().equals(CHANNEL_BLINDTILTLEVEL)) {
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_BLINDLIFTLEVEL)) {
|
||||
handleLiftCommand(command);
|
||||
} else if (channelUID.getId().equals(CHANNEL_BLINDTILTLEVEL)) {
|
||||
handleTiltCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLiftCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_LIFTLEVEL, level, 0);
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISINGLIFT);
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERINGLIFT);
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOPLIFT);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_LIFTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTiltCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_TILTLEVEL, Math.min(level, tiltMax), 0);
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISINGTILT);
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERINGTILT);
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOPTILT);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length >= 2) {
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
if (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])) {
|
||||
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) {
|
||||
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])
|
||||
&& 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);
|
||||
updateState(CHANNEL_BLINDLIFTLEVEL, new PercentType(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 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 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.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron contact closure outputs (CCOs).
|
||||
* e.g. VCRX CCOs and CCO RF module
|
||||
*
|
||||
* Note: For a RA2 Pulsed CCO, querying the output state with ?OUTPUT,<id>,1 is meaningless and will always
|
||||
* return 100 (on). Also, the main repeater will not report ~OUTPUT commands for a pulsed CCO regardless of
|
||||
* the #MONITORING setting. So this binding supports sending pulses ONLY.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*
|
||||
*/
|
||||
@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;
|
||||
private double defaultPulse = 0.5; // default pulse length (seconds)
|
||||
|
||||
protected enum CcoOutputType {
|
||||
PULSED,
|
||||
MAINTAINED
|
||||
}
|
||||
|
||||
protected @Nullable CcoOutputType outputType;
|
||||
|
||||
public CcoHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Number id = (Number) getThing().getConfiguration().get(INTEGRATION_ID);
|
||||
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
|
||||
return;
|
||||
}
|
||||
integrationId = id.intValue();
|
||||
logger.debug("Initializing CCO handler for integration ID {}", id);
|
||||
|
||||
// Determine output type from configuration if not pre-defined by subclass
|
||||
if (outputType == null) {
|
||||
String oType = (String) getThing().getConfiguration().get(CCO_TYPE);
|
||||
|
||||
if (oType == null || oType == CCO_TYPE_PULSED) {
|
||||
logger.debug("Setting CCO type Pulsed for device {}.", integrationId);
|
||||
outputType = CcoOutputType.PULSED;
|
||||
} else if (oType == CCO_TYPE_MAINTAINED) {
|
||||
logger.debug("Setting CCO type Maintained for device {}.", integrationId);
|
||||
outputType = CcoOutputType.MAINTAINED;
|
||||
} else {
|
||||
logger.warn("Invalid CCO type setting for device {}. Defaulting to Pulsed.", integrationId);
|
||||
outputType = CcoOutputType.PULSED;
|
||||
}
|
||||
}
|
||||
|
||||
// If output type pulsed, determine pulse length
|
||||
if (outputType == CcoOutputType.PULSED) {
|
||||
Number defaultPulse = (Number) getThing().getConfiguration().get(DEFAULT_PULSE);
|
||||
|
||||
if (defaultPulse != null) {
|
||||
double dp = defaultPulse.doubleValue();
|
||||
if (dp >= 0 && dp <= 99.0) {
|
||||
defaultPulse = dp;
|
||||
logger.debug("Pulse length set to {} seconds for device {}.", defaultPulse, integrationId);
|
||||
} else {
|
||||
logger.warn("Invalid pulse length value set. Using default for device {}.", integrationId);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Using default pulse length value for device {}", integrationId);
|
||||
}
|
||||
}
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for CCO {}", integrationId);
|
||||
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(ACTION_STATE); // 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_SWITCH)) {
|
||||
logger.debug("switch channel {} linked for CCO {}", channelUID.getId(), integrationId);
|
||||
|
||||
if (outputType == CcoOutputType.PULSED) {
|
||||
// Since this is a pulsed CCO channel state is always OFF
|
||||
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);
|
||||
} else {
|
||||
logger.warn("invalid output type defined for CCO {}", integrationId);
|
||||
}
|
||||
} else {
|
||||
logger.warn("invalid channel {} linked for CCO {}", channelUID.getId(), integrationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
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));
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
} else {
|
||||
output(ACTION_STATE, 100);
|
||||
}
|
||||
}
|
||||
|
||||
else if (command instanceof OnOffType && command == OnOffType.OFF) {
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
output(ACTION_STATE, 0);
|
||||
}
|
||||
}
|
||||
|
||||
else if (command instanceof RefreshType) {
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
queryOutput(ACTION_STATE);
|
||||
} else {
|
||||
updateState(CHANNEL_SWITCH, OnOffType.OFF);
|
||||
}
|
||||
} else {
|
||||
logger.debug("ignoring invalid command on channel {} for CCO {}", channelUID.getId(), integrationId);
|
||||
}
|
||||
} else {
|
||||
logger.debug("ignoring command on invalid channel {} for CCO {}", channelUID.getId(), integrationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
logger.debug("Update received for CCO: {} {}", type, StringUtils.join(parameters, ","));
|
||||
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_STATE.toString().equals(parameters[0])) {
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
try {
|
||||
BigDecimal state = new BigDecimal(parameters[1]);
|
||||
updateState(CHANNEL_SWITCH, state.compareTo(BigDecimal.ZERO) == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Unable to parse update {} {} from CCO {}", type, StringUtils.join(parameters, ","),
|
||||
integrationId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Do nothing on receiving updates for pulsed CCO except update online status
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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_LIGHTLEVEL;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.openhab.binding.lutron.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.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with a light dimmer.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @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;
|
||||
private LutronDuration fadeOutTime;
|
||||
private final AtomicReference<BigDecimal> lastLightLevel = new AtomicReference<>();
|
||||
|
||||
public DimmerHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singletonList(DimmerActions.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
if (config == null) {
|
||||
throw new IllegalStateException("handler not initialized");
|
||||
}
|
||||
|
||||
return config.integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getThing().getConfiguration().as(DimmerConfig.class);
|
||||
if (config.integrationId <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId configured");
|
||||
return;
|
||||
}
|
||||
fadeInTime = new LutronDuration(config.fadeInTime);
|
||||
fadeOutTime = new LutronDuration(config.fadeOutTime);
|
||||
logger.debug("Initializing Dimmer handler for integration ID {}", getIntegrationId());
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Dimmer {}", 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(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
lastLightLevel.set(config.onLevel);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
|
||||
if (command instanceof Number) {
|
||||
int level = ((Number) command).intValue();
|
||||
output(ACTION_ZONELEVEL, level, 0.25);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
if (config.onToLast) {
|
||||
output(ACTION_ZONELEVEL, lastLightLevel.get(), fadeInTime);
|
||||
} else {
|
||||
output(ACTION_ZONELEVEL, config.onLevel, fadeInTime);
|
||||
}
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
output(ACTION_ZONELEVEL, 0, fadeOutTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLightLevel(BigDecimal level, LutronDuration fade, LutronDuration delay) {
|
||||
int intLevel = level.intValue();
|
||||
output(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])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
if (level.compareTo(BigDecimal.ZERO) == 1) { // if (level > 0)
|
||||
lastLightLevel.set(level);
|
||||
}
|
||||
updateState(CHANNEL_LIGHTLEVEL, new PercentType(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigGrafikEye;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with GRAFIK Eye QS devices in
|
||||
* a RadioRA 2 or HomeWorks QS System.
|
||||
*
|
||||
* Does not communicate with the scene controller, timeclock controller, or wireless
|
||||
* and EcoSystem occupancy sensors.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GrafikEyeKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GrafikEyeKeypadHandler.class);
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
String mod = model == null ? "3COL" : model;
|
||||
logger.debug("Configuring components for GRAFIK Eye QS");
|
||||
|
||||
switch (mod) {
|
||||
case "3COL":
|
||||
case "2COL":
|
||||
case "1COL":
|
||||
case "0COL":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
ledList = kp.getComponents(mod, ComponentType.LED);
|
||||
cciList = kp.getComponents(mod, ComponentType.CCI);
|
||||
break;
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming model 3COL.", mod);
|
||||
buttonList = kp.getComponents("3COL", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("3COL", ComponentType.LED);
|
||||
cciList = kp.getComponents("3COL", ComponentType.CCI);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public GrafikEyeKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
kp = new KeypadConfigGrafikEye();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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.util.concurrent.ScheduledFuture;
|
||||
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.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with RadioRA 2 Green Mode subsystem
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GreenModeHandler extends LutronHandler {
|
||||
private static final Integer ACTION_STEP = 1;
|
||||
public static final int GREENSTEP_MIN = 1;
|
||||
|
||||
// poll interval parameters are in minutes
|
||||
private static final int POLL_INTERVAL_DEFAULT = 15;
|
||||
private static final int POLL_INTERVAL_MAX = 240;
|
||||
private static final int POLL_INTERVAL_MIN = 0;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GreenModeHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
private int pollInterval;
|
||||
private @Nullable ScheduledFuture<?> pollJob;
|
||||
|
||||
public GreenModeHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Number id = (Number) getThing().getConfiguration().get(INTEGRATION_ID);
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
|
||||
return;
|
||||
}
|
||||
integrationId = id.intValue();
|
||||
|
||||
Number pollInterval = (Number) getThing().getConfiguration().get(POLL_INTERVAL);
|
||||
if (pollInterval == null) {
|
||||
this.pollInterval = POLL_INTERVAL_DEFAULT;
|
||||
} else {
|
||||
this.pollInterval = pollInterval.intValue();
|
||||
this.pollInterval = Math.min(this.pollInterval, POLL_INTERVAL_MAX);
|
||||
this.pollInterval = Math.max(this.pollInterval, POLL_INTERVAL_MIN);
|
||||
}
|
||||
logger.debug("Initializing Green Mode handler for integration ID {} with poll interval {}", integrationId,
|
||||
this.pollInterval);
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Green Mode subsystem {}", getIntegrationId());
|
||||
stopPolling();
|
||||
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");
|
||||
queryGreenMode(ACTION_STEP); // handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thingOfflineNotify() {
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
private void startPolling() {
|
||||
if (pollInterval > 0 && pollJob == null) {
|
||||
logger.debug("Scheduling green mode polling job for integration ID {}", integrationId);
|
||||
pollJob = scheduler.scheduleWithFixedDelay(this::pollState, pollInterval, pollInterval, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPolling() {
|
||||
if (pollJob != null) {
|
||||
logger.debug("Canceling green mode polling job for integration ID {}", integrationId);
|
||||
pollJob.cancel(true);
|
||||
pollJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void pollState() {
|
||||
logger.trace("Executing green mode polling job for integration ID {}", integrationId);
|
||||
queryGreenMode(ACTION_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_STEP)) {
|
||||
queryGreenMode(ACTION_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_STEP)) {
|
||||
if (command == OnOffType.ON) {
|
||||
greenMode(ACTION_STEP, 2);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
greenMode(ACTION_STEP, 1);
|
||||
} else if (command instanceof Number) {
|
||||
Integer step = new Integer(((Number) command).intValue());
|
||||
if (step.intValue() >= GREENSTEP_MIN) {
|
||||
greenMode(ACTION_STEP, step);
|
||||
}
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryGreenMode(ACTION_STEP);
|
||||
} else {
|
||||
logger.debug("Ignoring invalid command {} for id {}", command, integrationId);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring command to invalid channel {} for id {}", channelUID.getId(), integrationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
try {
|
||||
if (type == LutronCommandType.MODE && parameters.length > 1
|
||||
&& ACTION_STEP.toString().equals(parameters[0])) {
|
||||
Long step = new Long(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
startPolling();
|
||||
}
|
||||
updateState(CHANNEL_STEP, new DecimalType(step.longValue()));
|
||||
} else {
|
||||
logger.debug("Ignoring unexpected update for id {}", integrationId);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Encountered number format exception while handling update for greenmode {}", integrationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
stopPolling();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
/**
|
||||
* 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 java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
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.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;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with the main Lutron control hub.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added reconnect and heartbeat config parameters, moved discovery service registration to
|
||||
* LutronHandlerFactory
|
||||
*/
|
||||
public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
private static final Pattern RESPONSE_REGEX = Pattern
|
||||
.compile("~(OUTPUT|DEVICE|SYSTEM|TIMECLOCK|MODE|SYSVAR),([0-9\\.:/]+),([0-9,\\.:/]*)\\Z");
|
||||
|
||||
private static final String DB_UPDATE_DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";
|
||||
|
||||
private static final Integer MONITOR_PROMPT = 12;
|
||||
private static final Integer MONITOR_SYSVAR = 10;
|
||||
private static final Integer MONITOR_ENABLE = 1;
|
||||
private static final Integer MONITOR_DISABLE = 2;
|
||||
|
||||
private static final Integer SYSTEM_DBEXPORTDATETIME = 10;
|
||||
|
||||
private static final int MAX_LOGIN_ATTEMPTS = 2;
|
||||
|
||||
private static final String PROMPT_GNET = "GNET>";
|
||||
private static final String PROMPT_QNET = "QNET>";
|
||||
private static final String PROMPT_SAFE = "SAFE>";
|
||||
private static final String LOGIN_MATCH_REGEX = "(login:|[GQ]NET>|SAFE>)";
|
||||
|
||||
private static final String DEFAULT_USER = "lutron";
|
||||
private static final String DEFAULT_PASSWORD = "integration";
|
||||
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 final Logger logger = LoggerFactory.getLogger(IPBridgeHandler.class);
|
||||
|
||||
private IPBridgeConfig config;
|
||||
private int reconnectInterval;
|
||||
private int heartbeatInterval;
|
||||
private int sendDelay;
|
||||
|
||||
private TelnetSession session;
|
||||
private BlockingQueue<LutronCommand> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private Thread messageSender;
|
||||
private ScheduledFuture<?> keepAlive;
|
||||
private ScheduledFuture<?> keepAliveReconnect;
|
||||
private ScheduledFuture<?> connectRetryJob;
|
||||
|
||||
private Date lastDbUpdateDate;
|
||||
private LutronDeviceDiscoveryService discoveryService;
|
||||
|
||||
private final AtomicBoolean requireSysvarMonitoring = new AtomicBoolean(false);
|
||||
|
||||
public void setDiscoveryService(LutronDeviceDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
public class LutronSafemodeException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public LutronSafemodeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public IPBridgeConfig getIPBridgeConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public IPBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
|
||||
this.session = new TelnetSession();
|
||||
|
||||
this.session.addListener(new TelnetSessionListener() {
|
||||
@Override
|
||||
public void inputAvailable() {
|
||||
parseUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(IOException exception) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.config = getThing().getConfiguration().as(IPBridgeConfig.class);
|
||||
|
||||
if (validConfiguration(this.config)) {
|
||||
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;
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Connecting");
|
||||
scheduler.submit(this::connect); // start the async connect task
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validConfiguration(IPBridgeConfig config) {
|
||||
if (config == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge configuration missing");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(config.ipAddress)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge address not specified");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void scheduleConnectRetry(long waitMinutes) {
|
||||
logger.debug("Scheduling connection retry in {} minutes", waitMinutes);
|
||||
connectRetryJob = scheduler.schedule(this::connect, waitMinutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private synchronized void connect() {
|
||||
if (this.session.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Connecting to bridge at {}", config.ipAddress);
|
||||
|
||||
try {
|
||||
if (!login(config)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid username/password");
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (LutronSafemodeException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "main repeater is in safe mode");
|
||||
disconnect();
|
||||
scheduleConnectRetry(reconnectInterval); // Possibly a temporary problem. Try again later.
|
||||
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
disconnect();
|
||||
scheduleConnectRetry(reconnectInterval); // Possibly a temporary problem. Try again later.
|
||||
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "login interrupted");
|
||||
disconnect();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// Disable prompts
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_PROMPT,
|
||||
MONITOR_DISABLE));
|
||||
|
||||
if (requireSysvarMonitoring.get()) {
|
||||
setSysvarMonitoring(true);
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
messageSender = new Thread(this::sendCommandsThread, "Lutron sender");
|
||||
messageSender.start();
|
||||
|
||||
logger.debug("Starting keepAlive job with interval {}", heartbeatInterval);
|
||||
keepAlive = scheduler.scheduleWithFixedDelay(this::sendKeepAlive, heartbeatInterval, heartbeatInterval,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void sendCommandsThread() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
LutronCommand command = sendQueue.take();
|
||||
|
||||
logger.debug("Sending command {}", command);
|
||||
|
||||
try {
|
||||
session.writeLine(command.toString());
|
||||
} 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();
|
||||
|
||||
// reconnect() will start a new thread; terminate this one
|
||||
break;
|
||||
}
|
||||
if (sendDelay > 0) {
|
||||
Thread.sleep(sendDelay); // introduce delay to throttle send rate
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void disconnect() {
|
||||
logger.debug("Disconnecting from bridge");
|
||||
|
||||
if (connectRetryJob != null) {
|
||||
connectRetryJob.cancel(true);
|
||||
}
|
||||
|
||||
if (this.keepAlive != null) {
|
||||
this.keepAlive.cancel(true);
|
||||
}
|
||||
|
||||
if (this.keepAliveReconnect != null) {
|
||||
// This method can be called from the keepAliveReconnect thread. Make sure
|
||||
// we don't interrupt ourselves, as that may prevent the reconnection attempt.
|
||||
this.keepAliveReconnect.cancel(false);
|
||||
}
|
||||
|
||||
if (messageSender != null && messageSender.isAlive()) {
|
||||
messageSender.interrupt();
|
||||
}
|
||||
|
||||
try {
|
||||
this.session.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error disconnecting: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void reconnect() {
|
||||
logger.debug("Keepalive timeout, attempting to reconnect to the bridge");
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE);
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
|
||||
private boolean login(IPBridgeConfig config) throws IOException, InterruptedException, LutronSafemodeException {
|
||||
this.session.open(config.ipAddress);
|
||||
this.session.waitFor("login:");
|
||||
|
||||
// Sometimes the Lutron Smart Bridge Pro will request login more than once.
|
||||
for (int attempt = 0; attempt < MAX_LOGIN_ATTEMPTS; attempt++) {
|
||||
this.session.writeLine(config.user != null ? config.user : DEFAULT_USER);
|
||||
this.session.waitFor("password:");
|
||||
this.session.writeLine(config.password != null ? config.password : DEFAULT_PASSWORD);
|
||||
|
||||
MatchResult matchResult = this.session.waitFor(LOGIN_MATCH_REGEX);
|
||||
|
||||
if (PROMPT_GNET.equals(matchResult.group()) || PROMPT_QNET.equals(matchResult.group())) {
|
||||
return true;
|
||||
} else if (PROMPT_SAFE.equals(matchResult.group())) {
|
||||
logger.warn("Lutron repeater is in safe mode. Unable to connect.");
|
||||
throw new LutronSafemodeException("Lutron repeater in safe mode");
|
||||
}
|
||||
|
||||
else {
|
||||
logger.debug("got another login prompt, logging in again");
|
||||
// we already got the login prompt so go straight to sending user
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void sendCommand(LutronCommand command) {
|
||||
this.sendQueue.add(command);
|
||||
}
|
||||
|
||||
private LutronHandler findThingHandler(int integrationId) {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
if (thing.getHandler() instanceof LutronHandler) {
|
||||
LutronHandler handler = (LutronHandler) thing.getHandler();
|
||||
|
||||
try {
|
||||
if (handler != null && handler.getIntegrationId() == integrationId) {
|
||||
return handler;
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
logger.trace("Handler for id {} not initialized", integrationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseUpdates() {
|
||||
String paramString;
|
||||
String scrubbedLine;
|
||||
|
||||
for (String line : this.session.readLines()) {
|
||||
if (line.trim().equals("")) {
|
||||
// Sometimes we get an empty line (possibly only when prompts are disabled). Ignore them.
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("Received message {}", line);
|
||||
|
||||
// System is alive, cancel reconnect task.
|
||||
if (this.keepAliveReconnect != null) {
|
||||
this.keepAliveReconnect.cancel(true);
|
||||
}
|
||||
|
||||
Matcher matcher = RESPONSE_REGEX.matcher(line);
|
||||
boolean responseMatched = matcher.find();
|
||||
|
||||
if (!responseMatched) {
|
||||
// In some cases with Caseta a CLI prompt may be embedded within a received response line.
|
||||
if (line.contains("NET>")) {
|
||||
// Try to remove it and re-attempt the regex match.
|
||||
scrubbedLine = line.replaceAll("[GQ]NET> ", "");
|
||||
matcher = RESPONSE_REGEX.matcher(scrubbedLine);
|
||||
responseMatched = matcher.find();
|
||||
if (responseMatched) {
|
||||
line = scrubbedLine;
|
||||
logger.debug("Cleaned response line: {}", scrubbedLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!responseMatched) {
|
||||
logger.debug("Ignoring message {}", line);
|
||||
continue;
|
||||
} else {
|
||||
// We have a good response message
|
||||
LutronCommandType type = LutronCommandType.valueOf(matcher.group(1));
|
||||
|
||||
if (type == LutronCommandType.SYSTEM) {
|
||||
// SYSTEM messages are assumed to be a response to the SYSTEM_DBEXPORTDATETIME
|
||||
// query. The response returns the last time the device database was updated.
|
||||
setDbUpdateDate(matcher.group(2), matcher.group(3));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer integrationId;
|
||||
|
||||
try {
|
||||
integrationId = Integer.valueOf(matcher.group(2));
|
||||
} catch (NumberFormatException e1) {
|
||||
logger.warn("Integer conversion error parsing update: {}", line);
|
||||
continue;
|
||||
}
|
||||
paramString = matcher.group(3);
|
||||
|
||||
// Now dispatch update to the proper thing handler
|
||||
LutronHandler handler = findThingHandler(integrationId);
|
||||
|
||||
if (handler != null) {
|
||||
try {
|
||||
handler.handleUpdate(type, paramString.split(","));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number format exception parsing update: {}", line);
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception while processing update: {}", line, e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("No thing configured for integration ID {}", integrationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendKeepAlive() {
|
||||
logger.debug("Scheduling keepalive reconnect job");
|
||||
|
||||
// Reconnect if no response is received within 30 seconds.
|
||||
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));
|
||||
}
|
||||
|
||||
private void setDbUpdateDate(String dateString, String timeString) {
|
||||
try {
|
||||
Date date = new SimpleDateFormat(DB_UPDATE_DATE_FORMAT).parse(dateString + " " + timeString);
|
||||
|
||||
if (this.lastDbUpdateDate == null || date.after(this.lastDbUpdateDate)) {
|
||||
scanForDevices();
|
||||
|
||||
this.lastDbUpdateDate = date;
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
logger.warn("Failed to parse DB update date {} {}", dateString, timeString);
|
||||
}
|
||||
}
|
||||
|
||||
private void scanForDevices() {
|
||||
try {
|
||||
if (discoveryService != null) {
|
||||
logger.debug("Initiating discovery scan for devices");
|
||||
discoveryService.startScan(null);
|
||||
} else {
|
||||
logger.debug("Unable to initiate discovery because discoveryService is null");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error scanning for paired devices: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSysvarMonitoring(boolean enable) {
|
||||
Integer setting = (enable) ? MONITOR_ENABLE : MONITOR_DISABLE;
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_SYSVAR, setting));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||
// enable sysvar monitoring the first time a sysvar child thing initializes
|
||||
if (childHandler instanceof SysvarHandler) {
|
||||
if (requireSysvarMonitoring.compareAndSet(false, true)) {
|
||||
setSysvarMonitoring(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
IPBridgeConfig newConfig = thing.getConfiguration().as(IPBridgeConfig.class);
|
||||
boolean validConfig = validConfiguration(newConfig);
|
||||
boolean needsReconnect = validConfig && !this.config.sameConnectionParameters(newConfig);
|
||||
|
||||
if (!validConfig || needsReconnect) {
|
||||
dispose();
|
||||
}
|
||||
|
||||
this.thing = thing;
|
||||
this.config = newConfig;
|
||||
|
||||
if (needsReconnect) {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigIntlSeetouch;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron International seeTouch keypads used in
|
||||
* Homeworks QS systems
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntlKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IntlKeypadHandler.class);
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
String mod = model == null ? "Generic" : model;
|
||||
logger.debug("Configuring components for keypad model {}", model);
|
||||
|
||||
switch (mod) {
|
||||
case "2B":
|
||||
case "3B":
|
||||
case "4B":
|
||||
case "5BRL":
|
||||
case "6BRL":
|
||||
case "7BRL":
|
||||
case "8BRL":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
ledList = kp.getComponents(mod, ComponentType.LED);
|
||||
cciList = kp.getComponents(mod, ComponentType.CCI);
|
||||
break;
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming 10BRL model.", mod);
|
||||
// fall through
|
||||
case "Generic":
|
||||
case "10BRL":
|
||||
buttonList = kp.getComponents("10BRL", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("10BRL", ComponentType.LED);
|
||||
cciList = kp.getComponents("10BRL", ComponentType.CCI);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IntlKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
kp = new KeypadConfigIntlSeetouch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigSeetouch;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron seeTouch and Hybrid seeTouch keypads used in
|
||||
* RadioRA2 and Homeworks QS systems
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KeypadHandler.class);
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
String mod = model == null ? "Generic" : model;
|
||||
logger.debug("Configuring components for keypad model {}", model);
|
||||
|
||||
switch (mod) {
|
||||
case "W1RLD":
|
||||
case "H1RLD":
|
||||
case "HN1RLD":
|
||||
buttonList = kp.getComponents("W1RLD", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("W1RLD", ComponentType.LED);
|
||||
break;
|
||||
case "W2RLD":
|
||||
case "H2RLD":
|
||||
case "HN2RLD":
|
||||
buttonList = kp.getComponents("W2RLD", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("W2RLD", ComponentType.LED);
|
||||
break;
|
||||
case "W3S":
|
||||
case "H3S":
|
||||
case "HN3S":
|
||||
buttonList = kp.getComponents("W3S", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("W3S", ComponentType.LED);
|
||||
|
||||
break;
|
||||
case "W3BD":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
ledList = kp.getComponents(mod, ComponentType.LED);
|
||||
break;
|
||||
case "W3BRL":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
ledList = kp.getComponents(mod, ComponentType.LED);
|
||||
break;
|
||||
case "W3BSRL":
|
||||
case "H3BSRL":
|
||||
case "HN3BSRL":
|
||||
buttonList = kp.getComponents("W3BSRL", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("W3BSRL", ComponentType.LED);
|
||||
break;
|
||||
case "W4S":
|
||||
case "H4S":
|
||||
case "HN4S":
|
||||
buttonList = kp.getComponents("W4S", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("W4S", ComponentType.LED);
|
||||
break;
|
||||
case "W5BRL":
|
||||
case "H5BRL":
|
||||
case "HN5BRL":
|
||||
case "W5BRLIR":
|
||||
buttonList = kp.getComponents("W5BRL", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("W5BRL", ComponentType.LED);
|
||||
break;
|
||||
case "W6BRL":
|
||||
case "H6BRL":
|
||||
case "HN6BRL":
|
||||
buttonList = kp.getComponents("W6BRL", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("W6BRL", ComponentType.LED);
|
||||
break;
|
||||
case "W7B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
ledList = kp.getComponents(mod, ComponentType.LED);
|
||||
break;
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming Generic model.", mod);
|
||||
// fall through
|
||||
case "Generic":
|
||||
buttonList = kp.getComponents("Generic", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("Generic", ComponentType.LED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public KeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
kp = new KeypadConfigSeetouch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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.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.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
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
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LutronHandler extends BaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronHandler.class);
|
||||
|
||||
public LutronHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
public abstract int getIntegrationId();
|
||||
|
||||
public abstract void handleUpdate(LutronCommandType type, String... parameters);
|
||||
|
||||
/**
|
||||
* Queries for any device state needed at initialization time or after losing connectivity to the bridge, and
|
||||
* updates device status. Will be called when bridge status changes to ONLINE and thing has status
|
||||
* OFFLINE:BRIDGE_OFFLINE.
|
||||
*/
|
||||
protected abstract void initDeviceState();
|
||||
|
||||
/**
|
||||
* Called when changing thing status to offline. Subclasses may override to take any needed actions.
|
||||
*/
|
||||
protected void thingOfflineNotify() {
|
||||
}
|
||||
|
||||
protected @Nullable IPBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
|
||||
return bridge == null ? null : (IPBridgeHandler) bridge.getHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("Bridge status changed to {} for lutron device handler {}", bridgeStatusInfo.getStatus(),
|
||||
getIntegrationId());
|
||||
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
|
||||
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
||||
initDeviceState();
|
||||
|
||||
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
thingOfflineNotify();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCommand(LutronCommand command) {
|
||||
IPBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR, "No bridge associated");
|
||||
thingOfflineNotify();
|
||||
} else {
|
||||
bridgeHandler.sendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
protected void output(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.OUTPUT, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void device(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.DEVICE, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void timeclock(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.TIMECLOCK, getIntegrationId(),
|
||||
parameters));
|
||||
}
|
||||
|
||||
protected void greenMode(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MODE, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void sysvar(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SYSVAR, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void shadegrp(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SHADEGRP, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void queryOutput(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.OUTPUT, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void queryDevice(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.DEVICE, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void queryTimeclock(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.QUERY, LutronCommandType.TIMECLOCK, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void queryGreenMode(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.MODE, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* Subclass that configures CcoHandler for Maintained outputs.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MaintainedCcoHandler extends CcoHandler {
|
||||
|
||||
public MaintainedCcoHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.outputType = CcoOutputType.MAINTAINED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added initDeviceState method
|
||||
*/
|
||||
@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;
|
||||
|
||||
public OccupancySensorHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Number id = (Number) getThing().getConfiguration().get("integrationId");
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
|
||||
return;
|
||||
}
|
||||
integrationId = id.intValue();
|
||||
logger.debug("Initializing Occupancy Sensor handler for integration ID {}", id);
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Occupancy Sensor {}", getIntegrationId());
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE); // can't poll this device, so assume it is online if the bridge is
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
return this.integrationId;
|
||||
}
|
||||
|
||||
@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])) {
|
||||
updateState(CHANNEL_OCCUPANCYSTATUS, OnOffType.ON);
|
||||
} else if (STATE_UNOCCUPIED.equals(parameters[1])) {
|
||||
updateState(CHANNEL_OCCUPANCYSTATUS, OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigPalladiom;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron Palladiom keypads used in
|
||||
* Homeworks QS systems
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PalladiomKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PalladiomKeypadHandler.class);
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
String mod = model == null ? "Generic" : model;
|
||||
logger.debug("Configuring components for keypad model {}", model);
|
||||
|
||||
switch (mod) {
|
||||
case "2W":
|
||||
case "3W":
|
||||
case "4W":
|
||||
case "RW":
|
||||
case "22W":
|
||||
case "24W":
|
||||
case "42W":
|
||||
case "44W":
|
||||
case "2RW":
|
||||
case "4RW":
|
||||
case "RRW":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
ledList = kp.getComponents(mod, ComponentType.LED);
|
||||
cciList = kp.getComponents(mod, ComponentType.CCI);
|
||||
break;
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming Generic setting.", mod);
|
||||
// fall through
|
||||
case "Generic":
|
||||
buttonList = kp.getComponents("Generic", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("Generic", ComponentType.LED);
|
||||
cciList = kp.getComponents("Generic", ComponentType.CCI);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public PalladiomKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
kp = new KeypadConfigPalladiom();
|
||||
}
|
||||
}
|
||||
@@ -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.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigPico;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron Pico keypads
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PicoKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PicoKeypadHandler.class);
|
||||
|
||||
public PicoKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
kp = new KeypadConfigPico();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
String mod = model == null ? "Generic" : model;
|
||||
logger.debug("Configuring components for keypad model {}", mod);
|
||||
|
||||
switch (mod) {
|
||||
case "2B":
|
||||
case "2BRL":
|
||||
case "3B":
|
||||
case "4B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
break;
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming model 3BRL.", mod);
|
||||
// fall through
|
||||
case "Generic":
|
||||
case "3BRL":
|
||||
buttonList = kp.getComponents("3BRL", ComponentType.BUTTON);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* Subclass that configures CcoHandler for Pulsed outputs.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PulsedCcoHandler extends CcoHandler {
|
||||
|
||||
public PulsedCcoHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.outputType = CcoOutputType.PULSED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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 java.util.Arrays;
|
||||
|
||||
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.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron QS IO Interfaces
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QSIOHandler extends BaseKeypadHandler {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
CCI1(1, "cci1", "CCI 1", ComponentType.CCI),
|
||||
CCI2(2, "cci2", "CCI 2", ComponentType.CCI),
|
||||
CCI3(3, "cci3", "CCI 3", ComponentType.CCI),
|
||||
CCI4(4, "cci4", "CCI 4", ComponentType.CCI),
|
||||
CCI5(5, "cci5", "CCI 5", ComponentType.CCI);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(QSIOHandler.class);
|
||||
|
||||
@Override
|
||||
protected boolean isLed(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isButton(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCCI(int id) {
|
||||
return (id >= 1 && id <= 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
logger.debug("Configuring components for VCRX");
|
||||
|
||||
cciList.addAll(Arrays.asList(Component.CCI1, Component.CCI2, Component.CCI3, Component.CCI4, Component.CCI5));
|
||||
}
|
||||
|
||||
public QSIOHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
}
|
||||
@@ -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.handler;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_SHADELEVEL;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
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 with a Lutron Sivoia QS shade
|
||||
*
|
||||
* @author Bob Adair - Initial contribution based on Alan Tong's DimmerHandler
|
||||
*/
|
||||
@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;
|
||||
|
||||
public ShadeHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Number id = (Number) getThing().getConfiguration().get("integrationId");
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
|
||||
return;
|
||||
}
|
||||
integrationId = id.intValue();
|
||||
logger.debug("Initializing Shade handler for integration ID {}", id);
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Shade {}", 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(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) {
|
||||
// Refresh state when new item is linked.
|
||||
if (channelUID.getId().equals(CHANNEL_SHADELEVEL)) {
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_SHADELEVEL)) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_ZONELEVEL, level, 0);
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISING);
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERING);
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOP);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length >= 2) {
|
||||
if (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])
|
||||
&& PARAMETER_POSITION_UPDATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
BigDecimal level = new BigDecimal(parameters[2]);
|
||||
logger.trace("Shade {} received position update: {}", getIntegrationId(), level);
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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_SWITCH;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with a switch.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added initDeviceState method
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SwitchHandler extends LutronHandler {
|
||||
private static final Integer ACTION_ZONELEVEL = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SwitchHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
|
||||
public SwitchHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Number id = (Number) getThing().getConfiguration().get("integrationId");
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
|
||||
return;
|
||||
}
|
||||
integrationId = id.intValue();
|
||||
logger.debug("Initializing Switch handler for integration ID {}", id);
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Switch {}", 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(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
output(ACTION_ZONELEVEL, 100);
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
output(ACTION_ZONELEVEL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
return this.integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
postCommand(CHANNEL_SWITCH, level.compareTo(BigDecimal.ZERO) == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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_VARSTATE;
|
||||
|
||||
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.core.library.types.DecimalType;
|
||||
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 getting/setting sysvar state for HomeWorks QS
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*
|
||||
*/
|
||||
@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;
|
||||
private int integrationId;
|
||||
|
||||
public SysvarHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
SysvarConfig config = this.config;
|
||||
if (config != null) {
|
||||
return config.integrationId;
|
||||
} else {
|
||||
throw new IllegalStateException("handler not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
SysvarConfig config = getThing().getConfiguration().as(SysvarConfig.class);
|
||||
this.config = config;
|
||||
if (config.integrationId <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId configured");
|
||||
} else {
|
||||
integrationId = config.integrationId;
|
||||
logger.debug("Initializing Sysvar handler for integration ID {}", integrationId);
|
||||
initDeviceState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing handler state for sysvar id {}", integrationId);
|
||||
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");
|
||||
querySysvar(ACTION_GETSETSYSVAR); // 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_VARSTATE)) {
|
||||
// Refresh state when new item is linked.
|
||||
querySysvar(ACTION_GETSETSYSVAR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_VARSTATE)) {
|
||||
if (command instanceof Number) {
|
||||
int state = ((Number) command).intValue();
|
||||
sysvar(ACTION_GETSETSYSVAR, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.SYSVAR && parameters.length > 1
|
||||
&& ACTION_GETSETSYSVAR.toString().equals(parameters[0])) {
|
||||
BigDecimal state = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
updateState(CHANNEL_VARSTATE, new DecimalType(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfigTabletopSeetouch;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron Tabletop seeTouch keypads used in RadioRA2 and Homeworks QS systems
|
||||
* (e.g. RR-T5RL, RR-T10RL, RR-T15RL, etc.)
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TabletopKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TabletopKeypadHandler.class);
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
String mod = model == null ? "Generic" : model;
|
||||
logger.debug("Configuring components for keypad model {}", model);
|
||||
|
||||
switch (mod) {
|
||||
case "T5RL":
|
||||
case "T10RL":
|
||||
case "T15RL":
|
||||
case "T5CRL":
|
||||
case "T10CRL":
|
||||
case "T15CRL":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
ledList = kp.getComponents(mod, ComponentType.LED);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming model T15RL.", mod);
|
||||
// fall through
|
||||
case "Generic":
|
||||
buttonList = kp.getComponents("Generic", ComponentType.BUTTON);
|
||||
ledList = kp.getComponents("Generic", ComponentType.LED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public TabletopKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
kp = new KeypadConfigTabletopSeetouch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* 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.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
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.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
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 with the RA2 time clock.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@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;
|
||||
|
||||
public TimeclockHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Number id = (Number) getThing().getConfiguration().get("integrationId");
|
||||
logger.debug("Initializing timeclock handler");
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
|
||||
return;
|
||||
}
|
||||
integrationId = id.intValue();
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Timeclock {}", 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");
|
||||
queryTimeclock(ACTION_CLOCKMODE); // handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
logger.debug("Handling channel link request for timeclock {}", integrationId);
|
||||
if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
|
||||
queryTimeclock(ACTION_CLOCKMODE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
String channelID = channelUID.getId();
|
||||
logger.debug("Handling timeclock command {} on channel {}", command, channelID);
|
||||
|
||||
if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer mode = new Integer(((DecimalType) command).intValue());
|
||||
timeclock(ACTION_CLOCKMODE, mode);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryTimeclock(ACTION_CLOCKMODE);
|
||||
} else {
|
||||
logger.debug("Invalid command type for clockmode channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_EXECEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = new Integer(((DecimalType) command).intValue());
|
||||
timeclock(ACTION_EXECEVENT, index);
|
||||
} else {
|
||||
logger.debug("Invalid command type for execevent channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_SUNRISE)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryTimeclock(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);
|
||||
} else {
|
||||
logger.debug("Invalid command type for sunset channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_ENABLEEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = new Integer(((DecimalType) command).intValue());
|
||||
timeclock(ACTION_SETEVENT, index, EVENT_ENABLE);
|
||||
} else {
|
||||
logger.debug("Invalid command type for enableevent channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_DISABLEEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = new Integer(((DecimalType) command).intValue());
|
||||
timeclock(ACTION_SETEVENT, index, EVENT_DISABLE);
|
||||
} else {
|
||||
logger.debug("Invalid command type for disableevent channnel");
|
||||
}
|
||||
} else {
|
||||
logger.debug("Command received on invalid channel");
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Calendar parseLutronTime(final String timeString) {
|
||||
Integer hour, minute;
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
try {
|
||||
String hh = timeString.split(":", 2)[0];
|
||||
String mm = timeString.split(":", 2)[1];
|
||||
hour = Integer.parseInt(hh);
|
||||
minute = Integer.parseInt(mm);
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException exception) {
|
||||
logger.warn("Invaid time format received from timeclock {}", integrationId);
|
||||
return null;
|
||||
}
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||
calendar.set(Calendar.MINUTE, minute);
|
||||
return calendar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type != LutronCommandType.TIMECLOCK) {
|
||||
return;
|
||||
}
|
||||
logger.debug("Handling update received from timeclock {}", integrationId);
|
||||
|
||||
try {
|
||||
if (parameters.length >= 2 && ACTION_CLOCKMODE.toString().equals(parameters[0])) {
|
||||
Integer mode = new Integer(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])) {
|
||||
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])) {
|
||||
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])) {
|
||||
Integer index = new Integer(parameters[1]);
|
||||
updateState(CHANNEL_EXECEVENT, new DecimalType(index));
|
||||
|
||||
} else if (parameters.length >= 3 && ACTION_SETEVENT.toString().equals(parameters[0])) {
|
||||
Integer index = new Integer(parameters[1]);
|
||||
Integer state = new Integer(parameters[2]);
|
||||
if (state.equals(EVENT_ENABLE)) {
|
||||
updateState(CHANNEL_ENABLEEVENT, new DecimalType(index));
|
||||
} else if (state.equals(EVENT_DISABLE)) {
|
||||
updateState(CHANNEL_DISABLEEVENT, new DecimalType(index));
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Encountered number format exception while handling update for timeclock {}", integrationId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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 java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
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.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with Lutron VCRX visor control receiver
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VcrxHandler extends BaseKeypadHandler {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
BUTTON1(1, "button1", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2(2, "button2", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3(3, "button3", "Button 3", ComponentType.BUTTON),
|
||||
BUTTON4(4, "button4", "Button 4", ComponentType.BUTTON),
|
||||
BUTTON5(5, "button5", "Button 5", ComponentType.BUTTON),
|
||||
BUTTON6(6, "button6", "Button 6", ComponentType.BUTTON),
|
||||
|
||||
CCI1(30, "cci1", "CCI 1", ComponentType.CCI),
|
||||
CCI2(31, "cci2", "CCI 2", ComponentType.CCI),
|
||||
CCI3(32, "cci3", "CCI 3", ComponentType.CCI),
|
||||
CCI4(33, "cci4", "CCI 4", ComponentType.CCI),
|
||||
|
||||
LED1(81, "led1", "LED 1", ComponentType.LED),
|
||||
LED2(82, "led2", "LED 2", ComponentType.LED),
|
||||
LED3(83, "led3", "LED 3", ComponentType.LED),
|
||||
LED4(84, "led4", "LED 4", ComponentType.LED),
|
||||
LED5(85, "led5", "LED 5", ComponentType.LED),
|
||||
LED6(86, "led6", "LED 6", ComponentType.LED);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<Component> BUTTON_GROUP = Arrays.asList(Component.BUTTON1, Component.BUTTON2,
|
||||
Component.BUTTON3, Component.BUTTON4, Component.BUTTON5, Component.BUTTON6);
|
||||
|
||||
private static final List<Component> LED_GROUP = Arrays.asList(Component.LED1, Component.LED2, Component.LED3,
|
||||
Component.LED4, Component.LED5, Component.LED6);
|
||||
|
||||
private static final List<Component> CCI_GROUP = Arrays.asList(Component.CCI1, Component.CCI2, Component.CCI3,
|
||||
Component.CCI4);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VcrxHandler.class);
|
||||
|
||||
@Override
|
||||
protected boolean isLed(int id) {
|
||||
return (id >= 81 && id <= 86);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isButton(int id) {
|
||||
return (id >= 1 && id <= 6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCCI(int id) {
|
||||
return (id >= 30 && id <= 33);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
logger.debug("Configuring components for VCRX");
|
||||
|
||||
buttonList.addAll(BUTTON_GROUP);
|
||||
ledList.addAll(LED_GROUP);
|
||||
cciList.addAll(CCI_GROUP);
|
||||
}
|
||||
|
||||
public VcrxHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with the virtual buttons on the RadioRA2 main repeater
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VirtualKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
private static final String MODEL_OPTION_CASETA = "Caseta";
|
||||
private static final String MODEL_OPTION_OTHER = "Other";
|
||||
|
||||
private class Component implements KeypadComponent {
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VirtualKeypadHandler.class);
|
||||
|
||||
@Override
|
||||
protected boolean isLed(int id) {
|
||||
return (id >= 101 && id <= 200);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isButton(int id) {
|
||||
return (id >= 1 && id <= 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCCI(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
String mod = model == null ? MODEL_OPTION_OTHER : model;
|
||||
logger.debug("Configuring components for virtual keypad for model {}", mod);
|
||||
boolean caseta = mod.equalsIgnoreCase(MODEL_OPTION_CASETA);
|
||||
|
||||
for (int x = 1; x <= 100; x++) {
|
||||
buttonList.add(new Component(x, String.format("button%d", x), "Virtual Button", ComponentType.BUTTON));
|
||||
if (!caseta) { // Caseta scene buttons have no virtual LEDs
|
||||
ledList.add(new Component(x + 100, String.format("led%d", x), "Virtual LED", ComponentType.LED));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VirtualKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
// Mark all channels "Advanced" since most are unlikely to be used in any particular config
|
||||
advancedChannels = true;
|
||||
}
|
||||
}
|
||||
@@ -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.handler;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
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.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with the Lutron Wallbox Input Closure Interface (WCI)
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WciHandler extends BaseKeypadHandler {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
BUTTON1(1, "button1", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2(2, "button2", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3(3, "button3", "Button 3", ComponentType.BUTTON),
|
||||
BUTTON4(4, "button4", "Button 4", ComponentType.BUTTON),
|
||||
BUTTON5(5, "button5", "Button 5", ComponentType.BUTTON),
|
||||
BUTTON6(6, "button6", "Button 6", ComponentType.BUTTON),
|
||||
BUTTON7(7, "button7", "Button 7", ComponentType.BUTTON),
|
||||
BUTTON8(8, "button8", "Button 8", ComponentType.BUTTON),
|
||||
|
||||
LED1(81, "led1", "LED 1", ComponentType.LED),
|
||||
LED2(82, "led2", "LED 2", ComponentType.LED),
|
||||
LED3(83, "led3", "LED 3", ComponentType.LED),
|
||||
LED4(84, "led4", "LED 4", ComponentType.LED),
|
||||
LED5(85, "led5", "LED 5", ComponentType.LED),
|
||||
LED6(86, "led6", "LED 6", ComponentType.LED),
|
||||
LED7(87, "led7", "LED 7", ComponentType.LED),
|
||||
LED8(88, "led8", "LED 8", ComponentType.LED);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<KeypadComponent> BUTTON_LIST = Arrays.asList(Component.BUTTON1, Component.BUTTON2,
|
||||
Component.BUTTON3, Component.BUTTON4, Component.BUTTON5, Component.BUTTON6, Component.BUTTON7,
|
||||
Component.BUTTON8);
|
||||
|
||||
private static final List<KeypadComponent> LED_LIST = Arrays.asList(Component.LED1, Component.LED2, Component.LED3,
|
||||
Component.LED4, Component.LED5, Component.LED6, Component.LED7, Component.LED8);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WciHandler.class);
|
||||
|
||||
@Override
|
||||
protected boolean isLed(int id) {
|
||||
return (id >= 81 && id <= 88);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isButton(int id) {
|
||||
return (id >= 1 && id <= 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCCI(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureComponents(@Nullable String model) {
|
||||
logger.trace("Configuring components for WCI");
|
||||
|
||||
buttonList.addAll(BUTTON_LIST);
|
||||
ledList.addAll(LED_LIST);
|
||||
}
|
||||
|
||||
public WciHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.hw;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.BINDING_ID;
|
||||
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* Defines common constants, which are used across the whole binding.
|
||||
*
|
||||
* @author Andrew Shilliday - Initial contribution
|
||||
*/
|
||||
public class HwConstants {
|
||||
public static final ThingTypeUID THING_TYPE_HWSERIALBRIDGE = new ThingTypeUID(BINDING_ID, "hwserialbridge");
|
||||
public static final ThingTypeUID THING_TYPE_HWDIMMER = new ThingTypeUID(BINDING_ID, "hwdimmer");
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.hw;
|
||||
|
||||
/**
|
||||
* Configuration settings for a {@link org.openhab.binding.lutron.handler.HWDimmerHandler}.
|
||||
*
|
||||
* @author Andrew Shilliday - Initial contribution
|
||||
*/
|
||||
public class HwDimmerConfig {
|
||||
private static final int DEFAULT_FADE = 1;
|
||||
private static final int DEFAULT_LEVEL = 100;
|
||||
|
||||
private String address;
|
||||
private Integer fadeTime = DEFAULT_FADE;
|
||||
private Integer defaultLevel = DEFAULT_LEVEL;
|
||||
|
||||
public String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Integer getFadeTime() {
|
||||
return this.fadeTime;
|
||||
}
|
||||
|
||||
public void setFadeTime(Integer fadeTime) {
|
||||
this.fadeTime = fadeTime;
|
||||
}
|
||||
|
||||
public Integer getDefaultLevel() {
|
||||
return defaultLevel;
|
||||
}
|
||||
|
||||
public void setDefaultLevel(Integer defaultLevel) {
|
||||
this.defaultLevel = defaultLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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.hw;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_LIGHTLEVEL;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* This class extends the BaseThingHandler to support HomeWorks Dimmer modules.
|
||||
*
|
||||
* @author Andrew Shilliday - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class HwDimmerHandler extends BaseThingHandler {
|
||||
private String address;
|
||||
private Integer fadeTime = 1;
|
||||
private Integer defaultLevel = 100;
|
||||
|
||||
public HwDimmerHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
HwDimmerConfig config = getThing().getConfiguration().as(HwDimmerConfig.class);
|
||||
|
||||
address = config.getAddress();
|
||||
if (address == null || address.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Address not set");
|
||||
return;
|
||||
}
|
||||
|
||||
fadeTime = config.getFadeTime();
|
||||
defaultLevel = config.getDefaultLevel();
|
||||
|
||||
if (getThing().getBridgeUID() == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
queryLevel();
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
|
||||
if (command instanceof Number) {
|
||||
int level = ((Number) command).intValue();
|
||||
outputLevel(level);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
outputLevel(defaultLevel);
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
outputLevel(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HwSerialBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return null;
|
||||
} else if (!(bridge.getHandler() instanceof HwSerialBridgeHandler)) {
|
||||
return null;
|
||||
} else {
|
||||
return (HwSerialBridgeHandler) bridge.getHandler();
|
||||
}
|
||||
}
|
||||
|
||||
private void queryLevel() {
|
||||
HwSerialBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR, "No bridge associated");
|
||||
return;
|
||||
}
|
||||
|
||||
String cmd = String.format("RDL, %s", address);
|
||||
bridgeHandler.sendCommand(cmd);
|
||||
}
|
||||
|
||||
private void outputLevel(Number level) {
|
||||
HwSerialBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR, "No bridge associated");
|
||||
return;
|
||||
}
|
||||
|
||||
String cmd = String.format("FADEDIM, %s, %s, 0, %s", level, fadeTime, address);
|
||||
bridgeHandler.sendCommand(cmd);
|
||||
}
|
||||
|
||||
public void handleLevelChange(Integer level) {
|
||||
updateState(CHANNEL_LIGHTLEVEL, new PercentType(level));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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.hw;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.LutronHandlerFactory;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* The Discovery Service for Lutron HomeWorks processors. There is no great way to automatically
|
||||
* discover modules in the legacy HomeWorks processor (that I know of) so this service simply iterates
|
||||
* through possible addresses and asks for status on that address. If it's a valid module, the processor will return
|
||||
* with the dimmer status and it will be discovered.
|
||||
*
|
||||
* @author Andrew Shilliday - Initial contribution
|
||||
*/
|
||||
public class HwDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
|
||||
private Logger logger = LoggerFactory.getLogger(HwDiscoveryService.class);
|
||||
|
||||
private final AtomicBoolean isScanning = new AtomicBoolean(false);
|
||||
|
||||
private @NonNullByDefault({}) HwSerialBridgeHandler handler;
|
||||
|
||||
public HwDiscoveryService() {
|
||||
super(LutronHandlerFactory.HW_DISCOVERABLE_DEVICE_TYPES_UIDS, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof HwSerialBridgeHandler) {
|
||||
this.handler = (HwSerialBridgeHandler) handler;
|
||||
this.handler.setDiscoveryService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
super.activate(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
scheduler.submit(() -> {
|
||||
if (isScanning.compareAndSet(false, true)) {
|
||||
try {
|
||||
logger.debug("Starting scan for HW Dimmers");
|
||||
for (int m = 1; m <= 8; m++) { // Modules
|
||||
for (int o = 1; o <= 4; o++) { // Outputs
|
||||
String address = String.format("[01:01:00:%02d:%02d]", m, o);
|
||||
handler.sendCommand("RDL, " + address);
|
||||
Thread.sleep(5);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Scan interrupted");
|
||||
} finally {
|
||||
isScanning.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the bridge when it receives a status update for a dimmer that is not registered.
|
||||
*/
|
||||
public void declareUnknownDimmer(String address) {
|
||||
if (address == null) {
|
||||
logger.info("Discovered HomeWorks dimmer with no address");
|
||||
return;
|
||||
}
|
||||
String addressUid = address.replaceAll("[\\[\\]]", "").replaceAll(":", "-");
|
||||
ThingUID bridgeUID = this.handler.getThing().getUID();
|
||||
ThingUID uid = new ThingUID(HwConstants.THING_TYPE_HWDIMMER, bridgeUID, addressUid);
|
||||
|
||||
Map<String, Object> props = new HashMap<>();
|
||||
|
||||
props.put("address", address);
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperties(props)
|
||||
.withRepresentationProperty("address").build();
|
||||
|
||||
thingDiscovered(result);
|
||||
|
||||
logger.debug("Discovered {}", uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.hw;
|
||||
|
||||
/**
|
||||
* Configuration settings for an {@link org.openhab.binding.lutron.handler.HWSerialBridgeHandler}.
|
||||
*
|
||||
* @author Andrew Shilliday - Initial contribution
|
||||
*/
|
||||
public class HwSerialBridgeConfig {
|
||||
public static final String SERIAL_PORT = "serialPort";
|
||||
public static final String BAUD = "baudRate";
|
||||
public static final String UPDATE_TIME = "updateTime";
|
||||
public static final Integer DEFAULT_BAUD = 9600;
|
||||
|
||||
private String serialPort;
|
||||
private Integer baudRate = DEFAULT_BAUD;
|
||||
private Boolean updateTime;
|
||||
|
||||
public String getSerialPort() {
|
||||
return serialPort;
|
||||
}
|
||||
|
||||
public void setSerialPort(String serialPort) {
|
||||
this.serialPort = serialPort;
|
||||
}
|
||||
|
||||
public Integer getBaudRate() {
|
||||
return baudRate;
|
||||
}
|
||||
|
||||
public void setBaudRate(Integer baudRate) {
|
||||
this.baudRate = baudRate;
|
||||
}
|
||||
|
||||
public Boolean getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(Boolean updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* 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.hw;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEventListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
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.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* This is the main handler for HomeWorks RS232 Processors.
|
||||
*
|
||||
* @author Andrew Shilliday - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class HwSerialBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(HwSerialBridgeHandler.class);
|
||||
private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy");
|
||||
private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
private String serialPortName;
|
||||
private int baudRate;
|
||||
private Boolean updateTime;
|
||||
private ScheduledFuture<?> updateTimeJob;
|
||||
|
||||
private HwDiscoveryService discoveryService;
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
private SerialPort serialPort;
|
||||
private OutputStreamWriter serialOutput;
|
||||
private BufferedReader serialInput;
|
||||
|
||||
public HwSerialBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing the Lutron HomeWorks RS232 bridge handler");
|
||||
HwSerialBridgeConfig configuration = getConfigAs(HwSerialBridgeConfig.class);
|
||||
serialPortName = configuration.getSerialPort();
|
||||
updateTime = configuration.getUpdateTime();
|
||||
if (configuration.getBaudRate() == null) {
|
||||
baudRate = HwSerialBridgeConfig.DEFAULT_BAUD;
|
||||
} else {
|
||||
baudRate = configuration.getBaudRate().intValue();
|
||||
}
|
||||
|
||||
if (serialPortName == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port not specified");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Lutron HomeWorks RS232 Bridge Handler Initializing.");
|
||||
logger.debug(" Serial Port: {},", serialPortName);
|
||||
logger.debug(" Baud: {},", baudRate);
|
||||
|
||||
scheduler.execute(() -> openConnection());
|
||||
}
|
||||
|
||||
public void setDiscoveryService(HwDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
private void openConnection() {
|
||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
|
||||
if (portIdentifier == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Invalid port: " + serialPortName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("Connecting to Lutron HomeWorks Processor using {}.", serialPortName);
|
||||
serialPort = portIdentifier.open(this.getClass().getName(), 2000);
|
||||
|
||||
logger.debug("Connection established using {}. Configuring IO parameters. ", serialPortName);
|
||||
|
||||
int db = SerialPort.DATABITS_8, sb = SerialPort.STOPBITS_1, p = SerialPort.PARITY_NONE;
|
||||
serialPort.setSerialPortParams(baudRate, db, sb, p);
|
||||
serialPort.enableReceiveThreshold(1);
|
||||
serialPort.disableReceiveTimeout();
|
||||
serialOutput = new OutputStreamWriter(serialPort.getOutputStream(), "US-ASCII");
|
||||
serialInput = new BufferedReader(new InputStreamReader(serialPort.getInputStream(), "US-ASCII"));
|
||||
|
||||
serialPort.addEventListener(this);
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
|
||||
logger.debug("Sending monitoring commands.");
|
||||
sendCommand("PROMPTOFF");
|
||||
sendCommand("KBMOFF");
|
||||
sendCommand("KLMOFF");
|
||||
sendCommand("GSMOFF");
|
||||
sendCommand("DLMON"); // Turn on dimmer monitoring
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
if (updateTime) {
|
||||
startUpdateProcessorTimeJob();
|
||||
}
|
||||
} catch (PortInUseException portInUseException) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port in use: " + serialPortName);
|
||||
} catch (UnsupportedCommOperationException | IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication error");
|
||||
} catch (TooManyListenersException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Too many listeners to serial port.");
|
||||
}
|
||||
}
|
||||
|
||||
private void startUpdateProcessorTimeJob() {
|
||||
if (updateTimeJob != null) {
|
||||
logger.debug("Canceling old scheduled job");
|
||||
updateTimeJob.cancel(false);
|
||||
updateTimeJob = null;
|
||||
}
|
||||
|
||||
updateTimeJob = scheduler.scheduleWithFixedDelay(() -> updateProcessorTime(), 0, 1, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
private void updateProcessorTime() {
|
||||
LocalDate date = LocalDate.now();
|
||||
String dateString = date.format(dateFormat);
|
||||
String timeString = date.format(timeFormat);
|
||||
logger.debug("Updating HomeWorks processor date and time to {} {}", dateString, timeString);
|
||||
|
||||
if (!this.getBridge().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
logger.warn("HomeWorks Bridge is offline and cannot update time on HomeWorks processor.");
|
||||
if (updateTimeJob != null) {
|
||||
updateTimeJob.cancel(false);
|
||||
updateTimeJob = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sendCommand("SD, " + dateString);
|
||||
sendCommand("ST, " + timeString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(HwDiscoveryService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Unexpected command for HomeWorks Bridge: {} - {}", channelUID, command);
|
||||
}
|
||||
|
||||
private void handleIncomingMessage(String line) {
|
||||
if (line == null || line.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Received message from HomeWorks processor: {}", line);
|
||||
String[] data = line.replaceAll("\\s", "").toUpperCase().split(",");
|
||||
if ("DL".equals(data[0])) {
|
||||
try {
|
||||
String address = data[1];
|
||||
Integer level = Integer.parseInt(data[2]);
|
||||
HwDimmerHandler handler = findHandler(address);
|
||||
if (handler == null) {
|
||||
discoveryService.declareUnknownDimmer(address);
|
||||
} else {
|
||||
handler.handleLevelChange(level);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("Error parsing incoming message", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HwDimmerHandler findHandler(String address) {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
if (thing.getHandler() instanceof HwDimmerHandler) {
|
||||
HwDimmerHandler handler = (HwDimmerHandler) thing.getHandler();
|
||||
if (address.equals(handler.getAddress())) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives Serial Port Events and reads Serial Port Data.
|
||||
*
|
||||
* @param serialPortEvent
|
||||
*/
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent serialPortEvent) {
|
||||
if (serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
|
||||
try {
|
||||
while (true) {
|
||||
String messageLine = serialInput.readLine();
|
||||
if (messageLine == null) {
|
||||
break;
|
||||
}
|
||||
handleIncomingMessage(messageLine);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error reading from serial port: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error reading from port");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCommand(String command) {
|
||||
try {
|
||||
logger.debug("HomeWorks bridge sending command: {}", command);
|
||||
serialOutput.write(command + "\r");
|
||||
serialOutput.flush();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error writing to serial port: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error writing to port.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.info("HomeWorks bridge being disposed.");
|
||||
if (serialPort != null) {
|
||||
serialPort.close();
|
||||
}
|
||||
|
||||
serialPort = null;
|
||||
serialInput = null;
|
||||
serialOutput = null;
|
||||
|
||||
if (updateTimeJob != null) {
|
||||
updateTimeJob.cancel(false);
|
||||
}
|
||||
|
||||
logger.debug("Finished disposing bridge.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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.keypadconfig;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
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.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract base class for keypad configuration definition classes
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class KeypadConfig {
|
||||
private final Logger logger = LoggerFactory.getLogger(KeypadConfig.class);
|
||||
|
||||
protected final HashMap<String, @Nullable List<KeypadComponent>> modelData = new HashMap<>();
|
||||
|
||||
public abstract boolean isCCI(int id);
|
||||
|
||||
public abstract boolean isButton(int id);
|
||||
|
||||
public abstract boolean isLed(int id);
|
||||
|
||||
/**
|
||||
* Get a list of all {@link KeypadComponent}s for the specified keypad model
|
||||
*
|
||||
* @param model The keypad model for which to return components.
|
||||
* @return List of components. Will be empty if no components match.
|
||||
*/
|
||||
public List<KeypadComponent> getComponents(String model) {
|
||||
return getComponents(model, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of {@link KeypadComponent}s of the specified type for the specified keypad model
|
||||
*
|
||||
* @param model The keypad model for which to return components.
|
||||
* @param type The component type to include, or null for all components.
|
||||
* @return List of components. Will be empty if no components match.
|
||||
*/
|
||||
public List<KeypadComponent> getComponents(String model, @Nullable ComponentType type) {
|
||||
List<KeypadComponent> filteredList = new LinkedList<>();
|
||||
List<KeypadComponent> cList = modelData.get(model);
|
||||
if (cList == null) {
|
||||
logger.debug("Keypad components lookup using invalid keypad model: {}", model);
|
||||
return filteredList;
|
||||
} else if (type == null) {
|
||||
return cList;
|
||||
} else {
|
||||
for (KeypadComponent i : cList) {
|
||||
if (i.type() == type) {
|
||||
filteredList.add(i);
|
||||
}
|
||||
}
|
||||
return filteredList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all component IDs for the specified keypad model
|
||||
*
|
||||
* @param model The keypad model for which to return component IDs.
|
||||
* @return List of component IDs. Will be empty if no components match.
|
||||
*/
|
||||
public @Nullable List<Integer> getComponentIds(String model) {
|
||||
return getComponentIds(model, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of component IDs of the specified type for the specified keypad model
|
||||
*
|
||||
* @param model The keypad model for which to return component IDs.
|
||||
* @param type The component type to include, or null for all components.
|
||||
* @return List of component IDs. Will be empty if no components match.
|
||||
*/
|
||||
public List<Integer> getComponentIds(String model, @Nullable ComponentType type) {
|
||||
List<Integer> idList = new LinkedList<>();
|
||||
List<KeypadComponent> cList = modelData.get(model);
|
||||
if (cList == null) {
|
||||
logger.debug("Keypad component IDs lookup using invalid keypad model: {}", model);
|
||||
} else {
|
||||
for (KeypadComponent i : cList) {
|
||||
if (type == null || i.type() == type) {
|
||||
idList.add(i.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
return idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine keypad model from list of button component IDs
|
||||
*
|
||||
* @param buttonIds List of button component IDs for a keypad. Must be in ascending order.
|
||||
* @return String containing the keypad model, or null if no models match.
|
||||
*/
|
||||
public @Nullable String determineModelFromComponentIds(List<Integer> buttonIds) {
|
||||
for (String k : modelData.keySet()) {
|
||||
List<Integer> modelButtonIds = getComponentIds(k, ComponentType.BUTTON);
|
||||
Collections.sort(modelButtonIds); // make sure button IDs are in ascending order for comparison
|
||||
if (modelButtonIds.equals(buttonIds)) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility routine to concatenate multiple lists of {@link KeypadComponent}s
|
||||
*
|
||||
* @param lists Lists to concatenate
|
||||
* @return Concatenated list
|
||||
*/
|
||||
@SafeVarargs
|
||||
protected static final List<KeypadComponent> combinedList(final List<KeypadComponent>... lists) {
|
||||
List<KeypadComponent> newlist = new LinkedList<>();
|
||||
for (List<KeypadComponent> list : lists) {
|
||||
newlist.addAll(list);
|
||||
}
|
||||
return newlist;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
|
||||
/**
|
||||
* Keypad configuration definition for Tabletop seeTouch line
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigGrafikEye extends KeypadConfig {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
BUTTON1(70, "button1", "Button 1", ComponentType.BUTTON), // Scene button 1
|
||||
BUTTON2(71, "button2", "Button 2", ComponentType.BUTTON), // Scene button 2
|
||||
BUTTON3(76, "button3", "Button 3", ComponentType.BUTTON), // Scene button 3
|
||||
BUTTON4(77, "button4", "Button 4", ComponentType.BUTTON), // Scene button 4
|
||||
BUTTON5(83, "button5", "Button 5", ComponentType.BUTTON), // Scene button 5/Off
|
||||
|
||||
BUTTON10(38, "button10", "Button 10", ComponentType.BUTTON), // Col 1
|
||||
BUTTON11(39, "button11", "Button 11", ComponentType.BUTTON), // Col 1
|
||||
BUTTON12(40, "button12", "Button 12", ComponentType.BUTTON), // Col 1
|
||||
LOWER1(41, "buttonlower1", "Lower button col 1", ComponentType.BUTTON), // Col 1 lower
|
||||
RAISE1(47, "buttonraise1", "Raise button col 1", ComponentType.BUTTON), // Col 1 raise
|
||||
|
||||
BUTTON20(44, "button20", "Button 20", ComponentType.BUTTON), // Col 2
|
||||
BUTTON21(45, "button21", "Button 21", ComponentType.BUTTON), // Col 2
|
||||
BUTTON22(46, "button22", "Button 22", ComponentType.BUTTON), // Col 2
|
||||
LOWER2(52, "buttonlower2", "Lower button col 2", ComponentType.BUTTON), // Col 2 lower
|
||||
RAISE2(53, "buttonraise2", "Raise button col 2", ComponentType.BUTTON), // Col 2 raise
|
||||
|
||||
BUTTON30(50, "button30", "Button 30", ComponentType.BUTTON), // Col 3
|
||||
BUTTON31(51, "button31", "Button 31", ComponentType.BUTTON), // Col 3
|
||||
BUTTON32(56, "button32", "Button 32", ComponentType.BUTTON), // Col 3
|
||||
LOWER3(57, "buttonlower3", "Lower button col 3", ComponentType.BUTTON), // Col 3 lower
|
||||
RAISE3(58, "buttonraise3", "Raise button col 3", ComponentType.BUTTON), // Col 3 raise
|
||||
|
||||
CCI1(163, "cci1", "CCI 1", ComponentType.CCI),
|
||||
|
||||
LED1(201, "led1", "LED 1", ComponentType.LED), // Scene button LEDs
|
||||
LED2(210, "led2", "LED 2", ComponentType.LED),
|
||||
LED3(219, "led3", "LED 3", ComponentType.LED),
|
||||
LED4(228, "led4", "LED 4", ComponentType.LED),
|
||||
LED5(237, "led5", "LED 5", ComponentType.LED),
|
||||
|
||||
LED10(174, "led10", "LED 10", ComponentType.LED), // Col 1 LEDs
|
||||
LED11(175, "led11", "LED 11", ComponentType.LED),
|
||||
LED12(211, "led12", "LED 12", ComponentType.LED),
|
||||
|
||||
LED20(183, "led20", "LED 20", ComponentType.LED), // Col 2 LEDs
|
||||
LED21(184, "led21", "LED 21", ComponentType.LED),
|
||||
LED22(220, "led22", "LED 22", ComponentType.LED),
|
||||
|
||||
LED30(192, "led30", "LED 30", ComponentType.LED), // Col 3 LEDs
|
||||
LED31(193, "led31", "LED 31", ComponentType.LED),
|
||||
LED32(229, "led32", "LED 32", ComponentType.LED);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<KeypadComponent> SCENE_BUTTON_GROUP = Arrays.asList(Component.BUTTON1, Component.BUTTON2,
|
||||
Component.BUTTON3, Component.BUTTON4, Component.BUTTON5);
|
||||
private static final List<KeypadComponent> SCENE_LED_GROUP = Arrays.asList(Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED4, Component.LED5);
|
||||
|
||||
private static final List<KeypadComponent> CCI_GROUP = Arrays.asList(Component.CCI1);
|
||||
|
||||
private static final List<KeypadComponent> COL1_BUTTON_GROUP = Arrays.asList(Component.BUTTON10, Component.BUTTON11,
|
||||
Component.BUTTON12, Component.LOWER1, Component.RAISE1);
|
||||
private static final List<KeypadComponent> COL2_BUTTON_GROUP = Arrays.asList(Component.BUTTON20, Component.BUTTON21,
|
||||
Component.BUTTON22, Component.LOWER2, Component.RAISE2);
|
||||
private static final List<KeypadComponent> COL3_BUTTON_GROUP = Arrays.asList(Component.BUTTON30, Component.BUTTON31,
|
||||
Component.BUTTON32, Component.LOWER3, Component.RAISE3);
|
||||
|
||||
private static final List<KeypadComponent> COL1_LED_GROUP = Arrays.asList(Component.LED10, Component.LED11,
|
||||
Component.LED12);
|
||||
private static final List<KeypadComponent> COL2_LED_GROUP = Arrays.asList(Component.LED20, Component.LED21,
|
||||
Component.LED22);
|
||||
private static final List<KeypadComponent> COL3_LED_GROUP = Arrays.asList(Component.LED30, Component.LED31,
|
||||
Component.LED32);
|
||||
|
||||
@Override
|
||||
public boolean isLed(int id) {
|
||||
return (id >= 174 && id <= 237);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButton(int id) {
|
||||
return (id >= 38 && id <= 83);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCCI(int id) {
|
||||
return (id == 163);
|
||||
}
|
||||
|
||||
public KeypadConfigGrafikEye() {
|
||||
modelData.put("0COL", combinedList(SCENE_BUTTON_GROUP, CCI_GROUP, SCENE_LED_GROUP));
|
||||
|
||||
modelData.put("1COL",
|
||||
combinedList(SCENE_BUTTON_GROUP, COL1_BUTTON_GROUP, CCI_GROUP, SCENE_LED_GROUP, COL1_LED_GROUP));
|
||||
|
||||
modelData.put("2COL", combinedList(SCENE_BUTTON_GROUP, COL1_BUTTON_GROUP, COL2_BUTTON_GROUP, CCI_GROUP,
|
||||
SCENE_LED_GROUP, COL1_LED_GROUP, COL2_LED_GROUP));
|
||||
|
||||
modelData.put("3COL", combinedList(SCENE_BUTTON_GROUP, COL1_BUTTON_GROUP, COL2_BUTTON_GROUP, COL3_BUTTON_GROUP,
|
||||
CCI_GROUP, SCENE_LED_GROUP, COL1_LED_GROUP, COL2_LED_GROUP, COL3_LED_GROUP));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
|
||||
/**
|
||||
* Keypad configuration definition for International seeTouch line
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigIntlSeetouch extends KeypadConfig {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
BUTTON1(1, "button1", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2(2, "button2", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3(3, "button3", "Button 3", ComponentType.BUTTON),
|
||||
BUTTON4(4, "button4", "Button 4", ComponentType.BUTTON),
|
||||
BUTTON5(5, "button5", "Button 5", ComponentType.BUTTON),
|
||||
BUTTON6(6, "button6", "Button 6", ComponentType.BUTTON),
|
||||
BUTTON7(7, "button7", "Button 7", ComponentType.BUTTON),
|
||||
BUTTON8(8, "button8", "Button 8", ComponentType.BUTTON),
|
||||
BUTTON9(9, "button9", "Button 9", ComponentType.BUTTON),
|
||||
BUTTON10(10, "button10", "Button 10", ComponentType.BUTTON),
|
||||
|
||||
LOWER1(18, "buttonlower", "Lower button", ComponentType.BUTTON),
|
||||
RAISE1(19, "buttonraise", "Raise button", ComponentType.BUTTON),
|
||||
|
||||
CCI1(25, "cci1", "", ComponentType.CCI),
|
||||
CCI2(26, "cci2", "", ComponentType.CCI),
|
||||
|
||||
LED1(81, "led1", "LED 1", ComponentType.LED),
|
||||
LED2(82, "led2", "LED 2", ComponentType.LED),
|
||||
LED3(83, "led3", "LED 3", ComponentType.LED),
|
||||
LED4(84, "led4", "LED 4", ComponentType.LED),
|
||||
LED5(85, "led5", "LED 5", ComponentType.LED),
|
||||
LED6(86, "led6", "LED 6", ComponentType.LED),
|
||||
LED7(87, "led7", "LED 7", ComponentType.LED),
|
||||
LED8(88, "led8", "LED 8", ComponentType.LED),
|
||||
LED9(89, "led9", "LED 9", ComponentType.LED),
|
||||
LED10(90, "led10", "LED 10", ComponentType.LED);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLed(int id) {
|
||||
return (id >= 81 && id <= 90);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButton(int id) {
|
||||
return ((id >= 1 && id <= 10) || (id >= 18 && id <= 19));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCCI(int id) {
|
||||
return (id >= 25 && id <= 26);
|
||||
}
|
||||
|
||||
public KeypadConfigIntlSeetouch() {
|
||||
modelData.put("2B", Arrays.asList(Component.BUTTON7, Component.BUTTON9, Component.LED7, Component.LED9,
|
||||
Component.CCI1, Component.CCI2));
|
||||
|
||||
modelData.put("3B", Arrays.asList(Component.BUTTON6, Component.BUTTON8, Component.BUTTON10, Component.LED6,
|
||||
Component.LED8, Component.LED10, Component.CCI1, Component.CCI2));
|
||||
|
||||
modelData.put("4B", Arrays.asList(Component.BUTTON2, Component.BUTTON4, Component.BUTTON7, Component.BUTTON9,
|
||||
Component.LED2, Component.LED4, Component.LED7, Component.LED9, Component.CCI1, Component.CCI2));
|
||||
|
||||
modelData.put("5BRL",
|
||||
Arrays.asList(Component.BUTTON6, Component.BUTTON7, Component.BUTTON8, Component.BUTTON9,
|
||||
Component.BUTTON10, Component.LOWER1, Component.RAISE1, Component.LED6, Component.LED7,
|
||||
Component.LED8, Component.LED9, Component.LED10, Component.CCI1, Component.CCI2));
|
||||
|
||||
modelData.put("6BRL",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON3, Component.BUTTON5, Component.BUTTON6,
|
||||
Component.BUTTON8, Component.BUTTON10, Component.LOWER1, Component.RAISE1, Component.LED1,
|
||||
Component.LED3, Component.LED5, Component.LED6, Component.LED8, Component.LED10, Component.CCI1,
|
||||
Component.CCI2));
|
||||
|
||||
modelData.put("7BRL",
|
||||
Arrays.asList(Component.BUTTON2, Component.BUTTON4, Component.BUTTON6, Component.BUTTON7,
|
||||
Component.BUTTON8, Component.BUTTON9, Component.BUTTON10, Component.LOWER1, Component.RAISE1,
|
||||
Component.LED2, Component.LED4, Component.LED6, Component.LED7, Component.LED8, Component.LED9,
|
||||
Component.LED10, Component.CCI1, Component.CCI2));
|
||||
|
||||
modelData.put("8BRL", Arrays.asList(Component.BUTTON1, Component.BUTTON3, Component.BUTTON5, Component.BUTTON6,
|
||||
Component.BUTTON7, Component.BUTTON8, Component.BUTTON9, Component.BUTTON10, Component.LOWER1,
|
||||
Component.RAISE1, Component.LED1, Component.LED3, Component.LED5, Component.LED6, Component.LED7,
|
||||
Component.LED8, Component.LED9, Component.LED10, Component.CCI1, Component.CCI2));
|
||||
|
||||
modelData.put("10BRL",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON5, Component.BUTTON6, Component.BUTTON7, Component.BUTTON8, Component.BUTTON9,
|
||||
Component.BUTTON10, Component.LOWER1, Component.RAISE1, Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED4, Component.LED5, Component.LED6, Component.LED7, Component.LED8,
|
||||
Component.LED9, Component.LED10, Component.CCI1, Component.CCI2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 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.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
|
||||
/**
|
||||
* Keypad configuration definition for Palladiom keypad line
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigPalladiom extends KeypadConfig {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
BUTTON1(1, "button1", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2(2, "button2", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3(3, "button3", "Button 3", ComponentType.BUTTON),
|
||||
BUTTON4(4, "button4", "Button 4", ComponentType.BUTTON),
|
||||
BUTTON5(5, "button5", "Button 5", ComponentType.BUTTON),
|
||||
BUTTON6(6, "button6", "Button 6", ComponentType.BUTTON),
|
||||
BUTTON7(7, "button7", "Button 7", ComponentType.BUTTON),
|
||||
BUTTON8(8, "button8", "Button 8", ComponentType.BUTTON),
|
||||
|
||||
LOWER1(16, "buttonlower1", "Lower button 1", ComponentType.BUTTON),
|
||||
RAISE1(17, "buttonraise1", "Raise button 2", ComponentType.BUTTON),
|
||||
LOWER2(18, "buttonlower2", "Lower button 3", ComponentType.BUTTON),
|
||||
RAISE2(19, "buttonraise2", "Raise button 4", ComponentType.BUTTON),
|
||||
|
||||
LED1(81, "led1", "LED 1", ComponentType.LED),
|
||||
LED2(82, "led2", "LED 2", ComponentType.LED),
|
||||
LED3(83, "led3", "LED 3", ComponentType.LED),
|
||||
LED4(84, "led4", "LED 4", ComponentType.LED),
|
||||
LED5(85, "led5", "LED 5", ComponentType.LED),
|
||||
LED6(86, "led6", "LED 6", ComponentType.LED),
|
||||
LED7(87, "led7", "LED 7", ComponentType.LED),
|
||||
LED8(88, "led8", "LED 8", ComponentType.LED);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLed(int id) {
|
||||
return (id >= 81 && id <= 88);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButton(int id) {
|
||||
return ((id >= 1 && id <= 8) || (id >= 16 && id <= 19));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCCI(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeypadConfigPalladiom() {
|
||||
modelData.put("2W", Arrays.asList(Component.BUTTON1, Component.BUTTON4, Component.LED1, Component.LED4));
|
||||
|
||||
modelData.put("3W", Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON4, Component.LED1,
|
||||
Component.LED2, Component.LED4));
|
||||
|
||||
modelData.put("4W", Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.LED1, Component.LED2, Component.LED3, Component.LED4));
|
||||
|
||||
modelData.put("RW", Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.LOWER1,
|
||||
Component.RAISE1, Component.LED1, Component.LED2, Component.LED3));
|
||||
|
||||
modelData.put("22W", Arrays.asList(Component.BUTTON1, Component.BUTTON4, Component.BUTTON5, Component.BUTTON8,
|
||||
Component.LED1, Component.LED4, Component.LED5, Component.LED8));
|
||||
|
||||
modelData.put("24W",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON5, Component.BUTTON8, Component.LED1, Component.LED2, Component.LED3,
|
||||
Component.LED4, Component.LED5, Component.LED8));
|
||||
|
||||
modelData.put("42W",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON4, Component.BUTTON5, Component.BUTTON6,
|
||||
Component.BUTTON7, Component.BUTTON8, Component.LED1, Component.LED4, Component.LED5,
|
||||
Component.LED6, Component.LED7, Component.LED8));
|
||||
|
||||
modelData.put("44W",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON5, Component.BUTTON6, Component.BUTTON7, Component.BUTTON8, Component.LED1,
|
||||
Component.LED2, Component.LED3, Component.LED4, Component.LED5, Component.LED6, Component.LED7,
|
||||
Component.LED8));
|
||||
|
||||
modelData.put("2RW",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON5,
|
||||
Component.BUTTON8, Component.LOWER1, Component.RAISE1, Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED5, Component.LED8));
|
||||
|
||||
modelData.put("4RW",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON5,
|
||||
Component.BUTTON6, Component.BUTTON7, Component.BUTTON8, Component.LOWER1, Component.RAISE1,
|
||||
Component.LED1, Component.LED2, Component.LED3, Component.LED5, Component.LED6, Component.LED7,
|
||||
Component.LED8));
|
||||
|
||||
modelData.put("RRW",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON5,
|
||||
Component.BUTTON6, Component.BUTTON7, Component.LOWER1, Component.RAISE1, Component.LOWER2,
|
||||
Component.RAISE2, Component.LED1, Component.LED2, Component.LED3, Component.LED5,
|
||||
Component.LED6, Component.LED7));
|
||||
|
||||
// Superset of all models
|
||||
modelData.put("Generic", Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3,
|
||||
Component.BUTTON4, Component.BUTTON5, Component.BUTTON6, Component.BUTTON7, Component.BUTTON8,
|
||||
Component.LOWER1, Component.RAISE1, Component.LOWER2, Component.RAISE2, Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED4, Component.LED5, Component.LED6, Component.LED7, Component.LED8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
|
||||
/**
|
||||
* Keypad configuration definition for Pico models
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigPico extends KeypadConfig {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
// Buttons for 2B, 2BRL, 3B, and 3BRL models
|
||||
BUTTON1(2, "button1", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2(3, "button2", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3(4, "button3", "Button 3", ComponentType.BUTTON),
|
||||
|
||||
RAISE(5, "buttonraise", "Raise Button", ComponentType.BUTTON),
|
||||
LOWER(6, "buttonlower", "Lower Button", ComponentType.BUTTON),
|
||||
|
||||
// Buttons for PJ2-4B model
|
||||
BUTTON1_4B(8, "button01", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2_4B(9, "button02", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3_4B(10, "button03", "Button 3", ComponentType.BUTTON),
|
||||
BUTTON4_4B(11, "button04", "Button 4", ComponentType.BUTTON);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLed(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButton(int id) {
|
||||
return (id >= 2 && id <= 11);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCCI(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeypadConfigPico() {
|
||||
modelData.put("2B", Arrays.asList(Component.BUTTON1, Component.BUTTON3));
|
||||
modelData.put("2BRL", Arrays.asList(Component.BUTTON1, Component.BUTTON3, Component.RAISE, Component.LOWER));
|
||||
modelData.put("3B", Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3));
|
||||
modelData.put("3BRL", Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.RAISE,
|
||||
Component.LOWER));
|
||||
modelData.put("4B",
|
||||
Arrays.asList(Component.BUTTON1_4B, Component.BUTTON2_4B, Component.BUTTON3_4B, Component.BUTTON4_4B));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
|
||||
/**
|
||||
* Keypad configuration definition for seeTouch and Hybrid seeTouch
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigSeetouch extends KeypadConfig {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
BUTTON1(1, "button1", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2(2, "button2", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3(3, "button3", "Button 3", ComponentType.BUTTON),
|
||||
BUTTON4(4, "button4", "Button 4", ComponentType.BUTTON),
|
||||
BUTTON5(5, "button5", "Button 5", ComponentType.BUTTON),
|
||||
BUTTON6(6, "button6", "Button 6", ComponentType.BUTTON),
|
||||
BUTTON7(7, "button7", "Button 7", ComponentType.BUTTON),
|
||||
|
||||
LOWER1(16, "buttontoplower", "Top lower button", ComponentType.BUTTON),
|
||||
RAISE1(17, "buttontopraise", "Top raise button", ComponentType.BUTTON),
|
||||
LOWER2(18, "buttonbottomlower", "Bottom lower button", ComponentType.BUTTON),
|
||||
RAISE2(19, "buttonbottomraise", "Bottom raise button", ComponentType.BUTTON),
|
||||
|
||||
// CCI1(25, "cci1", "CCI 1", ComponentType.CCI), // listed in spec but currently unused in binding
|
||||
// CCI2(26, "cci2", "CCI 2", ComponentType.CCI), // listed in spec but currently unused in binding
|
||||
|
||||
LED1(81, "led1", "LED 1", ComponentType.LED),
|
||||
LED2(82, "led2", "LED 2", ComponentType.LED),
|
||||
LED3(83, "led3", "LED 3", ComponentType.LED),
|
||||
LED4(84, "led4", "LED 4", ComponentType.LED),
|
||||
LED5(85, "led5", "LED 5", ComponentType.LED),
|
||||
LED6(86, "led6", "LED 6", ComponentType.LED),
|
||||
LED7(87, "led7", "LED 7", ComponentType.LED);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLed(int id) {
|
||||
return (id >= 81 && id <= 87);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButton(int id) {
|
||||
return ((id >= 1 && id <= 7) || (id >= 16 && id <= 19));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCCI(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeypadConfigSeetouch() {
|
||||
modelData.put("W1RLD",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON5,
|
||||
Component.BUTTON6, Component.LOWER2, Component.RAISE2, Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED5, Component.LED6));
|
||||
|
||||
modelData.put("W2RLD",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON5, Component.BUTTON6,
|
||||
Component.LOWER1, Component.RAISE1, Component.LOWER2, Component.RAISE2, Component.LED1,
|
||||
Component.LED2, Component.LED5, Component.LED6));
|
||||
|
||||
modelData.put("W3S", Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON6,
|
||||
Component.LOWER2, Component.RAISE2, Component.LED1, Component.LED2, Component.LED3, Component.LED6));
|
||||
|
||||
modelData.put("W3BD",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON5,
|
||||
Component.BUTTON6, Component.BUTTON7, Component.LED1, Component.LED2, Component.LED3,
|
||||
Component.LED5, Component.LED6, Component.LED7));
|
||||
|
||||
modelData.put("W3BRL", Arrays.asList(Component.BUTTON2, Component.BUTTON3, Component.BUTTON4, Component.LOWER2,
|
||||
Component.RAISE2, Component.LED2, Component.LED3, Component.LED4));
|
||||
|
||||
modelData.put("W3BSRL", Arrays.asList(Component.BUTTON1, Component.BUTTON3, Component.BUTTON5, Component.LOWER2,
|
||||
Component.RAISE2, Component.LED1, Component.LED3, Component.LED5));
|
||||
|
||||
modelData.put("W4S",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON6, Component.LOWER2, Component.RAISE2, Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED4, Component.LED6));
|
||||
|
||||
modelData.put("W5BRL",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON5, Component.LOWER2, Component.RAISE2, Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED4, Component.LED5));
|
||||
|
||||
modelData.put("W6BRL",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON5, Component.BUTTON6, Component.LOWER2, Component.RAISE2, Component.LED1,
|
||||
Component.LED2, Component.LED3, Component.LED4, Component.LED5, Component.LED6));
|
||||
|
||||
modelData.put("W7B",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON5, Component.BUTTON6, Component.BUTTON7, Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED4, Component.LED5, Component.LED6, Component.LED7));
|
||||
|
||||
modelData.put("Generic",
|
||||
Arrays.asList(Component.BUTTON1, Component.BUTTON2, Component.BUTTON3, Component.BUTTON4,
|
||||
Component.BUTTON5, Component.BUTTON6, Component.BUTTON7, Component.LOWER1, Component.RAISE1,
|
||||
Component.LOWER2, Component.RAISE2, Component.LED1, Component.LED2, Component.LED3,
|
||||
Component.LED4, Component.LED5, Component.LED6, Component.LED7));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
|
||||
/**
|
||||
* Keypad configuration definition for Tabletop seeTouch line
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigTabletopSeetouch extends KeypadConfig {
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
BUTTON1(1, "button1", "Button 1", ComponentType.BUTTON),
|
||||
BUTTON2(2, "button2", "Button 2", ComponentType.BUTTON),
|
||||
BUTTON3(3, "button3", "Button 3", ComponentType.BUTTON),
|
||||
BUTTON4(4, "button4", "Button 4", ComponentType.BUTTON),
|
||||
BUTTON5(5, "button5", "Button 5", ComponentType.BUTTON),
|
||||
BUTTON6(6, "button6", "Button 6", ComponentType.BUTTON),
|
||||
BUTTON7(7, "button7", "Button 7", ComponentType.BUTTON),
|
||||
BUTTON8(8, "button8", "Button 8", ComponentType.BUTTON),
|
||||
BUTTON9(9, "button9", "Button 9", ComponentType.BUTTON),
|
||||
BUTTON10(10, "button10", "Button 10", ComponentType.BUTTON),
|
||||
BUTTON11(11, "button11", "Button 11", ComponentType.BUTTON),
|
||||
BUTTON12(12, "button12", "Button 12", ComponentType.BUTTON),
|
||||
BUTTON13(13, "button13", "Button 13", ComponentType.BUTTON),
|
||||
BUTTON14(14, "button14", "Button 14", ComponentType.BUTTON),
|
||||
BUTTON15(15, "button15", "Button 15", ComponentType.BUTTON),
|
||||
|
||||
BUTTON16(16, "button16", "Button 16", ComponentType.BUTTON),
|
||||
BUTTON17(17, "button17", "Button 17", ComponentType.BUTTON),
|
||||
|
||||
LOWER1(20, "buttonlower1", "Lower button 1", ComponentType.BUTTON),
|
||||
RAISE1(21, "buttonraise1", "Raise button 1", ComponentType.BUTTON),
|
||||
LOWER2(22, "buttonlower2", "Lower button 2", ComponentType.BUTTON),
|
||||
RAISE2(23, "buttonraise2", "Raise button 2", ComponentType.BUTTON),
|
||||
LOWER3(24, "buttonlower3", "Lower button 3", ComponentType.BUTTON),
|
||||
RAISE3(25, "buttonraise3", "Raise button 3", ComponentType.BUTTON),
|
||||
|
||||
LED1(81, "led1", "LED 1", ComponentType.LED),
|
||||
LED2(82, "led2", "LED 2", ComponentType.LED),
|
||||
LED3(83, "led3", "LED 3", ComponentType.LED),
|
||||
LED4(84, "led4", "LED 4", ComponentType.LED),
|
||||
LED5(85, "led5", "LED 5", ComponentType.LED),
|
||||
LED6(86, "led6", "LED 6", ComponentType.LED),
|
||||
LED7(87, "led7", "LED 7", ComponentType.LED),
|
||||
LED8(88, "led8", "LED 8", ComponentType.LED),
|
||||
LED9(89, "led9", "LED 9", ComponentType.LED),
|
||||
LED10(90, "led10", "LED 10", ComponentType.LED),
|
||||
LED11(91, "led11", "LED 11", ComponentType.LED),
|
||||
LED12(92, "led12", "LED 12", ComponentType.LED),
|
||||
LED13(93, "led13", "LED 13", ComponentType.LED),
|
||||
LED14(94, "led14", "LED 14", ComponentType.LED),
|
||||
LED15(95, "led15", "LED 15", ComponentType.LED),
|
||||
|
||||
LED16(96, "led16", "LED 16", ComponentType.LED),
|
||||
LED17(97, "led17", "LED 17", ComponentType.LED);
|
||||
|
||||
private final int id;
|
||||
private final String channel;
|
||||
private final String description;
|
||||
private final ComponentType type;
|
||||
|
||||
Component(int id, String channel, String description, ComponentType type) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentType type() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<KeypadComponent> BUTTON_GROUP1 = Arrays.asList(Component.BUTTON1, Component.BUTTON2,
|
||||
Component.BUTTON3, Component.BUTTON4, Component.BUTTON5);
|
||||
private static final List<KeypadComponent> BUTTON_GROUP2 = Arrays.asList(Component.BUTTON6, Component.BUTTON7,
|
||||
Component.BUTTON8, Component.BUTTON9, Component.BUTTON10);
|
||||
private static final List<KeypadComponent> BUTTON_GROUP3 = Arrays.asList(Component.BUTTON11, Component.BUTTON12,
|
||||
Component.BUTTON13, Component.BUTTON14, Component.BUTTON15);
|
||||
|
||||
private static final List<KeypadComponent> BUTTON_GROUPBOTTOM_RL = Arrays.asList(Component.BUTTON16,
|
||||
Component.BUTTON17, Component.LOWER3, Component.RAISE3);
|
||||
private static final List<KeypadComponent> BUTTON_GROUPBOTTOM_CRL = Arrays.asList(Component.LOWER1,
|
||||
Component.RAISE1, Component.LOWER2, Component.RAISE2, Component.LOWER3, Component.RAISE3);
|
||||
private static final List<KeypadComponent> BUTTON_GROUPBOTTOM_GENERIC = Arrays.asList(Component.BUTTON16,
|
||||
Component.BUTTON17, Component.LOWER1, Component.RAISE1, Component.LOWER2, Component.RAISE2,
|
||||
Component.LOWER3, Component.RAISE3);
|
||||
|
||||
private static final List<KeypadComponent> LED_GROUP1 = Arrays.asList(Component.LED1, Component.LED2,
|
||||
Component.LED3, Component.LED4, Component.LED5);
|
||||
private static final List<KeypadComponent> LED_GROUP2 = Arrays.asList(Component.LED6, Component.LED7,
|
||||
Component.LED8, Component.LED9, Component.LED10);
|
||||
private static final List<KeypadComponent> LED_GROUP3 = Arrays.asList(Component.LED11, Component.LED12,
|
||||
Component.LED13, Component.LED14, Component.LED15);
|
||||
|
||||
private static final List<KeypadComponent> LED_GROUPBOTTOM_RL = Arrays.asList(Component.LED16, Component.LED17);
|
||||
|
||||
@Override
|
||||
public boolean isLed(int id) {
|
||||
return (id >= 81 && id <= 97);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButton(int id) {
|
||||
return (id >= 1 && id <= 25);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCCI(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeypadConfigTabletopSeetouch() {
|
||||
modelData.put("T5RL", combinedList(BUTTON_GROUP1, BUTTON_GROUPBOTTOM_RL, LED_GROUP1, LED_GROUPBOTTOM_RL));
|
||||
|
||||
modelData.put("T10RL", combinedList(BUTTON_GROUP1, BUTTON_GROUP2, BUTTON_GROUPBOTTOM_RL, LED_GROUP1, LED_GROUP2,
|
||||
LED_GROUPBOTTOM_RL));
|
||||
|
||||
modelData.put("T15RL", combinedList(BUTTON_GROUP1, BUTTON_GROUP2, BUTTON_GROUP3, BUTTON_GROUPBOTTOM_RL,
|
||||
LED_GROUP1, LED_GROUP2, LED_GROUP3, LED_GROUPBOTTOM_RL));
|
||||
|
||||
modelData.put("T5CRL", combinedList(BUTTON_GROUP1, BUTTON_GROUPBOTTOM_CRL, LED_GROUP1));
|
||||
|
||||
modelData.put("T10CRL",
|
||||
combinedList(BUTTON_GROUP1, BUTTON_GROUP2, BUTTON_GROUPBOTTOM_CRL, LED_GROUP1, LED_GROUP2));
|
||||
|
||||
modelData.put("T15CRL", combinedList(BUTTON_GROUP1, BUTTON_GROUP2, BUTTON_GROUP3, BUTTON_GROUPBOTTOM_CRL,
|
||||
LED_GROUP1, LED_GROUP2, LED_GROUP3));
|
||||
|
||||
modelData.put("Generic", combinedList(BUTTON_GROUP1, BUTTON_GROUP2, BUTTON_GROUP3, BUTTON_GROUPBOTTOM_GENERIC,
|
||||
LED_GROUP1, LED_GROUP2, LED_GROUP3, LED_GROUPBOTTOM_RL)); // Superset of all models
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 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.net;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.net.telnet.InvalidTelnetOptionException;
|
||||
import org.apache.commons.net.telnet.SuppressGAOptionHandler;
|
||||
import org.apache.commons.net.telnet.TelnetClient;
|
||||
import org.apache.commons.net.telnet.TelnetInputListener;
|
||||
import org.apache.commons.net.telnet.TelnetOptionHandler;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A single telnet session.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Fix to readInput and added debug logging
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TelnetSession implements Closeable {
|
||||
|
||||
private static final int BUFSIZE = 8192;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TelnetSession.class);
|
||||
|
||||
private TelnetClient telnetClient;
|
||||
private @Nullable BufferedReader reader;
|
||||
private @Nullable PrintStream outstream;
|
||||
|
||||
private CharBuffer charBuffer;
|
||||
private List<TelnetSessionListener> listeners = new ArrayList<>();
|
||||
|
||||
private @Nullable TelnetOptionHandler suppressGAOptionHandler;
|
||||
|
||||
public TelnetSession() {
|
||||
logger.trace("Creating new TelnetSession");
|
||||
this.telnetClient = new TelnetClient();
|
||||
this.charBuffer = CharBuffer.allocate(BUFSIZE);
|
||||
|
||||
this.telnetClient.setReaderThread(true);
|
||||
this.telnetClient.registerInputListener(new TelnetInputListener() {
|
||||
@Override
|
||||
public void telnetInputAvailable() {
|
||||
try {
|
||||
readInput();
|
||||
} catch (IOException e) {
|
||||
notifyInputError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addListener(TelnetSessionListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public void clearListeners() {
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
private void notifyInputAvailable() {
|
||||
for (TelnetSessionListener listener : this.listeners) {
|
||||
listener.inputAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyInputError(IOException exception) {
|
||||
logger.debug("TelnetSession notifyInputError: {}", exception.getMessage());
|
||||
for (TelnetSessionListener listener : this.listeners) {
|
||||
listener.error(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public void open(String host) throws IOException {
|
||||
open(host, 23);
|
||||
}
|
||||
|
||||
public void open(String host, int port) throws IOException {
|
||||
// Synchronized block prevents listener thread from attempting to read input before we're ready.
|
||||
synchronized (this.charBuffer) {
|
||||
logger.trace("TelnetSession open called");
|
||||
try {
|
||||
telnetClient.connect(host, port);
|
||||
telnetClient.setKeepAlive(true);
|
||||
} catch (IOException e) {
|
||||
logger.debug("TelnetSession open: error connecting: {}", e.getMessage());
|
||||
throw (e);
|
||||
}
|
||||
|
||||
if (this.suppressGAOptionHandler == null) {
|
||||
// Only do this once.
|
||||
this.suppressGAOptionHandler = new SuppressGAOptionHandler(true, true, true, true);
|
||||
|
||||
try {
|
||||
this.telnetClient.addOptionHandler(this.suppressGAOptionHandler);
|
||||
} catch (InvalidTelnetOptionException e) {
|
||||
// Should never happen. Wrap it inside IOException so as not to declare another throwable.
|
||||
logger.debug("TelnetSession open: error adding telnet option handler: {}", e.getMessage());
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.reader = new BufferedReader(new InputStreamReader(this.telnetClient.getInputStream()));
|
||||
this.outstream = new PrintStream(this.telnetClient.getOutputStream());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
synchronized (charBuffer) {
|
||||
logger.trace("TelnetSession close called");
|
||||
try {
|
||||
if (telnetClient.isConnected()) {
|
||||
telnetClient.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("TelnetSession close: error disconnecting: {}", e.getMessage());
|
||||
throw (e);
|
||||
} finally {
|
||||
reader = null;
|
||||
outstream = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
synchronized (charBuffer) {
|
||||
return reader != null;
|
||||
}
|
||||
}
|
||||
|
||||
private void readInput() throws IOException {
|
||||
synchronized (charBuffer) {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.read(charBuffer);
|
||||
} catch (IOException e) {
|
||||
logger.debug("TelnetSession readInput: error reading: {}", e.getMessage());
|
||||
throw (e);
|
||||
}
|
||||
charBuffer.notifyAll();
|
||||
|
||||
if (charBuffer.position() > 0) {
|
||||
notifyInputAvailable();
|
||||
}
|
||||
} else {
|
||||
logger.debug("TelnetSession readInput: reader is null - session is closed");
|
||||
throw new IOException("Session is closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MatchResult waitFor(String prompt) throws InterruptedException {
|
||||
return waitFor(prompt, 0);
|
||||
}
|
||||
|
||||
public MatchResult waitFor(String prompt, long timeout) throws InterruptedException {
|
||||
Pattern regex = Pattern.compile(prompt);
|
||||
long startTime = timeout > 0 ? System.currentTimeMillis() : 0;
|
||||
|
||||
logger.trace("TelnetSession waitFor called with {} {}", prompt, timeout);
|
||||
synchronized (this.charBuffer) {
|
||||
this.charBuffer.flip();
|
||||
|
||||
String bufdata = this.charBuffer.toString();
|
||||
int n = bufdata.lastIndexOf('\n');
|
||||
String lastLine;
|
||||
|
||||
if (n != -1) {
|
||||
lastLine = bufdata.substring(n + 1);
|
||||
} else {
|
||||
lastLine = bufdata;
|
||||
}
|
||||
|
||||
Matcher matcher = regex.matcher(lastLine);
|
||||
|
||||
while (!matcher.find()) {
|
||||
long elapsed = timeout > 0 ? (System.currentTimeMillis() - startTime) : 0;
|
||||
|
||||
if (timeout > 0 && elapsed >= timeout) {
|
||||
break;
|
||||
}
|
||||
|
||||
this.charBuffer.clear();
|
||||
this.charBuffer.put(lastLine);
|
||||
|
||||
this.charBuffer.wait(timeout - elapsed);
|
||||
this.charBuffer.flip();
|
||||
|
||||
bufdata = this.charBuffer.toString();
|
||||
n = bufdata.lastIndexOf('\n');
|
||||
|
||||
if (n != -1) {
|
||||
lastLine = bufdata.substring(n + 1);
|
||||
} else {
|
||||
lastLine = bufdata;
|
||||
}
|
||||
|
||||
matcher = regex.matcher(lastLine);
|
||||
}
|
||||
|
||||
this.charBuffer.clear();
|
||||
|
||||
return matcher.toMatchResult();
|
||||
}
|
||||
}
|
||||
|
||||
public Iterable<String> readLines() {
|
||||
synchronized (this.charBuffer) {
|
||||
this.charBuffer.flip();
|
||||
|
||||
String bufdata = this.charBuffer.toString();
|
||||
int n = bufdata.lastIndexOf('\n');
|
||||
String leftover;
|
||||
String[] lines = null;
|
||||
|
||||
if (n != -1) {
|
||||
leftover = bufdata.substring(n + 1);
|
||||
bufdata = bufdata.substring(0, n).trim();
|
||||
|
||||
lines = bufdata.split("\r\n");
|
||||
} else {
|
||||
leftover = bufdata;
|
||||
}
|
||||
|
||||
this.charBuffer.clear();
|
||||
this.charBuffer.put(leftover);
|
||||
|
||||
return lines == null ? Collections.<String> emptyList() : Arrays.asList(lines);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLine(String line) throws IOException {
|
||||
synchronized (charBuffer) {
|
||||
logger.trace("TelnetSession writeLine called with {}", line);
|
||||
PrintStream out = outstream;
|
||||
if (out == null) {
|
||||
logger.debug("TelnetSession writeLine: outstream is null - session is closed");
|
||||
throw new IOException("Session is closed");
|
||||
}
|
||||
out.print(line + "\r\n");
|
||||
|
||||
if (out.checkError()) {
|
||||
logger.debug("TelnetSession writeLine: error writing to outstream");
|
||||
throw new IOException("Could not write to stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.net;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Listener for telnet session events.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface TelnetSessionListener {
|
||||
|
||||
void inputAvailable();
|
||||
|
||||
void error(IOException exception);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 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,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.protocol;
|
||||
|
||||
/**
|
||||
* Type of command in the Lutron integration protocol.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added additional commands
|
||||
*
|
||||
*/
|
||||
public enum LutronCommandType {
|
||||
AREA,
|
||||
DEVICE,
|
||||
GROUP,
|
||||
MODE,
|
||||
MONITORING,
|
||||
OUTPUT,
|
||||
SHADEGRP,
|
||||
SYSTEM,
|
||||
SYSVAR,
|
||||
TIMECLOCK,
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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.math.BigDecimal;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Holds time durations used by the Lutron protocols
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LutronDuration {
|
||||
public static final int MAX_SECONDS = 360000 - 1;
|
||||
public static final int MAX_HUNDREDTHS = 99;
|
||||
|
||||
private static final Pattern PATTERN_SS = Pattern.compile("^(\\d{1,2})$");
|
||||
private static final Pattern PATTERN_SSDEC = Pattern.compile("^(\\d{1,2})\\.(\\d{2})$");
|
||||
private static final Pattern PATTERN_MMSS = Pattern.compile("^(\\d{1,2}):(\\d{2})$");
|
||||
private static final Pattern PATTERN_HHMMSS = Pattern.compile("^(\\d{1,2}):(\\d{2}):(\\d{2})$");
|
||||
|
||||
public final Integer seconds;
|
||||
public final Integer hundredths;
|
||||
|
||||
/**
|
||||
* Constructor accepting duration in seconds
|
||||
*/
|
||||
public LutronDuration(Integer seconds) {
|
||||
if (seconds < 0 || seconds > MAX_SECONDS) {
|
||||
throw new IllegalArgumentException("Invalid duration");
|
||||
}
|
||||
this.seconds = seconds;
|
||||
this.hundredths = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor accepting duration in seconds and hundredths of seconds
|
||||
*/
|
||||
public LutronDuration(Integer seconds, Integer hundredths) {
|
||||
if (seconds < 0 || seconds > MAX_SECONDS || hundredths < 0 || hundredths > MAX_HUNDREDTHS) {
|
||||
throw new IllegalArgumentException("Invalid duration");
|
||||
}
|
||||
this.seconds = seconds;
|
||||
this.hundredths = hundredths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor accepting duration in seconds as a BigDecimal
|
||||
*/
|
||||
public LutronDuration(BigDecimal seconds) {
|
||||
if (seconds.compareTo(BigDecimal.ZERO) == -1 || seconds.compareTo(new BigDecimal(MAX_SECONDS)) == 1) {
|
||||
new IllegalArgumentException("Invalid duration");
|
||||
}
|
||||
this.seconds = seconds.intValue();
|
||||
BigDecimal fractional = seconds.subtract(new BigDecimal(seconds.intValue()));
|
||||
this.hundredths = fractional.movePointRight(2).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor accepting duration in seconds as a Double
|
||||
*/
|
||||
public LutronDuration(Double seconds) {
|
||||
this(new BigDecimal(seconds).setScale(2, BigDecimal.ROUND_HALF_UP));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor accepting duration string of the format: SS.ss, SS, MM:SS, or HH:MM:SS
|
||||
*/
|
||||
public LutronDuration(String duration) {
|
||||
Matcher matcherSS = PATTERN_SS.matcher(duration);
|
||||
if (matcherSS.find()) {
|
||||
Integer seconds = Integer.valueOf(matcherSS.group(1));
|
||||
this.seconds = seconds;
|
||||
this.hundredths = 0;
|
||||
return;
|
||||
}
|
||||
Matcher matcherSSDec = PATTERN_SSDEC.matcher(duration);
|
||||
if (matcherSSDec.find()) {
|
||||
this.seconds = Integer.valueOf(matcherSSDec.group(1));
|
||||
this.hundredths = Integer.valueOf(matcherSSDec.group(2));
|
||||
return;
|
||||
}
|
||||
Matcher matcherMMSS = PATTERN_MMSS.matcher(duration);
|
||||
if (matcherMMSS.find()) {
|
||||
Integer minutes = Integer.valueOf(matcherMMSS.group(1));
|
||||
Integer seconds = Integer.valueOf(matcherMMSS.group(2));
|
||||
this.seconds = minutes * 60 + seconds;
|
||||
this.hundredths = 0;
|
||||
return;
|
||||
}
|
||||
Matcher matcherHHMMSS = PATTERN_HHMMSS.matcher(duration);
|
||||
if (matcherHHMMSS.find()) {
|
||||
Integer hours = Integer.valueOf(matcherHHMMSS.group(1));
|
||||
Integer minutes = Integer.valueOf(matcherHHMMSS.group(2));
|
||||
Integer seconds = Integer.valueOf(matcherHHMMSS.group(3));
|
||||
this.seconds = hours * 60 * 60 + minutes * 60 + seconds;
|
||||
this.hundredths = 0;
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid duration");
|
||||
}
|
||||
|
||||
public String asLipString() {
|
||||
if (seconds < 100) {
|
||||
if (hundredths == 0) {
|
||||
return String.valueOf(seconds);
|
||||
} else {
|
||||
return String.format("%d.%02d", seconds, hundredths);
|
||||
}
|
||||
} else if (seconds < 3600) {
|
||||
return String.format("%d:%02d", seconds / 60, seconds % 60);
|
||||
} else {
|
||||
return String.format("%d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, (seconds % 60));
|
||||
}
|
||||
}
|
||||
|
||||
public String asLeapString() {
|
||||
return ""; // TBD
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asLipString();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* Requested operation of a command to the Lutron integration protocol.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*
|
||||
*/
|
||||
public enum LutronOperation {
|
||||
EXECUTE("#"),
|
||||
QUERY("?");
|
||||
|
||||
private final String operationChar;
|
||||
|
||||
LutronOperation(String operationChar) {
|
||||
this.operationChar = operationChar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.operationChar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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.radiora;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.TooManyListenersException;
|
||||
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEventListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* RS232 connection to the RadioRA Classic system.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class RS232Connection implements RadioRAConnection, SerialPortEventListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RS232Connection.class);
|
||||
|
||||
protected SerialPortManager serialPortManager;
|
||||
protected SerialPort serialPort;
|
||||
|
||||
protected BufferedReader inputReader;
|
||||
|
||||
protected RadioRAFeedbackListener listener;
|
||||
protected RS232MessageParser parser = new RS232MessageParser();
|
||||
|
||||
public RS232Connection(SerialPortManager serialPortManager) {
|
||||
super();
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(String portName, int baud) throws RadioRAConnectionException {
|
||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portName);
|
||||
if (portIdentifier == null) {
|
||||
throw new RadioRAConnectionException(String.format("Port not found", portName));
|
||||
}
|
||||
|
||||
try {
|
||||
serialPort = portIdentifier.open("openhab", 5000);
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
serialPort.setSerialPortParams(baud, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
|
||||
serialPort.addEventListener(this);
|
||||
inputReader = new BufferedReader(new InputStreamReader(serialPort.getInputStream()));
|
||||
} catch (PortInUseException e) {
|
||||
throw new RadioRAConnectionException(String.format("Port %s already in use", portIdentifier.getName()));
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
throw new RadioRAConnectionException("Error initializing - Failed to set serial port params");
|
||||
} catch (TooManyListenersException e) {
|
||||
throw new RadioRAConnectionException("Error initializing - Failed to add event listener");
|
||||
} catch (IOException e) {
|
||||
throw new RadioRAConnectionException("Error initializing - Failed to get input stream");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String command) {
|
||||
logger.debug("Writing to serial port: {}", command.toString());
|
||||
try {
|
||||
serialPort.getOutputStream().write(command.getBytes());
|
||||
} catch (IOException e) {
|
||||
logger.debug("An error occurred writing to serial port", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
serialPort.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent ev) {
|
||||
switch (ev.getEventType()) {
|
||||
case SerialPortEvent.DATA_AVAILABLE:
|
||||
try {
|
||||
if (!inputReader.ready()) {
|
||||
logger.debug("Serial Data Available but input reader not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
String message = inputReader.readLine();
|
||||
logger.debug("Msg Received: {}", message);
|
||||
RadioRAFeedback feedback = parser.parse(message);
|
||||
|
||||
if (feedback != null) {
|
||||
logger.debug("Msg Parsed as {}", feedback.getClass().getName());
|
||||
listener.handleRadioRAFeedback(feedback);
|
||||
}
|
||||
logger.debug("Finished handling feedback");
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException occurred", e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unhandled SerialPortEvent raised [{}]", ev.getEventType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener(RadioRAFeedbackListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.radiora;
|
||||
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.LEDMapFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.LocalZoneChangeFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.ZoneMapFeedback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class handles decoding message types from RadioRA
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class RS232MessageParser {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(RS232MessageParser.class);
|
||||
|
||||
public RadioRAFeedback parse(String msg) {
|
||||
String prefix = parsePrefix(msg);
|
||||
|
||||
switch (prefix) {
|
||||
case "LMP":
|
||||
return new LEDMapFeedback(msg);
|
||||
case "LZC":
|
||||
return new LocalZoneChangeFeedback(msg);
|
||||
case "ZMP":
|
||||
return new ZoneMapFeedback(msg);
|
||||
case "!":
|
||||
// No action to take when this message is received but handle
|
||||
// it to prevent the the default log statement from occurring.
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.debug("Unhandled msg received from RS232 [{}]", msg);
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String parsePrefix(String msg) {
|
||||
String[] arr = msg.split(",");
|
||||
if (arr.length < 1) {
|
||||
logger.debug("Unexpected msg received from RS232 [{}]", msg);
|
||||
return "";
|
||||
}
|
||||
|
||||
return arr[0];
|
||||
}
|
||||
}
|
||||
@@ -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.radiora;
|
||||
|
||||
/**
|
||||
* Interface to the RadioRA Classic system
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public interface RadioRAConnection {
|
||||
|
||||
public void open(String portName, int baud) throws RadioRAConnectionException;
|
||||
|
||||
public void disconnect();
|
||||
|
||||
public void write(String command);
|
||||
|
||||
public void setListener(RadioRAFeedbackListener listener);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.radiora;
|
||||
|
||||
/**
|
||||
* Thrown when an attempt to open a RadioRA Connection fails.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class RadioRAConnectionException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RadioRAConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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.radiora;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.LutronBindingConstants;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link RadioRAConstants} class defines common constants for RadioRA classic devices
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioRAConstants {
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_RS232 = new ThingTypeUID(LutronBindingConstants.BINDING_ID, "ra-rs232");
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(LutronBindingConstants.BINDING_ID,
|
||||
"ra-dimmer");
|
||||
public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(LutronBindingConstants.BINDING_ID,
|
||||
"ra-switch");
|
||||
public static final ThingTypeUID THING_TYPE_PHANTOM = new ThingTypeUID(LutronBindingConstants.BINDING_ID,
|
||||
"ra-phantomButton");
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.radiora;
|
||||
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
|
||||
/**
|
||||
* Interface for handling feedback messages from RadioRA system
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public interface RadioRAFeedbackListener {
|
||||
|
||||
void handleRadioRAFeedback(RadioRAFeedback feedback);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.radiora.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Configuration class for Dimmer type
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class DimmerConfig {
|
||||
private int zoneNumber;
|
||||
private BigDecimal fadeOutSec;
|
||||
private BigDecimal fadeInSec;
|
||||
|
||||
public int getZoneNumber() {
|
||||
return zoneNumber;
|
||||
}
|
||||
|
||||
public void setZoneNumber(int zoneNumber) {
|
||||
this.zoneNumber = zoneNumber;
|
||||
}
|
||||
|
||||
public BigDecimal getFadeOutSec() {
|
||||
return fadeOutSec;
|
||||
}
|
||||
|
||||
public void setFadeOutSec(BigDecimal fadeOutSec) {
|
||||
this.fadeOutSec = fadeOutSec;
|
||||
}
|
||||
|
||||
public BigDecimal getFadeInSec() {
|
||||
return fadeInSec;
|
||||
}
|
||||
|
||||
public void setFadeInSec(BigDecimal fadeInSec) {
|
||||
this.fadeInSec = fadeInSec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.radiora.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Configuration class for PhantomButton thing type.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class PhantomButtonConfig {
|
||||
|
||||
private int buttonNumber;
|
||||
private BigDecimal fadeSec;
|
||||
|
||||
public int getButtonNumber() {
|
||||
return buttonNumber;
|
||||
}
|
||||
|
||||
public void setButtonNumber(int buttonNumber) {
|
||||
this.buttonNumber = buttonNumber;
|
||||
}
|
||||
|
||||
public BigDecimal getFadeSec() {
|
||||
return fadeSec;
|
||||
}
|
||||
|
||||
public void setFadeSec(BigDecimal fadeSec) {
|
||||
this.fadeSec = fadeSec;
|
||||
}
|
||||
}
|
||||
@@ -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.radiora.config;
|
||||
|
||||
/**
|
||||
* Configuration class for RS232 thing type.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class RS232Config {
|
||||
|
||||
private String portName;
|
||||
private int baud = 9600;
|
||||
private int zoneMapQueryInterval = 60;
|
||||
|
||||
public String getPortName() {
|
||||
return portName;
|
||||
}
|
||||
|
||||
public void setPortName(String portName) {
|
||||
this.portName = portName;
|
||||
}
|
||||
|
||||
public int getBaud() {
|
||||
return baud;
|
||||
}
|
||||
|
||||
public void setBaud(int baud) {
|
||||
this.baud = baud;
|
||||
}
|
||||
|
||||
public int getZoneMapQueryInterval() {
|
||||
return zoneMapQueryInterval;
|
||||
}
|
||||
|
||||
public void setZoneMapQueryInterval(int zoneMapQueryInterval) {
|
||||
this.zoneMapQueryInterval = zoneMapQueryInterval;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.radiora.config;
|
||||
|
||||
/**
|
||||
* Configuration class for Switch thing type.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class SwitchConfig {
|
||||
|
||||
private int zoneNumber;
|
||||
|
||||
public int getZoneNumber() {
|
||||
return zoneNumber;
|
||||
}
|
||||
|
||||
public void setZoneNumber(int zoneNumber) {
|
||||
this.zoneNumber = zoneNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.radiora.handler;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.openhab.binding.lutron.internal.LutronBindingConstants;
|
||||
import org.openhab.binding.lutron.internal.radiora.config.DimmerConfig;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.LocalZoneChangeFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.SetDimmerLevelCommand;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.SetSwitchLevelCommand;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.ZoneMapFeedback;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* Handler for RadioRA dimmers
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class DimmerHandler extends LutronHandler {
|
||||
|
||||
/**
|
||||
* Used to internally keep track of dimmer level. This helps us better respond
|
||||
* to external dimmer changes since RadioRA protocol does not send dimmer
|
||||
* levels in their messages.
|
||||
*/
|
||||
private AtomicInteger lastKnownIntensity = new AtomicInteger(100);
|
||||
|
||||
private AtomicBoolean switchEnabled = new AtomicBoolean(false);
|
||||
|
||||
public DimmerHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
DimmerConfig config = getConfigAs(DimmerConfig.class);
|
||||
|
||||
if (LutronBindingConstants.CHANNEL_LIGHTLEVEL.equals(channelUID.getId())) {
|
||||
if (command instanceof PercentType) {
|
||||
int intensity = ((PercentType) command).intValue();
|
||||
|
||||
SetDimmerLevelCommand cmd = new SetDimmerLevelCommand(config.getZoneNumber(), intensity);
|
||||
getRS232Handler().sendCommand(cmd);
|
||||
|
||||
updateInternalState(intensity);
|
||||
}
|
||||
|
||||
if (command instanceof OnOffType) {
|
||||
OnOffType onOffCmd = (OnOffType) command;
|
||||
|
||||
SetSwitchLevelCommand cmd = new SetSwitchLevelCommand(config.getZoneNumber(), onOffCmd);
|
||||
getRS232Handler().sendCommand(cmd);
|
||||
|
||||
updateInternalState(onOffCmd);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ChannelUID channelUID, State newState) {
|
||||
if (LutronBindingConstants.CHANNEL_LIGHTLEVEL.equals(channelUID.getId())) {
|
||||
PercentType percent = (PercentType) newState.as(PercentType.class);
|
||||
updateInternalState(percent.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFeedback(RadioRAFeedback feedback) {
|
||||
if (feedback instanceof LocalZoneChangeFeedback) {
|
||||
handleLocalZoneChangeFeedback((LocalZoneChangeFeedback) feedback);
|
||||
} else if (feedback instanceof ZoneMapFeedback) {
|
||||
handleZoneMapFeedback((ZoneMapFeedback) feedback);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleZoneMapFeedback(ZoneMapFeedback feedback) {
|
||||
char value = feedback.getZoneValue(getConfigAs(DimmerConfig.class).getZoneNumber());
|
||||
if (value == '1') {
|
||||
turnDimmerOnToLastKnownIntensity();
|
||||
} else if (value == '0') {
|
||||
turnDimmerOff();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocalZoneChangeFeedback(LocalZoneChangeFeedback feedback) {
|
||||
if (feedback.getZoneNumber() == getConfigAs(DimmerConfig.class).getZoneNumber()) {
|
||||
if (LocalZoneChangeFeedback.State.ON.equals(feedback.getState())) {
|
||||
turnDimmerOnToLastKnownIntensity();
|
||||
} else if (LocalZoneChangeFeedback.State.OFF.equals(feedback.getState())) {
|
||||
turnDimmerOff();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void turnDimmerOnToLastKnownIntensity() {
|
||||
if (!switchEnabled.get()) {
|
||||
updateState(LutronBindingConstants.CHANNEL_LIGHTLEVEL, new PercentType(lastKnownIntensity.get()));
|
||||
}
|
||||
switchEnabled.set(true);
|
||||
}
|
||||
|
||||
private void turnDimmerOff() {
|
||||
updateState(LutronBindingConstants.CHANNEL_LIGHTLEVEL, PercentType.ZERO);
|
||||
switchEnabled.set(false);
|
||||
}
|
||||
|
||||
private void updateInternalState(int intensity) {
|
||||
if (intensity > 0) {
|
||||
lastKnownIntensity.set(intensity);
|
||||
}
|
||||
switchEnabled.set(intensity > 0);
|
||||
}
|
||||
|
||||
private void updateInternalState(OnOffType type) {
|
||||
switchEnabled.set(OnOffType.ON.equals(type));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.radiora.handler;
|
||||
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
|
||||
/**
|
||||
* Base class for non bridge handlers for Lutron RadioRA devices
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public abstract class LutronHandler extends BaseThingHandler {
|
||||
|
||||
public LutronHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
public RS232Handler getRS232Handler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Unable to get bridge");
|
||||
return null;
|
||||
}
|
||||
ThingHandler th = bridge.getHandler();
|
||||
if (th instanceof RS232Handler) {
|
||||
return (RS232Handler) th;
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge not properly configured.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void handleFeedback(RadioRAFeedback feedback);
|
||||
}
|
||||
@@ -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.radiora.handler;
|
||||
|
||||
import org.openhab.binding.lutron.internal.LutronBindingConstants;
|
||||
import org.openhab.binding.lutron.internal.radiora.config.PhantomButtonConfig;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.ButtonPressCommand;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.LEDMapFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Handler for RadioRA Phantom buttons
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class PhantomButtonHandler extends LutronHandler {
|
||||
|
||||
public PhantomButtonHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(LutronBindingConstants.CHANNEL_SWITCH)) {
|
||||
if (command instanceof OnOffType) {
|
||||
ButtonPressCommand cmd = new ButtonPressCommand(
|
||||
getConfigAs(PhantomButtonConfig.class).getButtonNumber(),
|
||||
ButtonPressCommand.ButtonState.valueOf(command.toString()));
|
||||
getRS232Handler().sendCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFeedback(RadioRAFeedback feedback) {
|
||||
if (feedback instanceof LEDMapFeedback) {
|
||||
handleLEDMapFeedback((LEDMapFeedback) feedback);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLEDMapFeedback(LEDMapFeedback feedback) {
|
||||
boolean zoneEnabled = feedback.getZoneValue(getConfigAs(PhantomButtonConfig.class).getButtonNumber()) == '1';
|
||||
|
||||
updateState(LutronBindingConstants.CHANNEL_SWITCH, zoneEnabled ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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.radiora.handler;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.lutron.internal.radiora.RS232Connection;
|
||||
import org.openhab.binding.lutron.internal.radiora.RadioRAConnection;
|
||||
import org.openhab.binding.lutron.internal.radiora.RadioRAConnectionException;
|
||||
import org.openhab.binding.lutron.internal.radiora.RadioRAFeedbackListener;
|
||||
import org.openhab.binding.lutron.internal.radiora.config.RS232Config;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRACommand;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.ZoneMapInquiryCommand;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
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;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RS232Handler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial contribution
|
||||
*/
|
||||
public class RS232Handler extends BaseBridgeHandler implements RadioRAFeedbackListener {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(RS232Handler.class);
|
||||
|
||||
private RadioRAConnection connection;
|
||||
|
||||
private ScheduledFuture<?> zoneMapScheduledTask;
|
||||
|
||||
public RS232Handler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
|
||||
this.connection = new RS232Connection(serialPortManager);
|
||||
this.connection.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (zoneMapScheduledTask != null) {
|
||||
zoneMapScheduledTask.cancel(true);
|
||||
}
|
||||
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
connectToRS232();
|
||||
|
||||
scheduleZoneMapQuery();
|
||||
}
|
||||
|
||||
protected void connectToRS232() {
|
||||
RS232Config config = getConfigAs(RS232Config.class);
|
||||
String portName = config.getPortName();
|
||||
int baud = config.getBaud();
|
||||
|
||||
logger.debug("Attempting to connect to RS232 on port {}", portName);
|
||||
|
||||
try {
|
||||
connection.open(portName, baud);
|
||||
} catch (RadioRAConnectionException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Connected successfully");
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
protected void scheduleZoneMapQuery() {
|
||||
RS232Config config = getConfigAs(RS232Config.class);
|
||||
logger.debug("Scheduling zone map query at {} second inverval", config.getZoneMapQueryInterval());
|
||||
|
||||
Runnable task = () -> sendCommand(new ZoneMapInquiryCommand());
|
||||
|
||||
zoneMapScheduledTask = this.scheduler.scheduleWithFixedDelay(task, 3, config.getZoneMapQueryInterval(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void sendCommand(RadioRACommand command) {
|
||||
connection.write(command.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRadioRAFeedback(RadioRAFeedback feedback) {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
ThingHandler handler = thing.getHandler();
|
||||
if (handler instanceof LutronHandler) {
|
||||
((LutronHandler) handler).handleFeedback(feedback);
|
||||
} else {
|
||||
logger.debug("Unexpected - Thing {} is not a LutronHandler", thing.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.radiora.handler;
|
||||
|
||||
import org.openhab.binding.lutron.internal.LutronBindingConstants;
|
||||
import org.openhab.binding.lutron.internal.radiora.config.SwitchConfig;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.LocalZoneChangeFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.RadioRAFeedback;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.SetSwitchLevelCommand;
|
||||
import org.openhab.binding.lutron.internal.radiora.protocol.ZoneMapFeedback;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler for RadioRA switches
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class SwitchHandler extends LutronHandler {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(SwitchHandler.class);
|
||||
|
||||
public SwitchHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (LutronBindingConstants.CHANNEL_SWITCH.equals(channelUID.getId())) {
|
||||
if (command instanceof OnOffType) {
|
||||
SetSwitchLevelCommand cmd = new SetSwitchLevelCommand(getConfigAs(SwitchConfig.class).getZoneNumber(),
|
||||
(OnOffType) command);
|
||||
|
||||
getRS232Handler().sendCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFeedback(RadioRAFeedback feedback) {
|
||||
if (feedback instanceof LocalZoneChangeFeedback) {
|
||||
handleLocalZoneChangeFeedback((LocalZoneChangeFeedback) feedback);
|
||||
} else if (feedback instanceof ZoneMapFeedback) {
|
||||
handleZoneMapFeedback((ZoneMapFeedback) feedback);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleZoneMapFeedback(ZoneMapFeedback feedback) {
|
||||
char value = feedback.getZoneValue(getConfigAs(SwitchConfig.class).getZoneNumber());
|
||||
|
||||
if (value == '1') {
|
||||
updateState(LutronBindingConstants.CHANNEL_SWITCH, OnOffType.ON);
|
||||
} else if (value == '0') {
|
||||
updateState(LutronBindingConstants.CHANNEL_SWITCH, OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocalZoneChangeFeedback(LocalZoneChangeFeedback feedback) {
|
||||
if (feedback.getZoneNumber() == getConfigAs(SwitchConfig.class).getZoneNumber()) {
|
||||
if (LocalZoneChangeFeedback.State.CHG.equals(feedback.getState())) {
|
||||
logger.debug("Not Implemented Yet - CHG state received from Local Zone Change Feedback.");
|
||||
}
|
||||
|
||||
updateState(LutronBindingConstants.CHANNEL_SWITCH, OnOffType.valueOf(feedback.getState().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.radiora.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Button Press (BP) Command.
|
||||
* Trigger a Phantom Button Press on the RadioRA Serial Device.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class ButtonPressCommand extends RadioRACommand {
|
||||
|
||||
public enum ButtonState {
|
||||
OFF,
|
||||
ON,
|
||||
TOG
|
||||
}
|
||||
|
||||
private int buttonNumber; // 1 to 15, 16 ALL ON, 17 ALL OFF
|
||||
private ButtonState state; // ON/OFF/TOG
|
||||
private Integer fadeSec; // 0 to 240 (optional)
|
||||
|
||||
public ButtonPressCommand(int buttonNumber, ButtonState state) {
|
||||
this.buttonNumber = buttonNumber;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setFadeSeconds(int seconds) {
|
||||
this.fadeSec = seconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return "BP";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getArgs() {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(String.valueOf(buttonNumber));
|
||||
args.add(String.valueOf(state));
|
||||
|
||||
if (fadeSec != null) {
|
||||
args.add(String.valueOf(fadeSec));
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -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.radiora.protocol;
|
||||
|
||||
/**
|
||||
* Feedback (LMP) that gives the state of all phantom LEDs
|
||||
* <p>
|
||||
* <b>Syntax:</b>
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* LMP,<LED States>
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <b>Example:</b>
|
||||
* <p>
|
||||
* Phantom LEDs 1 and 5 are ON, all others are OFF
|
||||
*
|
||||
* <pre>
|
||||
* LMP,100010000000000
|
||||
* </pre>
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class LEDMapFeedback extends RadioRAFeedback {
|
||||
|
||||
private String bitmap; // 15 bit String of (0,1). 1 is ON, 0 is OFF
|
||||
|
||||
public LEDMapFeedback(String msg) {
|
||||
String[] params = parse(msg, 1);
|
||||
|
||||
bitmap = params[1];
|
||||
}
|
||||
|
||||
public String getBitmap() {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public char getZoneValue(int zone) {
|
||||
if (zone < 1 || zone > bitmap.length()) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
return bitmap.charAt(zone - 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.radiora.protocol;
|
||||
|
||||
/**
|
||||
* Feedback for when a device was changed locally (not through Master Control)
|
||||
* <p>
|
||||
* <b>Syntax:</b>
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* LZC,<Zone Number>,<State>
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <b>Examples:</b>
|
||||
* <p>
|
||||
* Dimmer 1 changed from 100% to 50%
|
||||
*
|
||||
* <pre>
|
||||
* LZC,01,CHG
|
||||
* </pre>
|
||||
*
|
||||
* Dimmer 4 changed from OFF to 25%
|
||||
*
|
||||
* <pre>
|
||||
* LZC,04,ON
|
||||
* </pre>
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class LocalZoneChangeFeedback extends RadioRAFeedback {
|
||||
|
||||
private int zoneNumber; // 1 to 32
|
||||
private State state; // ON, OFF, CHG
|
||||
|
||||
public enum State {
|
||||
ON,
|
||||
OFF,
|
||||
CHG
|
||||
}
|
||||
|
||||
public LocalZoneChangeFeedback(String msg) {
|
||||
String[] params = parse(msg, 2);
|
||||
|
||||
zoneNumber = Integer.parseInt(params[1].trim());
|
||||
state = State.valueOf(params[2].trim().toUpperCase());
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public int getZoneNumber() {
|
||||
return zoneNumber;
|
||||
}
|
||||
}
|
||||
@@ -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.radiora.protocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base class for commands.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public abstract class RadioRACommand {
|
||||
|
||||
protected static final String FIELD_SEPARATOR = ",";
|
||||
protected static final String CMD_TERMINATOR = "\r";
|
||||
|
||||
public abstract String getCommand();
|
||||
|
||||
public abstract List<String> getArgs();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
str.append(getCommand());
|
||||
|
||||
for (String arg : getArgs()) {
|
||||
str.append(FIELD_SEPARATOR);
|
||||
str.append(arg);
|
||||
}
|
||||
|
||||
str.append(CMD_TERMINATOR);
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.radiora.protocol;
|
||||
|
||||
/**
|
||||
* Base class for Feedback from RadioRA
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class RadioRAFeedback {
|
||||
|
||||
public String[] parse(String msg, int numParams) {
|
||||
String[] params = msg.split(",");
|
||||
if (params.length < numParams + 1) {
|
||||
throw new IllegalStateException("Invalid message format: " + msg);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.radiora.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Set Dimmer Level (SDL)
|
||||
* Set an individual Dimmer’s light level.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class SetDimmerLevelCommand extends RadioRACommand {
|
||||
|
||||
private int zoneNumber; // 1 to 32
|
||||
private int dimmerLevel; // 0 to 100
|
||||
private Integer fadeSec; // 0 to 240 (optional)
|
||||
|
||||
public SetDimmerLevelCommand(int zoneNumber, int dimmerLevel) {
|
||||
this.zoneNumber = zoneNumber;
|
||||
this.dimmerLevel = dimmerLevel;
|
||||
}
|
||||
|
||||
public void setFadeSeconds(int seconds) {
|
||||
fadeSec = seconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return "SDL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getArgs() {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(String.valueOf(zoneNumber));
|
||||
args.add(String.valueOf(dimmerLevel));
|
||||
|
||||
if (fadeSec != null) {
|
||||
args.add(String.valueOf(fadeSec));
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -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.radiora.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* Set Switch Level (SSL)
|
||||
* Turn an individual Switch ON or OFF.
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class SetSwitchLevelCommand extends RadioRACommand {
|
||||
|
||||
private int zoneNumber; // 1 to 32
|
||||
private OnOffType state; // ON/OFF
|
||||
private Integer delaySec; // 0 to 240 (optional)
|
||||
|
||||
public SetSwitchLevelCommand(int zoneNumber, OnOffType state) {
|
||||
this.zoneNumber = zoneNumber;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setDelaySeconds(int seconds) {
|
||||
this.delaySec = seconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return "SSL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getArgs() {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(String.valueOf(zoneNumber));
|
||||
args.add(String.valueOf(state));
|
||||
|
||||
if (delaySec != null) {
|
||||
args.add(String.valueOf(delaySec));
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -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.radiora.protocol;
|
||||
|
||||
/**
|
||||
* Feedback that gives the state of all zones
|
||||
* <p>
|
||||
* <b>Syntax:</b>
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* ZMP,<Zone States>
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <b>Example:</b>
|
||||
* <p>
|
||||
* Zones 2 and 9 are ON, all others are OFF, and Zones 31 and 32 are unassigned.
|
||||
*
|
||||
* <pre>
|
||||
* ZMP,010000001000000000000000000000XX
|
||||
* </pre>
|
||||
*
|
||||
* @author Jeff Lauterbach - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class ZoneMapFeedback extends RadioRAFeedback {
|
||||
|
||||
private String zoneStates; // 32 bit String of (0,1,X)
|
||||
|
||||
public ZoneMapFeedback(String msg) {
|
||||
String[] params = parse(msg, 1);
|
||||
|
||||
zoneStates = params[1];
|
||||
}
|
||||
|
||||
public String getZoneStates() {
|
||||
return zoneStates;
|
||||
}
|
||||
|
||||
public char getZoneValue(int zone) {
|
||||
if (zone < 1 || zone > zoneStates.length()) {
|
||||
return 'X';
|
||||
}
|
||||
|
||||
return zoneStates.charAt(zone - 1);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user