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.time.ZoneId;
18 import java.time.ZonedDateTime;
19 import java.util.Calendar;
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.DateTimeType;
25 import org.openhab.core.library.types.DecimalType;
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 the RA2 time clock.
39 * @author Bob Adair - Initial contribution
42 public class TimeclockHandler extends LutronHandler {
43 private static final Integer ACTION_CLOCKMODE = 1;
44 private static final Integer ACTION_SUNRISE = 2;
45 private static final Integer ACTION_SUNSET = 3;
46 private static final Integer ACTION_EXECEVENT = 5;
47 private static final Integer ACTION_SETEVENT = 6;
48 private static final Integer EVENT_ENABLE = 1;
49 private static final Integer EVENT_DISABLE = 2;
51 private final Logger logger = LoggerFactory.getLogger(TimeclockHandler.class);
53 private int integrationId;
55 public TimeclockHandler(Thing thing) {
60 public int getIntegrationId() {
65 public void initialize() {
66 Number id = (Number) getThing().getConfiguration().get("integrationId");
67 logger.debug("Initializing timeclock handler");
69 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId");
72 integrationId = id.intValue();
77 protected void initDeviceState() {
78 logger.debug("Initializing device state for Timeclock {}", getIntegrationId());
79 Bridge bridge = getBridge();
81 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
82 } else if (bridge.getStatus() == ThingStatus.ONLINE) {
83 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
84 queryTimeclock(ACTION_CLOCKMODE); // handleUpdate() will set thing status to online when response arrives
86 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
91 public void channelLinked(ChannelUID channelUID) {
92 logger.debug("Handling channel link request for timeclock {}", integrationId);
93 if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
94 queryTimeclock(ACTION_CLOCKMODE);
99 public void handleCommand(ChannelUID channelUID, Command command) {
100 String channelID = channelUID.getId();
101 logger.debug("Handling timeclock command {} on channel {}", command, channelID);
103 if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
104 if (command instanceof DecimalType) {
105 Integer mode = new Integer(((DecimalType) command).intValue());
106 timeclock(ACTION_CLOCKMODE, mode);
107 } else if (command instanceof RefreshType) {
108 queryTimeclock(ACTION_CLOCKMODE);
110 logger.debug("Invalid command type for clockmode channnel");
112 } else if (channelUID.getId().equals(CHANNEL_EXECEVENT)) {
113 if (command instanceof DecimalType) {
114 Integer index = new Integer(((DecimalType) command).intValue());
115 timeclock(ACTION_EXECEVENT, index);
117 logger.debug("Invalid command type for execevent channnel");
119 } else if (channelUID.getId().equals(CHANNEL_SUNRISE)) {
120 if (command instanceof RefreshType) {
121 queryTimeclock(ACTION_SUNRISE);
123 logger.debug("Invalid command type for sunrise channnel");
125 } else if (channelUID.getId().equals(CHANNEL_SUNSET)) {
126 if (command instanceof RefreshType) {
127 queryTimeclock(ACTION_SUNSET);
129 logger.debug("Invalid command type for sunset channnel");
131 } else if (channelUID.getId().equals(CHANNEL_ENABLEEVENT)) {
132 if (command instanceof DecimalType) {
133 Integer index = new Integer(((DecimalType) command).intValue());
134 timeclock(ACTION_SETEVENT, index, EVENT_ENABLE);
136 logger.debug("Invalid command type for enableevent channnel");
138 } else if (channelUID.getId().equals(CHANNEL_DISABLEEVENT)) {
139 if (command instanceof DecimalType) {
140 Integer index = new Integer(((DecimalType) command).intValue());
141 timeclock(ACTION_SETEVENT, index, EVENT_DISABLE);
143 logger.debug("Invalid command type for disableevent channnel");
146 logger.debug("Command received on invalid channel");
150 private @Nullable Calendar parseLutronTime(final String timeString) {
151 Integer hour, minute;
152 Calendar calendar = Calendar.getInstance();
154 String hh = timeString.split(":", 2)[0];
155 String mm = timeString.split(":", 2)[1];
156 hour = Integer.parseInt(hh);
157 minute = Integer.parseInt(mm);
158 } catch (NumberFormatException | IndexOutOfBoundsException exception) {
159 logger.warn("Invaid time format received from timeclock {}", integrationId);
162 calendar.set(Calendar.HOUR_OF_DAY, hour);
163 calendar.set(Calendar.MINUTE, minute);
168 public void handleUpdate(LutronCommandType type, String... parameters) {
169 if (type != LutronCommandType.TIMECLOCK) {
172 logger.debug("Handling update received from timeclock {}", integrationId);
175 if (parameters.length >= 2 && ACTION_CLOCKMODE.toString().equals(parameters[0])) {
176 Integer mode = new Integer(parameters[1]);
177 if (getThing().getStatus() == ThingStatus.UNKNOWN) {
178 updateStatus(ThingStatus.ONLINE);
180 updateState(CHANNEL_CLOCKMODE, new DecimalType(mode));
182 } else if (parameters.length >= 2 && ACTION_SUNRISE.toString().equals(parameters[0])) {
183 Calendar calendar = parseLutronTime(parameters[1]);
184 if (calendar != null) {
185 updateState(CHANNEL_SUNRISE,
186 new DateTimeType(ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault())));
189 } else if (parameters.length >= 2 && ACTION_SUNSET.toString().equals(parameters[0])) {
190 Calendar calendar = parseLutronTime(parameters[1]);
191 if (calendar != null) {
192 updateState(CHANNEL_SUNSET,
193 new DateTimeType(ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault())));
196 } else if (parameters.length >= 2 && ACTION_EXECEVENT.toString().equals(parameters[0])) {
197 Integer index = new Integer(parameters[1]);
198 updateState(CHANNEL_EXECEVENT, new DecimalType(index));
200 } else if (parameters.length >= 3 && ACTION_SETEVENT.toString().equals(parameters[0])) {
201 Integer index = new Integer(parameters[1]);
202 Integer state = new Integer(parameters[2]);
203 if (state.equals(EVENT_ENABLE)) {
204 updateState(CHANNEL_ENABLEEVENT, new DecimalType(index));
205 } else if (state.equals(EVENT_DISABLE)) {
206 updateState(CHANNEL_DISABLEEVENT, new DecimalType(index));
209 } catch (NumberFormatException e) {
210 logger.debug("Encountered number format exception while handling update for timeclock {}", integrationId);