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.dscalarm.internal.handler;
15 import static org.openhab.binding.dscalarm.internal.DSCAlarmBindingConstants.*;
17 import java.text.ParseException;
18 import java.text.SimpleDateFormat;
19 import java.util.Date;
20 import java.util.EventObject;
21 import java.util.List;
23 import org.openhab.binding.dscalarm.internal.DSCAlarmCode;
24 import org.openhab.binding.dscalarm.internal.DSCAlarmEvent;
25 import org.openhab.binding.dscalarm.internal.DSCAlarmMessage;
26 import org.openhab.binding.dscalarm.internal.DSCAlarmMessage.DSCAlarmMessageInfoType;
27 import org.openhab.core.library.types.DateTimeType;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * This is a class for handling a Panel type Thing.
41 * @author Russell Stephens - Initial Contribution
43 public class PanelThingHandler extends DSCAlarmBaseThingHandler {
45 private static final int PANEL_COMMAND_POLL = 0;
46 private static final int PANEL_COMMAND_STATUS_REPORT = 1;
47 private static final int PANEL_COMMAND_LABELS_REQUEST = 2;
48 private static final int PANEL_COMMAND_DUMP_ZONE_TIMERS = 8;
49 private static final int PANEL_COMMAND_SET_TIME_DATE = 10;
50 private static final int PANEL_COMMAND_CODE_SEND = 200;
52 private final Logger logger = LoggerFactory.getLogger(PanelThingHandler.class);
59 public PanelThingHandler(Thing thing) {
61 setDSCAlarmThingType(DSCAlarmThingType.PANEL);
65 public void updateChannel(ChannelUID channelUID, int state, String description) {
66 logger.debug("updateChannel(): Panel Channel UID: {}", channelUID);
73 if (channelUID != null) {
74 switch (channelUID.getId()) {
76 updateState(channelUID, new StringType(description));
78 case PANEL_SYSTEM_ERROR:
79 updateState(channelUID, new StringType(description));
83 SimpleDateFormat sdfReceived = new SimpleDateFormat("hhmmMMddyy");
86 date = sdfReceived.parse(description);
87 } catch (ParseException e) {
88 logger.warn("updateChannel(): Parse Exception occurred while trying to parse date string: {}. ",
93 SimpleDateFormat sdfUpdate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
94 String systemTime = sdfUpdate.format(date);
95 updateState(channelUID, new DateTimeType(systemTime));
99 case PANEL_TIME_STAMP:
100 boolState = state != 0;
101 onOffType = OnOffType.from(boolState);
102 updateState(channelUID, onOffType);
104 case PANEL_TIME_BROADCAST:
105 boolState = state != 0;
106 onOffType = OnOffType.from(boolState);
107 updateState(channelUID, onOffType);
110 updateState(channelUID, new DecimalType(state));
112 case PANEL_TROUBLE_MESSAGE:
113 updateState(channelUID, new StringType(description));
115 case PANEL_TROUBLE_LED:
116 boolState = state != 0;
117 onOffType = OnOffType.from(boolState);
118 updateState(channelUID, onOffType);
120 case PANEL_SERVICE_REQUIRED:
121 trouble = state != 0;
122 onOffType = OnOffType.from(trouble);
123 updateState(channelUID, onOffType);
125 case PANEL_AC_TROUBLE:
126 trouble = state != 0;
127 onOffType = OnOffType.from(trouble);
128 updateState(channelUID, onOffType);
130 case PANEL_TELEPHONE_TROUBLE:
131 trouble = state != 0;
132 onOffType = OnOffType.from(trouble);
133 updateState(channelUID, onOffType);
135 case PANEL_FTC_TROUBLE:
136 trouble = state != 0;
137 onOffType = OnOffType.from(trouble);
138 updateState(channelUID, onOffType);
140 case PANEL_ZONE_FAULT:
141 trouble = state != 0;
142 onOffType = OnOffType.from(trouble);
143 updateState(channelUID, onOffType);
145 case PANEL_ZONE_TAMPER:
146 trouble = state != 0;
147 onOffType = OnOffType.from(trouble);
148 updateState(channelUID, onOffType);
150 case PANEL_ZONE_LOW_BATTERY:
151 trouble = state != 0;
152 onOffType = OnOffType.from(trouble);
153 updateState(channelUID, onOffType);
155 case PANEL_TIME_LOSS:
156 trouble = state != 0;
157 onOffType = OnOffType.from(trouble);
158 updateState(channelUID, onOffType);
160 case PANEL_FIRE_KEY_ALARM:
161 trigger = state != 0;
162 onOffType = OnOffType.from(trigger);
163 updateState(channelUID, onOffType);
165 case PANEL_PANIC_KEY_ALARM:
166 trigger = state != 0;
167 onOffType = OnOffType.from(trigger);
168 updateState(channelUID, onOffType);
170 case PANEL_AUX_KEY_ALARM:
171 trigger = state != 0;
172 onOffType = OnOffType.from(trigger);
173 updateState(channelUID, onOffType);
175 case PANEL_AUX_INPUT_ALARM:
176 trigger = state != 0;
177 onOffType = OnOffType.from(trigger);
178 updateState(channelUID, onOffType);
181 logger.debug("updateChannel(): Panel Channel not updated - {}.", channelUID);
188 public void handleCommand(ChannelUID channelUID, Command command) {
189 logger.debug("handleCommand(): Command Received - {} {}.", channelUID, command);
191 if (command instanceof RefreshType) {
195 if (dscAlarmBridgeHandler != null && dscAlarmBridgeHandler.isConnected()) {
198 switch (channelUID.getId()) {
200 cmd = Integer.parseInt(command.toString());
201 handlePanelCommand(cmd);
202 updateState(channelUID, new StringType(String.valueOf(-1)));
204 case PANEL_TIME_STAMP:
205 if (command instanceof OnOffType onOffCommand) {
206 cmd = command == OnOffType.ON ? 1 : 0;
207 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.TimeStampControl, String.valueOf(cmd));
208 updateState(channelUID, onOffCommand);
211 case PANEL_TIME_BROADCAST:
212 if (command instanceof OnOffType onOffCommand) {
213 cmd = command == OnOffType.ON ? 1 : 0;
214 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.TimeDateBroadcastControl, String.valueOf(cmd));
215 updateState(channelUID, onOffCommand);
225 * Method to handle PANEL_COMMAND
229 private void handlePanelCommand(int cmd) {
231 case PANEL_COMMAND_POLL:
232 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.Poll);
234 case PANEL_COMMAND_STATUS_REPORT:
235 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.StatusReport);
237 case PANEL_COMMAND_LABELS_REQUEST:
238 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.LabelsRequest);
240 case PANEL_COMMAND_DUMP_ZONE_TIMERS:
241 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.DumpZoneTimers);
243 case PANEL_COMMAND_SET_TIME_DATE:
244 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.SetTimeDate);
246 case PANEL_COMMAND_CODE_SEND:
247 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.CodeSend, getUserCode());
255 * Method to set the time stamp state.
259 private void setTimeStampState(String timeStamp) {
260 if (timeStamp != null) {
261 ChannelUID channelUID = new ChannelUID(getThing().getUID(), PANEL_TIME_STAMP);
262 int state = timeStamp.isEmpty() ? 0 : 1;
263 updateChannel(channelUID, state, "");
268 * Method to set Channel PANEL_SYSTEM_ERROR.
271 * @param dscAlarmMessage
273 private void panelSystemError(DSCAlarmMessage dscAlarmMessage) {
274 ChannelUID channelUID = new ChannelUID(getThing().getUID(), PANEL_SYSTEM_ERROR);
275 int systemErrorCode = 0;
276 String systemErrorDescription = "";
278 if (dscAlarmMessage != null) {
279 systemErrorCode = Integer.parseInt(dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA));
280 switch (systemErrorCode) {
282 systemErrorDescription = "Receive Buffer Overrun";
285 systemErrorDescription = "Receive Buffer Overflow";
288 systemErrorDescription = "Transmit Buffer Overflow";
291 systemErrorDescription = "Keybus Transmit Buffer Overrun";
294 systemErrorDescription = "Keybus Transmit Time Timeout";
297 systemErrorDescription = "Keybus Transmit Mode Timeout";
300 systemErrorDescription = "Keybus Transmit Keystring Timeout";
303 systemErrorDescription = "Keybus Interface Not Functioning";
306 systemErrorDescription = "Keybus Busy - Attempting to Disarm or Arm with user code";
309 systemErrorDescription = "Keybus Busy – Lockout";
312 systemErrorDescription = "Keybus Busy – Installers Mode";
315 systemErrorDescription = "Keybus Busy - General Busy";
318 systemErrorDescription = "API Command Syntax Error";
321 systemErrorDescription = "API Command Partition Error - Requested Partition is out of bounds";
324 systemErrorDescription = "API Command Not Supported";
327 systemErrorDescription = "API System Not Armed - Sent in response to a disarm command";
330 systemErrorDescription = "API System Not Ready to Arm - System is either not-secure, in exit-delay, or already armed";
333 systemErrorDescription = "API Command Invalid Length";
336 systemErrorDescription = "API User Code not Required";
339 systemErrorDescription = "API Invalid Characters in Command - No alpha characters are allowed except for checksum";
342 systemErrorDescription = "API Virtual Keypad is Disabled";
345 systemErrorDescription = "API Not Valid Parameter";
348 systemErrorDescription = "API Keypad Does Not Come Out of Blank Mode";
351 systemErrorDescription = "API IT-100 is Already in Thermostat Menu";
354 systemErrorDescription = "API IT-100 is NOT in Thermostat Menu";
357 systemErrorDescription = "API No Response From Thermostat or Escort Module";
361 systemErrorDescription = "No Error";
367 String errorMessage = String.format("%03d", systemErrorCode) + ": " + systemErrorDescription;
368 channelUID = new ChannelUID(getThing().getUID(), PANEL_SYSTEM_ERROR);
369 updateState(channelUID, new StringType(errorMessage));
373 * Handle Verbose Trouble Status events for the EyezOn Envisalink 3/2DS DSC Alarm Interface.
377 private void verboseTroubleStatusHandler(EventObject event) {
378 DSCAlarmEvent dscAlarmEvent = (DSCAlarmEvent) event;
379 DSCAlarmMessage dscAlarmMessage = dscAlarmEvent.getDSCAlarmMessage();
380 String[] channelTypes = { PANEL_SERVICE_REQUIRED, PANEL_AC_TROUBLE, PANEL_TELEPHONE_TROUBLE, PANEL_FTC_TROUBLE,
381 PANEL_ZONE_FAULT, PANEL_ZONE_TAMPER, PANEL_ZONE_LOW_BATTERY, PANEL_TIME_LOSS };
383 ChannelUID channelUID = null;
386 int bitField = Integer.decode("0x" + dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA));
387 int[] masks = { 1, 2, 4, 8, 16, 32, 64, 128 };
388 int[] bits = new int[bitCount];
390 for (int i = 0; i < bitCount; i++) {
391 channelUID = new ChannelUID(getThing().getUID(), channelTypes[i]);
392 bits[i] = bitField & masks[i];
393 updateChannel(channelUID, bits[i] != 0 ? 1 : 0, "");
398 * Restores all partitions that are in alarm after special panel alarm conditions have been restored.
400 * @param dscAlarmCode
402 private void restorePartitionsInAlarm(DSCAlarmCode dscAlarmCode) {
403 logger.debug("restorePartitionsInAlarm(): DSC Alarm Code: {}!", dscAlarmCode.toString());
405 ChannelUID channelUID = null;
407 if (dscAlarmCode == DSCAlarmCode.FireKeyRestored || dscAlarmCode == DSCAlarmCode.AuxiliaryKeyRestored
408 || dscAlarmCode == DSCAlarmCode.PanicKeyRestored
409 || dscAlarmCode == DSCAlarmCode.AuxiliaryInputAlarmRestored) {
410 List<Thing> things = dscAlarmBridgeHandler.getThing().getThings();
411 for (Thing thg : things) {
412 if (thg.getThingTypeUID().equals(PARTITION_THING_TYPE)) {
413 DSCAlarmBaseThingHandler handler = (DSCAlarmBaseThingHandler) thg.getHandler();
414 if (handler != null) {
415 channelUID = new ChannelUID(thg.getUID(), PARTITION_IN_ALARM);
416 handler.updateChannel(channelUID, 0, "");
418 logger.debug("restorePartitionsInAlarm(): Partition In Alarm Restored: {}!", thg.getUID());
426 @SuppressWarnings("PMD.CompareObjectsWithEquals")
427 public void dscAlarmEventReceived(EventObject event, Thing thing) {
429 DSCAlarmEvent dscAlarmEvent = (DSCAlarmEvent) event;
430 DSCAlarmMessage dscAlarmMessage = dscAlarmEvent.getDSCAlarmMessage();
431 String dscAlarmMessageData = dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA);
432 setTimeStampState(dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.TIME_STAMP));
434 if (getThing() == thing) {
435 ChannelUID channelUID = null;
436 DSCAlarmCode dscAlarmCode = DSCAlarmCode
437 .getDSCAlarmCodeValue(dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.CODE));
438 logger.debug("dscAlarmEventRecieved(): Thing - {} Command - {}", thing.getUID(), dscAlarmCode);
442 switch (dscAlarmCode) {
443 case CommandAcknowledge: /* 500 */
445 case SystemError: /* 502 */
446 int errorCode = Integer.parseInt(dscAlarmMessageData);
448 if (errorCode == 23 || errorCode == 24) {
449 List<Thing> things = dscAlarmBridgeHandler.getThing().getThings();
450 for (Thing thg : things) {
451 if (thg.getThingTypeUID().equals(PARTITION_THING_TYPE)) {
452 DSCAlarmBaseThingHandler handler = (DSCAlarmBaseThingHandler) thg.getHandler();
453 if (handler != null) {
454 channelUID = new ChannelUID(thg.getUID(), PARTITION_ARM_MODE);
455 handler.updateChannel(channelUID, 0, "");
461 panelSystemError(dscAlarmMessage);
463 case TimeDateBroadcast: /* 550 */
464 channelUID = new ChannelUID(getThing().getUID(), PANEL_TIME);
465 String panelTime = dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA);
466 updateChannel(channelUID, state, panelTime);
468 channelUID = new ChannelUID(getThing().getUID(), PANEL_TIME_BROADCAST);
469 updateChannel(channelUID, 1, "");
471 case FireKeyAlarm: /* 621 */
473 case FireKeyRestored: /* 622 */
474 channelUID = new ChannelUID(getThing().getUID(), PANEL_FIRE_KEY_ALARM);
475 updateChannel(channelUID, state, "");
476 restorePartitionsInAlarm(dscAlarmCode);
478 case AuxiliaryKeyAlarm: /* 623 */
480 case AuxiliaryKeyRestored: /* 624 */
481 channelUID = new ChannelUID(getThing().getUID(), PANEL_AUX_KEY_ALARM);
482 updateChannel(channelUID, state, "");
483 restorePartitionsInAlarm(dscAlarmCode);
485 case PanicKeyAlarm: /* 625 */
487 case PanicKeyRestored: /* 626 */
488 channelUID = new ChannelUID(getThing().getUID(), PANEL_PANIC_KEY_ALARM);
489 updateChannel(channelUID, state, "");
490 restorePartitionsInAlarm(dscAlarmCode);
492 case AuxiliaryInputAlarm: /* 631 */
494 case AuxiliaryInputAlarmRestored: /* 632 */
495 channelUID = new ChannelUID(getThing().getUID(), PANEL_AUX_INPUT_ALARM);
496 updateChannel(channelUID, state, "");
497 restorePartitionsInAlarm(dscAlarmCode);
499 case TroubleLEDOn: /* 840 */
500 channelUID = new ChannelUID(getThing().getUID(), PANEL_TROUBLE_LED);
501 updateChannel(channelUID, 1, "");
503 case TroubleLEDOff: /* 841 */
504 channelUID = new ChannelUID(getThing().getUID(), PANEL_SERVICE_REQUIRED);
505 updateChannel(channelUID, 0, "");
507 channelUID = new ChannelUID(getThing().getUID(), PANEL_AC_TROUBLE);
508 updateChannel(channelUID, 0, "");
510 channelUID = new ChannelUID(getThing().getUID(), PANEL_TELEPHONE_TROUBLE);
511 updateChannel(channelUID, 0, "");
513 channelUID = new ChannelUID(getThing().getUID(), PANEL_FTC_TROUBLE);
514 updateChannel(channelUID, 0, "");
516 channelUID = new ChannelUID(getThing().getUID(), PANEL_ZONE_FAULT);
517 updateChannel(channelUID, 0, "");
519 channelUID = new ChannelUID(getThing().getUID(), PANEL_ZONE_TAMPER);
520 updateChannel(channelUID, 0, "");
522 channelUID = new ChannelUID(getThing().getUID(), PANEL_ZONE_LOW_BATTERY);
523 updateChannel(channelUID, 0, "");
525 channelUID = new ChannelUID(getThing().getUID(), PANEL_TIME_LOSS);
526 updateChannel(channelUID, 0, "");
528 channelUID = new ChannelUID(getThing().getUID(), PANEL_TROUBLE_LED);
529 updateChannel(channelUID, 0, "");
531 case PanelBatteryTrouble: /* 800 */
532 case PanelACTrouble: /* 802 */
533 case SystemBellTrouble: /* 806 */
534 case TLMLine1Trouble: /* 810 */
535 case TLMLine2Trouble: /* 812 */
536 case FTCTrouble: /* 814 */
537 case GeneralDeviceLowBattery: /* 821 */
538 case WirelessKeyLowBatteryTrouble: /* 825 */
539 case HandheldKeypadLowBatteryTrouble: /* 827 */
540 case GeneralSystemTamper: /* 829 */
541 case HomeAutomationTrouble: /* 831 */
542 case KeybusFault: /* 896 */
543 channelUID = new ChannelUID(getThing().getUID(), PANEL_TROUBLE_MESSAGE);
544 updateChannel(channelUID, 0,
545 dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DESCRIPTION));
547 case PanelBatteryTroubleRestore: /* 801 */
548 case PanelACRestore: /* 803 */
549 case SystemBellTroubleRestore: /* 807 */
550 case TLMLine1TroubleRestore: /* 811 */
551 case TLMLine2TroubleRestore: /* 813 */
552 case GeneralDeviceLowBatteryRestore: /* 822 */
553 case WirelessKeyLowBatteryTroubleRestore: /* 826 */
554 case HandheldKeypadLowBatteryTroubleRestore: /* 828 */
555 case GeneralSystemTamperRestore: /* 830 */
556 case HomeAutomationTroubleRestore: /* 832 */
557 case KeybusFaultRestore: /* 897 */
558 channelUID = new ChannelUID(getThing().getUID(), PANEL_TROUBLE_MESSAGE);
559 updateChannel(channelUID, 0, "");
561 case VerboseTroubleStatus: /* 849 */
562 verboseTroubleStatusHandler(event);
564 case CodeRequired: /* 900 */
565 dscAlarmBridgeHandler.sendCommand(DSCAlarmCode.CodeSend, getUserCode());