2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.lutron.internal.handler;
15 import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.Locale;
20 import org.apache.commons.lang.StringUtils;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
24 import org.openhab.core.library.types.OnOffType;
25 import org.openhab.core.thing.Bridge;
26 import org.openhab.core.thing.ChannelUID;
27 import org.openhab.core.thing.Thing;
28 import org.openhab.core.thing.ThingStatus;
29 import org.openhab.core.thing.ThingStatusDetail;
30 import org.openhab.core.types.Command;
31 import org.openhab.core.types.RefreshType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * Handler responsible for communicating with Lutron contact closure outputs (CCOs).
37 * e.g. VCRX CCOs and CCO RF module
39 * Note: For a RA2 Pulsed CCO, querying the output state with ?OUTPUT,<id>,1 is meaningless and will always
40 * return 100 (on). Also, the main repeater will not report ~OUTPUT commands for a pulsed CCO regardless of
41 * the #MONITORING setting. So this binding supports sending pulses ONLY.
43 * @author Bob Adair - Initial contribution
47 public class CcoHandler extends LutronHandler {
48 private static final Integer ACTION_PULSE = 6;
49 private static final Integer ACTION_STATE = 1;
51 private final Logger logger = LoggerFactory.getLogger(CcoHandler.class);
53 private int integrationId;
54 private double defaultPulse = 0.5; // default pulse length (seconds)
56 protected enum CcoOutputType {
61 protected @Nullable CcoOutputType outputType;
63 public CcoHandler(Thing thing) {
68 public int getIntegrationId() {
73 public void initialize() {
74 Number id = (Number) getThing().getConfiguration().get(INTEGRATION_ID);
77 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
80 integrationId = id.intValue();
81 logger.debug("Initializing CCO handler for integration ID {}", id);
83 // Determine output type from configuration if not pre-defined by subclass
84 if (outputType == null) {
85 String oType = (String) getThing().getConfiguration().get(CCO_TYPE);
87 if (oType == null || oType == CCO_TYPE_PULSED) {
88 logger.debug("Setting CCO type Pulsed for device {}.", integrationId);
89 outputType = CcoOutputType.PULSED;
90 } else if (oType == CCO_TYPE_MAINTAINED) {
91 logger.debug("Setting CCO type Maintained for device {}.", integrationId);
92 outputType = CcoOutputType.MAINTAINED;
94 logger.warn("Invalid CCO type setting for device {}. Defaulting to Pulsed.", integrationId);
95 outputType = CcoOutputType.PULSED;
99 // If output type pulsed, determine pulse length
100 if (outputType == CcoOutputType.PULSED) {
101 Number defaultPulse = (Number) getThing().getConfiguration().get(DEFAULT_PULSE);
103 if (defaultPulse != null) {
104 double dp = defaultPulse.doubleValue();
105 if (dp >= 0 && dp <= 99.0) {
107 logger.debug("Pulse length set to {} seconds for device {}.", defaultPulse, integrationId);
109 logger.warn("Invalid pulse length value set. Using default for device {}.", integrationId);
112 logger.debug("Using default pulse length value for device {}", integrationId);
119 protected void initDeviceState() {
120 logger.debug("Initializing device state for CCO {}", integrationId);
121 Bridge bridge = getBridge();
122 if (bridge == null) {
123 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
124 } else if (bridge.getStatus() == ThingStatus.ONLINE) {
125 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
126 queryOutput(ACTION_STATE); // handleUpdate() will set thing status to online when response arrives
128 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
133 public void channelLinked(ChannelUID channelUID) {
134 if (channelUID.getId().equals(CHANNEL_SWITCH)) {
135 logger.debug("switch channel {} linked for CCO {}", channelUID.getId(), integrationId);
137 if (outputType == CcoOutputType.PULSED) {
138 // Since this is a pulsed CCO channel state is always OFF
139 updateState(channelUID, OnOffType.OFF);
140 } else if (outputType == CcoOutputType.MAINTAINED) {
141 // Query the device state and let the service routine update the channel state
142 queryOutput(ACTION_STATE);
144 logger.warn("invalid output type defined for CCO {}", integrationId);
147 logger.warn("invalid channel {} linked for CCO {}", channelUID.getId(), integrationId);
152 public void handleCommand(ChannelUID channelUID, Command command) {
153 if (channelUID.getId().equals(CHANNEL_SWITCH)) {
154 if (command instanceof OnOffType && command == OnOffType.ON) {
155 if (outputType == CcoOutputType.PULSED) {
156 output(ACTION_PULSE, String.format(Locale.ROOT, "%.2f", defaultPulse));
157 updateState(channelUID, OnOffType.OFF);
159 output(ACTION_STATE, 100);
163 else if (command instanceof OnOffType && command == OnOffType.OFF) {
164 if (outputType == CcoOutputType.MAINTAINED) {
165 output(ACTION_STATE, 0);
169 else if (command instanceof RefreshType) {
170 if (outputType == CcoOutputType.MAINTAINED) {
171 queryOutput(ACTION_STATE);
173 updateState(CHANNEL_SWITCH, OnOffType.OFF);
176 logger.debug("ignoring invalid command on channel {} for CCO {}", channelUID.getId(), integrationId);
179 logger.debug("ignoring command on invalid channel {} for CCO {}", channelUID.getId(), integrationId);
184 public void handleUpdate(LutronCommandType type, String... parameters) {
185 logger.debug("Update received for CCO: {} {}", type, StringUtils.join(parameters, ","));
187 if (outputType == CcoOutputType.MAINTAINED) {
188 if (type == LutronCommandType.OUTPUT && parameters.length > 1
189 && ACTION_STATE.toString().equals(parameters[0])) {
190 if (getThing().getStatus() == ThingStatus.UNKNOWN) {
191 updateStatus(ThingStatus.ONLINE);
194 BigDecimal state = new BigDecimal(parameters[1]);
195 updateState(CHANNEL_SWITCH, state.compareTo(BigDecimal.ZERO) == 0 ? OnOffType.OFF : OnOffType.ON);
196 } catch (NumberFormatException e) {
197 logger.warn("Unable to parse update {} {} from CCO {}", type, StringUtils.join(parameters, ","),
203 // Do nothing on receiving updates for pulsed CCO except update online status
204 if (getThing().getStatus() == ThingStatus.UNKNOWN) {
205 updateStatus(ThingStatus.ONLINE);