]> git.basschouten.com Git - openhab-addons.git/blob
c7ada31cda2b7169430c360a8babbb88188ac621
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.lutron.internal.handler;
14
15 import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.Locale;
19
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;
34
35 /**
36  * Handler responsible for communicating with Lutron contact closure outputs (CCOs).
37  * e.g. VCRX CCOs and CCO RF module
38  *
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.
42  *
43  * @author Bob Adair - Initial contribution
44  *
45  */
46 @NonNullByDefault
47 public class CcoHandler extends LutronHandler {
48     private static final Integer ACTION_PULSE = 6;
49     private static final Integer ACTION_STATE = 1;
50
51     private final Logger logger = LoggerFactory.getLogger(CcoHandler.class);
52
53     private int integrationId;
54     private double defaultPulse = 0.5; // default pulse length (seconds)
55
56     protected enum CcoOutputType {
57         PULSED,
58         MAINTAINED
59     }
60
61     protected @Nullable CcoOutputType outputType;
62
63     public CcoHandler(Thing thing) {
64         super(thing);
65     }
66
67     @Override
68     public int getIntegrationId() {
69         return integrationId;
70     }
71
72     @Override
73     public void initialize() {
74         Number id = (Number) getThing().getConfiguration().get(INTEGRATION_ID);
75
76         if (id == null) {
77             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
78             return;
79         }
80         integrationId = id.intValue();
81         logger.debug("Initializing CCO handler for integration ID {}", id);
82
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);
86
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;
93             } else {
94                 logger.warn("Invalid CCO type setting for device {}. Defaulting to Pulsed.", integrationId);
95                 outputType = CcoOutputType.PULSED;
96             }
97         }
98
99         // If output type pulsed, determine pulse length
100         if (outputType == CcoOutputType.PULSED) {
101             Number defaultPulse = (Number) getThing().getConfiguration().get(DEFAULT_PULSE);
102
103             if (defaultPulse != null) {
104                 double dp = defaultPulse.doubleValue();
105                 if (dp >= 0 && dp <= 99.0) {
106                     defaultPulse = dp;
107                     logger.debug("Pulse length set to {} seconds for device {}.", defaultPulse, integrationId);
108                 } else {
109                     logger.warn("Invalid pulse length value set. Using default for device {}.", integrationId);
110                 }
111             } else {
112                 logger.debug("Using default pulse length value for device {}", integrationId);
113             }
114         }
115         initDeviceState();
116     }
117
118     @Override
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
127         } else {
128             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
129         }
130     }
131
132     @Override
133     public void channelLinked(ChannelUID channelUID) {
134         if (channelUID.getId().equals(CHANNEL_SWITCH)) {
135             logger.debug("switch channel {} linked for CCO {}", channelUID.getId(), integrationId);
136
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);
143             } else {
144                 logger.warn("invalid output type defined for CCO {}", integrationId);
145             }
146         } else {
147             logger.warn("invalid channel {} linked for CCO {}", channelUID.getId(), integrationId);
148         }
149     }
150
151     @Override
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);
158                 } else {
159                     output(ACTION_STATE, 100);
160                 }
161             }
162
163             else if (command instanceof OnOffType && command == OnOffType.OFF) {
164                 if (outputType == CcoOutputType.MAINTAINED) {
165                     output(ACTION_STATE, 0);
166                 }
167             }
168
169             else if (command instanceof RefreshType) {
170                 if (outputType == CcoOutputType.MAINTAINED) {
171                     queryOutput(ACTION_STATE);
172                 } else {
173                     updateState(CHANNEL_SWITCH, OnOffType.OFF);
174                 }
175             } else {
176                 logger.debug("ignoring invalid command on channel {} for CCO {}", channelUID.getId(), integrationId);
177             }
178         } else {
179             logger.debug("ignoring command on invalid channel {} for CCO {}", channelUID.getId(), integrationId);
180         }
181     }
182
183     @Override
184     public void handleUpdate(LutronCommandType type, String... parameters) {
185         logger.debug("Update received for CCO: {} {}", type, StringUtils.join(parameters, ","));
186
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);
192                 }
193                 try {
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, ","),
198                             integrationId);
199                     return;
200                 }
201             }
202         } else {
203             // Do nothing on receiving updates for pulsed CCO except update online status
204             if (getThing().getStatus() == ThingStatus.UNKNOWN) {
205                 updateStatus(ThingStatus.ONLINE);
206             }
207         }
208     }
209 }