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.powermax.internal.handler;
15 import static org.openhab.binding.powermax.internal.PowermaxBindingConstants.*;
17 import java.time.Instant;
18 import java.time.ZonedDateTime;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.powermax.internal.config.PowermaxX10Configuration;
22 import org.openhab.binding.powermax.internal.config.PowermaxZoneConfiguration;
23 import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
24 import org.openhab.binding.powermax.internal.state.PowermaxPanelSettingsListener;
25 import org.openhab.binding.powermax.internal.state.PowermaxState;
26 import org.openhab.binding.powermax.internal.state.PowermaxX10Settings;
27 import org.openhab.binding.powermax.internal.state.PowermaxZoneSettings;
28 import org.openhab.core.i18n.TimeZoneProvider;
29 import org.openhab.core.library.types.DateTimeType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.OpenClosedType;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.ThingStatusInfo;
38 import org.openhab.core.thing.binding.BaseThingHandler;
39 import org.openhab.core.thing.binding.ThingHandler;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link PowermaxThingHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Laurent Garnier - Initial contribution
51 public class PowermaxThingHandler extends BaseThingHandler implements PowermaxPanelSettingsListener {
53 private final Logger logger = LoggerFactory.getLogger(PowermaxThingHandler.class);
55 private static final int ZONE_NR_MIN = 1;
56 private static final int ZONE_NR_MAX = 64;
57 private static final int X10_NR_MIN = 1;
58 private static final int X10_NR_MAX = 16;
60 private final TimeZoneProvider timeZoneProvider;
62 private PowermaxBridgeHandler bridgeHandler;
64 public PowermaxThingHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
66 this.timeZoneProvider = timeZoneProvider;
70 public void initialize() {
71 logger.debug("Initializing handler for thing {}", getThing().getUID());
72 Bridge bridge = getBridge();
74 initializeThingState(null, null);
76 initializeThingState(bridge.getHandler(), bridge.getStatus());
81 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
82 logger.debug("Bridge status changed to {} for thing {}", bridgeStatusInfo, getThing().getUID());
83 Bridge bridge = getBridge();
84 initializeThingState((bridge == null) ? null : bridge.getHandler(), bridgeStatusInfo.getStatus());
87 private void initializeThingState(ThingHandler bridgeHandler, ThingStatus bridgeStatus) {
88 if (bridgeHandler != null && bridgeStatus != null) {
89 if (bridgeStatus == ThingStatus.ONLINE) {
90 boolean validConfig = false;
91 String errorMsg = "Unexpected thing type " + getThing().getThingTypeUID();
93 if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
94 PowermaxZoneConfiguration config = getConfigAs(PowermaxZoneConfiguration.class);
95 if (config.zoneNumber != null && config.zoneNumber >= ZONE_NR_MIN
96 && config.zoneNumber <= ZONE_NR_MAX) {
99 errorMsg = "zoneNumber setting must be defined in thing configuration and set between "
100 + ZONE_NR_MIN + " and " + ZONE_NR_MAX;
102 } else if (getThing().getThingTypeUID().equals(THING_TYPE_X10)) {
103 PowermaxX10Configuration config = getConfigAs(PowermaxX10Configuration.class);
104 if (config.deviceNumber != null && config.deviceNumber >= X10_NR_MIN
105 && config.deviceNumber <= X10_NR_MAX) {
108 errorMsg = "deviceNumber setting must be defined in thing configuration and set between "
109 + X10_NR_MIN + " and " + X10_NR_MAX;
114 updateStatus(ThingStatus.UNKNOWN);
115 logger.debug("Set handler status to UNKNOWN for thing {} (bridge ONLINE)", getThing().getUID());
116 this.bridgeHandler = (PowermaxBridgeHandler) bridgeHandler;
117 this.bridgeHandler.registerPanelSettingsListener(this);
118 onPanelSettingsUpdated(this.bridgeHandler.getPanelSettings());
120 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
123 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
124 logger.debug("Set handler status to OFFLINE for thing {} (bridge OFFLINE)", getThing().getUID());
127 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
128 logger.debug("Set handler status to OFFLINE for thing {}", getThing().getUID());
133 public void dispose() {
134 logger.debug("Handler disposed for thing {}", getThing().getUID());
135 if (bridgeHandler != null) {
136 bridgeHandler.unregisterPanelSettingsListener(this);
142 public void handleCommand(ChannelUID channelUID, Command command) {
143 logger.debug("Received command {} from channel {}", command, channelUID.getId());
145 if (bridgeHandler == null) {
147 } else if (command instanceof RefreshType) {
148 updateChannelFromAlarmState(channelUID.getId(), bridgeHandler.getCurrentState());
150 switch (channelUID.getId()) {
152 if (command instanceof OnOffType) {
153 bridgeHandler.zoneBypassed(getConfigAs(PowermaxZoneConfiguration.class).zoneNumber.byteValue(),
154 command.equals(OnOffType.ON));
156 logger.debug("Command of type {} while OnOffType is expected. Command is ignored.",
157 command.getClass().getSimpleName());
161 bridgeHandler.x10Command(getConfigAs(PowermaxX10Configuration.class).deviceNumber.byteValue(),
165 logger.debug("No available command for channel {}. Command is ignored.", channelUID.getId());
172 * Update channel to match a new alarm system state
174 * @param channel: the channel
175 * @param state: the alarm system state
177 public void updateChannelFromAlarmState(String channel, PowermaxState state) {
178 if (state == null || channel == null || !isLinked(channel)) {
182 if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
183 int num = getConfigAs(PowermaxZoneConfiguration.class).zoneNumber.intValue();
184 if (channel.equals(TRIPPED) && (state.isSensorTripped(num) != null)) {
185 updateState(TRIPPED, state.isSensorTripped(num) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
186 } else if (channel.equals(LAST_TRIP) && (state.getSensorLastTripped(num) != null)) {
187 ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(state.getSensorLastTripped(num)),
188 timeZoneProvider.getTimeZone());
189 updateState(LAST_TRIP, new DateTimeType(zoned));
190 } else if (channel.equals(BYPASSED) && (state.isSensorBypassed(num) != null)) {
191 updateState(BYPASSED, state.isSensorBypassed(num) ? OnOffType.ON : OnOffType.OFF);
192 } else if (channel.equals(ARMED) && (state.isSensorArmed(num) != null)) {
193 updateState(ARMED, state.isSensorArmed(num) ? OnOffType.ON : OnOffType.OFF);
194 } else if (channel.equals(LOCKED) && (state.isSensorArmed(num) != null)) {
195 updateState(LOCKED, state.isSensorArmed(num) ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
196 } else if (channel.equals(LOW_BATTERY) && (state.isSensorLowBattery(num) != null)) {
197 updateState(LOW_BATTERY, state.isSensorLowBattery(num) ? OnOffType.ON : OnOffType.OFF);
199 } else if (getThing().getThingTypeUID().equals(THING_TYPE_X10)) {
200 int num = getConfigAs(PowermaxX10Configuration.class).deviceNumber.intValue();
201 if (channel.equals(X10_STATUS) && (state.getPGMX10DeviceStatus(num) != null)) {
202 updateState(X10_STATUS, state.getPGMX10DeviceStatus(num) ? OnOffType.ON : OnOffType.OFF);
208 public void onPanelSettingsUpdated(@Nullable PowermaxPanelSettings settings) {
209 if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
210 PowermaxZoneConfiguration config = getConfigAs(PowermaxZoneConfiguration.class);
211 onZoneSettingsUpdated(config.zoneNumber, settings);
212 } else if (getThing().getThingTypeUID().equals(THING_TYPE_X10)) {
213 if (isNotReadyForThingStatusUpdate()) {
217 PowermaxX10Configuration config = getConfigAs(PowermaxX10Configuration.class);
218 PowermaxX10Settings deviceSettings = (settings == null) ? null
219 : settings.getX10Settings(config.deviceNumber);
220 if (settings == null) {
221 if (getThing().getStatus() != ThingStatus.UNKNOWN) {
222 updateStatus(ThingStatus.UNKNOWN);
223 logger.debug("Set handler status to UNKNOWN for thing {}", getThing().getUID());
225 } else if (deviceSettings == null || !deviceSettings.isEnabled()) {
226 if (getThing().getStatus() != ThingStatus.OFFLINE) {
227 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Disabled device");
228 logger.debug("Set handler status to OFFLINE for thing {} (X10 device {} disabled)",
229 getThing().getUID(), config.deviceNumber);
231 } else if (getThing().getStatus() != ThingStatus.ONLINE) {
232 updateStatus(ThingStatus.ONLINE);
233 logger.debug("Set handler status to ONLINE for thing {} (X10 device {} enabled)", getThing().getUID(),
234 config.deviceNumber);
240 public void onZoneSettingsUpdated(int zoneNumber, @Nullable PowermaxPanelSettings settings) {
241 if (getThing().getThingTypeUID().equals(THING_TYPE_ZONE)) {
242 PowermaxZoneConfiguration config = getConfigAs(PowermaxZoneConfiguration.class);
243 if (zoneNumber == config.zoneNumber) {
244 if (isNotReadyForThingStatusUpdate()) {
248 PowermaxZoneSettings zoneSettings = (settings == null) ? null
249 : settings.getZoneSettings(config.zoneNumber);
250 if (settings == null) {
251 if (getThing().getStatus() != ThingStatus.UNKNOWN) {
252 updateStatus(ThingStatus.UNKNOWN);
253 logger.debug("Set handler status to UNKNOWN for thing {}", getThing().getUID());
255 } else if (zoneSettings == null) {
256 if (getThing().getStatus() != ThingStatus.OFFLINE) {
257 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Zone not paired");
258 logger.debug("Set handler status to OFFLINE for thing {} (zone number {} not paired)",
259 getThing().getUID(), config.zoneNumber);
261 } else if (getThing().getStatus() != ThingStatus.ONLINE) {
262 updateStatus(ThingStatus.ONLINE);
263 logger.debug("Set handler status to ONLINE for thing {} (zone number {} paired)",
264 getThing().getUID(), config.zoneNumber);
270 private boolean isNotReadyForThingStatusUpdate() {
271 return (getThing().getStatus() == ThingStatus.OFFLINE)
272 && ((getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR)
273 || (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE)
274 || (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_UNINITIALIZED));
277 public PowermaxZoneConfiguration getZoneConfiguration() {
278 return getConfigAs(PowermaxZoneConfiguration.class);
281 public PowermaxX10Configuration getX10Configuration() {
282 return getConfigAs(PowermaxX10Configuration.class);