2 * Copyright (c) 2010-2023 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.Arrays;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.lutron.internal.protocol.OutputCommand;
23 import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
24 import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.thing.Bridge;
27 import org.openhab.core.thing.ChannelUID;
28 import org.openhab.core.thing.Thing;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.thing.ThingStatusDetail;
31 import org.openhab.core.types.Command;
32 import org.openhab.core.types.RefreshType;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * Handler responsible for communicating with Lutron contact closure outputs (CCOs).
38 * e.g. VCRX CCOs and CCO RF module
40 * Note: For a RA2 Pulsed CCO, querying the output state with {@code ?OUTPUT,<id>,1} is meaningless and will
41 * always return 100 (on). Also, the main repeater will not report ~OUTPUT commands for a pulsed CCO regardless
42 * of the #MONITORING setting. So this binding supports sending pulses ONLY.
44 * @author Bob Adair - Initial contribution
48 public class CcoHandler extends LutronHandler {
49 private final Logger logger = LoggerFactory.getLogger(CcoHandler.class);
51 private int integrationId;
52 private double defaultPulse = 0.5; // default pulse length (seconds)
54 protected enum CcoOutputType {
59 protected @Nullable CcoOutputType outputType;
61 public CcoHandler(Thing thing) {
66 public int getIntegrationId() {
71 public void initialize() {
72 Number id = (Number) getThing().getConfiguration().get(INTEGRATION_ID);
75 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
78 integrationId = id.intValue();
79 logger.debug("Initializing CCO handler for integration ID {}", id);
81 // Determine output type from configuration if not pre-defined by subclass
82 if (outputType == null) {
83 String oType = (String) getThing().getConfiguration().get(CCO_TYPE);
85 if (oType == null || oType == CCO_TYPE_PULSED) {
86 logger.debug("Setting CCO type Pulsed for device {}.", integrationId);
87 outputType = CcoOutputType.PULSED;
88 } else if (oType == CCO_TYPE_MAINTAINED) {
89 logger.debug("Setting CCO type Maintained for device {}.", integrationId);
90 outputType = CcoOutputType.MAINTAINED;
92 logger.warn("Invalid CCO type setting for device {}. Defaulting to Pulsed.", integrationId);
93 outputType = CcoOutputType.PULSED;
97 // If output type pulsed, determine pulse length
98 if (outputType == CcoOutputType.PULSED) {
99 Number defaultPulse = (Number) getThing().getConfiguration().get(DEFAULT_PULSE);
101 if (defaultPulse != null) {
102 double dp = defaultPulse.doubleValue();
103 if (dp >= 0 && dp <= 99.0) {
105 logger.debug("Pulse length set to {} seconds for device {}.", defaultPulse, integrationId);
107 logger.warn("Invalid pulse length value set. Using default for device {}.", integrationId);
110 logger.debug("Using default pulse length value for device {}", integrationId);
117 protected void initDeviceState() {
118 logger.debug("Initializing device state for CCO {}", integrationId);
119 Bridge bridge = getBridge();
120 if (bridge == null) {
121 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
122 } else if (bridge.getStatus() == ThingStatus.ONLINE) {
123 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
124 queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
125 // handleUpdate() will set thing status to online when response arrives
127 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
132 public void channelLinked(ChannelUID channelUID) {
133 if (channelUID.getId().equals(CHANNEL_SWITCH)) {
134 logger.debug("switch channel {} linked for CCO {}", channelUID.getId(), integrationId);
136 if (outputType == CcoOutputType.PULSED) {
137 // Since this is a pulsed CCO channel state is always OFF
138 updateState(channelUID, OnOffType.OFF);
139 } else if (outputType == CcoOutputType.MAINTAINED) {
140 // Query the device state and let the service routine update the channel state
141 queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
143 logger.warn("invalid output type defined for CCO {}", integrationId);
146 logger.warn("invalid channel {} linked for CCO {}", channelUID.getId(), integrationId);
151 public void handleCommand(ChannelUID channelUID, Command command) {
152 if (channelUID.getId().equals(CHANNEL_SWITCH)) {
153 if (command instanceof OnOffType && command == OnOffType.ON) {
154 if (outputType == CcoOutputType.PULSED) {
155 output(TargetType.CCO, OutputCommand.ACTION_PULSE, Double.valueOf(defaultPulse), null, null);
156 updateState(channelUID, OnOffType.OFF);
158 output(TargetType.CCO, OutputCommand.ACTION_STATE, 100, null, null);
162 else if (command instanceof OnOffType && command == OnOffType.OFF) {
163 if (outputType == CcoOutputType.MAINTAINED) {
164 output(TargetType.CCO, OutputCommand.ACTION_STATE, 0, null, null);
168 else if (command instanceof RefreshType) {
169 if (outputType == CcoOutputType.MAINTAINED) {
170 queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
172 updateState(CHANNEL_SWITCH, OnOffType.OFF);
175 logger.debug("ignoring invalid command on channel {} for CCO {}", channelUID.getId(), integrationId);
178 logger.debug("ignoring command on invalid channel {} for CCO {}", channelUID.getId(), integrationId);
183 public void handleUpdate(LutronCommandType type, String... parameters) {
184 logger.debug("Update received for CCO: {} {}", type, Arrays.asList(parameters));
186 if (outputType == CcoOutputType.MAINTAINED) {
187 if (type == LutronCommandType.OUTPUT && parameters.length > 1
188 && OutputCommand.ACTION_STATE.toString().equals(parameters[0])) {
189 if (getThing().getStatus() == ThingStatus.UNKNOWN) {
190 updateStatus(ThingStatus.ONLINE);
193 BigDecimal state = new BigDecimal(parameters[1]);
194 updateState(CHANNEL_SWITCH, OnOffType.from(state.compareTo(BigDecimal.ZERO) != 0));
195 } catch (NumberFormatException e) {
196 logger.warn("Unable to parse update {} {} from CCO {}", type, Arrays.asList(parameters),
202 // Do nothing on receiving updates for pulsed CCO except update online status
203 if (getThing().getStatus() == ThingStatus.UNKNOWN) {
204 updateStatus(ThingStatus.ONLINE);