]> git.basschouten.com Git - openhab-addons.git/blob
405c2b97d705f799779b2ea1c3127c80fa4591bb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.powermax.internal.handler;
14
15 import static org.openhab.binding.powermax.internal.PowermaxBindingConstants.*;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.EventObject;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.concurrent.CopyOnWriteArrayList;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.openhab.binding.powermax.internal.config.PowermaxIpConfiguration;
27 import org.openhab.binding.powermax.internal.config.PowermaxSerialConfiguration;
28 import org.openhab.binding.powermax.internal.discovery.PowermaxDiscoveryService;
29 import org.openhab.binding.powermax.internal.message.PowermaxCommManager;
30 import org.openhab.binding.powermax.internal.message.PowermaxSendType;
31 import org.openhab.binding.powermax.internal.state.PowermaxArmMode;
32 import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
33 import org.openhab.binding.powermax.internal.state.PowermaxPanelSettingsListener;
34 import org.openhab.binding.powermax.internal.state.PowermaxPanelType;
35 import org.openhab.binding.powermax.internal.state.PowermaxState;
36 import org.openhab.binding.powermax.internal.state.PowermaxStateContainer.Value;
37 import org.openhab.binding.powermax.internal.state.PowermaxStateEvent;
38 import org.openhab.binding.powermax.internal.state.PowermaxStateEventListener;
39 import org.openhab.core.i18n.TimeZoneProvider;
40 import org.openhab.core.io.transport.serial.SerialPortManager;
41 import org.openhab.core.library.types.OnOffType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.thing.Bridge;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.binding.BaseBridgeHandler;
49 import org.openhab.core.thing.binding.ThingHandlerService;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.RefreshType;
52 import org.openhab.core.types.UnDefType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * The {@link PowermaxBridgeHandler} is responsible for handling commands, which are
58  * sent to one of the channels.
59  *
60  * @author Laurent Garnier - Initial contribution
61  */
62 public class PowermaxBridgeHandler extends BaseBridgeHandler implements PowermaxStateEventListener {
63
64     private final Logger logger = LoggerFactory.getLogger(PowermaxBridgeHandler.class);
65     private final SerialPortManager serialPortManager;
66     private final TimeZoneProvider timeZoneProvider;
67
68     private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
69     private static final long FIVE_MINUTES = TimeUnit.MINUTES.toMillis(5);
70
71     /** Default delay in milliseconds to reset a motion detection */
72     private static final long DEFAULT_MOTION_OFF_DELAY = TimeUnit.MINUTES.toMillis(3);
73
74     private static final int NB_EVENT_LOG = 10;
75
76     private static final PowermaxPanelType DEFAULT_PANEL_TYPE = PowermaxPanelType.POWERMAX_PRO;
77
78     private static final int JOB_REPEAT = 20;
79
80     private static final int MAX_DOWNLOAD_ATTEMPTS = 3;
81
82     private ScheduledFuture<?> globalJob;
83
84     private List<PowermaxPanelSettingsListener> listeners = new CopyOnWriteArrayList<>();
85
86     /** The delay in milliseconds to reset a motion detection */
87     private long motionOffDelay;
88
89     /** The PIN code to use for arming/disarming the Powermax alarm system from openHAB */
90     private String pinCode;
91
92     /** Force the standard mode rather than trying using the Powerlink mode */
93     private boolean forceStandardMode;
94
95     /** The object to store the current state of the Powermax alarm system */
96     private PowermaxState currentState;
97
98     /** The object in charge of the communication with the Powermax alarm system */
99     private PowermaxCommManager commManager;
100
101     private int remainingDownloadAttempts;
102
103     public PowermaxBridgeHandler(Bridge thing, SerialPortManager serialPortManager, TimeZoneProvider timeZoneProvider) {
104         super(thing);
105         this.serialPortManager = serialPortManager;
106         this.timeZoneProvider = timeZoneProvider;
107     }
108
109     @Override
110     public Collection<Class<? extends ThingHandlerService>> getServices() {
111         return Collections.singleton(PowermaxDiscoveryService.class);
112     }
113
114     public PowermaxState getCurrentState() {
115         return currentState;
116     }
117
118     public PowermaxPanelSettings getPanelSettings() {
119         return (commManager == null) ? null : commManager.getPanelSettings();
120     }
121
122     @Override
123     public void initialize() {
124         logger.debug("initializing handler for thing {}", getThing().getUID());
125
126         commManager = null;
127
128         String threadName = "OH-binding-" + getThing().getUID().getAsString();
129
130         String errorMsg = null;
131         if (getThing().getThingTypeUID().equals(BRIDGE_TYPE_SERIAL)) {
132             errorMsg = initializeBridgeSerial(getConfigAs(PowermaxSerialConfiguration.class), threadName);
133         } else if (getThing().getThingTypeUID().equals(BRIDGE_TYPE_IP)) {
134             errorMsg = initializeBridgeIp(getConfigAs(PowermaxIpConfiguration.class), threadName);
135         } else {
136             errorMsg = "Unexpected thing type " + getThing().getThingTypeUID();
137         }
138
139         if (errorMsg == null) {
140             if (globalJob == null || globalJob.isCancelled()) {
141                 // Delay the startup in case the handler is restarted immediately
142                 globalJob = scheduler.scheduleWithFixedDelay(() -> {
143                     try {
144                         logger.trace("Powermax job...");
145                         updateMotionSensorState();
146                         updateRingingState();
147                         if (isConnected()) {
148                             checkKeepAlive();
149                             commManager.retryDownloadSetup(remainingDownloadAttempts);
150                         } else {
151                             tryReconnect();
152                         }
153                     } catch (Exception e) {
154                         logger.warn("Exception in scheduled job: {}", e.getMessage(), e);
155                     }
156                 }, 10, JOB_REPEAT, TimeUnit.SECONDS);
157             }
158         } else {
159             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
160         }
161     }
162
163     private String initializeBridgeSerial(PowermaxSerialConfiguration config, String threadName) {
164         String errorMsg = null;
165         if (config.serialPort != null && !config.serialPort.trim().isEmpty()
166                 && !config.serialPort.trim().startsWith("rfc2217")) {
167             motionOffDelay = getMotionOffDelaySetting(config.motionOffDelay, DEFAULT_MOTION_OFF_DELAY);
168             boolean allowArming = getBooleanSetting(config.allowArming, false);
169             boolean allowDisarming = getBooleanSetting(config.allowDisarming, false);
170             pinCode = config.pinCode;
171             forceStandardMode = getBooleanSetting(config.forceStandardMode, false);
172             PowermaxPanelType panelType = getPanelTypeSetting(config.panelType, DEFAULT_PANEL_TYPE);
173             boolean autoSyncTime = getBooleanSetting(config.autoSyncTime, false);
174
175             PowermaxArmMode.DISARMED.setAllowedCommand(allowDisarming);
176             PowermaxArmMode.ARMED_HOME.setAllowedCommand(allowArming);
177             PowermaxArmMode.ARMED_AWAY.setAllowedCommand(allowArming);
178             PowermaxArmMode.ARMED_HOME_INSTANT.setAllowedCommand(allowArming);
179             PowermaxArmMode.ARMED_AWAY_INSTANT.setAllowedCommand(allowArming);
180             PowermaxArmMode.ARMED_NIGHT.setAllowedCommand(allowArming);
181             PowermaxArmMode.ARMED_NIGHT_INSTANT.setAllowedCommand(allowArming);
182
183             commManager = new PowermaxCommManager(config.serialPort, panelType, forceStandardMode, autoSyncTime,
184                     serialPortManager, threadName, timeZoneProvider);
185         } else {
186             if (config.serialPort != null && config.serialPort.trim().startsWith("rfc2217")) {
187                 errorMsg = "Please use the IP Connection thing type for a serial over IP connection.";
188             } else {
189                 errorMsg = "serialPort setting must be defined in thing configuration";
190             }
191         }
192         return errorMsg;
193     }
194
195     private String initializeBridgeIp(PowermaxIpConfiguration config, String threadName) {
196         String errorMsg = null;
197         if (config.ip != null && !config.ip.trim().isEmpty() && config.tcpPort != null) {
198             motionOffDelay = getMotionOffDelaySetting(config.motionOffDelay, DEFAULT_MOTION_OFF_DELAY);
199             boolean allowArming = getBooleanSetting(config.allowArming, false);
200             boolean allowDisarming = getBooleanSetting(config.allowDisarming, false);
201             pinCode = config.pinCode;
202             forceStandardMode = getBooleanSetting(config.forceStandardMode, false);
203             PowermaxPanelType panelType = getPanelTypeSetting(config.panelType, DEFAULT_PANEL_TYPE);
204             boolean autoSyncTime = getBooleanSetting(config.autoSyncTime, false);
205
206             PowermaxArmMode.DISARMED.setAllowedCommand(allowDisarming);
207             PowermaxArmMode.ARMED_HOME.setAllowedCommand(allowArming);
208             PowermaxArmMode.ARMED_AWAY.setAllowedCommand(allowArming);
209             PowermaxArmMode.ARMED_HOME_INSTANT.setAllowedCommand(allowArming);
210             PowermaxArmMode.ARMED_AWAY_INSTANT.setAllowedCommand(allowArming);
211             PowermaxArmMode.ARMED_NIGHT.setAllowedCommand(allowArming);
212             PowermaxArmMode.ARMED_NIGHT_INSTANT.setAllowedCommand(allowArming);
213
214             commManager = new PowermaxCommManager(config.ip, config.tcpPort, panelType, forceStandardMode, autoSyncTime,
215                     threadName, timeZoneProvider);
216         } else {
217             errorMsg = "ip and port settings must be defined in thing configuration";
218         }
219         return errorMsg;
220     }
221
222     @Override
223     public void dispose() {
224         logger.debug("Handler disposed for thing {}", getThing().getUID());
225         if (globalJob != null && !globalJob.isCancelled()) {
226             globalJob.cancel(true);
227             globalJob = null;
228         }
229         closeConnection();
230         commManager = null;
231         super.dispose();
232     }
233
234     /*
235      * Set the state of items linked to motion sensors to OFF when the last trip is older
236      * than the value defined by the variable motionOffDelay
237      */
238     private void updateMotionSensorState() {
239         long now = System.currentTimeMillis();
240         if (currentState != null) {
241             boolean update = false;
242             PowermaxState updateState = commManager.createNewState();
243             PowermaxPanelSettings panelSettings = getPanelSettings();
244             for (int i = 1; i <= panelSettings.getNbZones(); i++) {
245                 if (panelSettings.getZoneSettings(i) != null && panelSettings.getZoneSettings(i).isMotionSensor()
246                         && currentState.getZone(i).isLastTripBeforeTime(now - motionOffDelay)) {
247                     update = true;
248                     updateState.getZone(i).tripped.setValue(false);
249                 }
250             }
251             if (update) {
252                 updateChannelsFromAlarmState(TRIPPED, updateState);
253                 currentState.merge(updateState);
254             }
255         }
256     }
257
258     /**
259      * Turn off the Ringing flag when the bell time expires
260      */
261     private void updateRingingState() {
262         if (currentState != null && Boolean.TRUE.equals(currentState.ringing.getValue())) {
263             long now = System.currentTimeMillis();
264             long bellTime = getPanelSettings().getBellTime() * ONE_MINUTE;
265
266             if ((currentState.ringingSince.getValue() + bellTime) < now) {
267                 PowermaxState updateState = commManager.createNewState();
268                 updateState.ringing.setValue(false);
269                 updateChannelsFromAlarmState(RINGING, updateState);
270                 currentState.merge(updateState);
271             }
272         }
273     }
274
275     /*
276      * Check that we're actively communicating with the panel
277      */
278     private void checkKeepAlive() {
279         long now = System.currentTimeMillis();
280         if (Boolean.TRUE.equals(currentState.powerlinkMode.getValue())
281                 && (currentState.lastKeepAlive.getValue() != null)
282                 && ((now - currentState.lastKeepAlive.getValue()) > ONE_MINUTE)) {
283             // In Powerlink mode: let Powermax know we are alive
284             commManager.sendRestoreMessage();
285             currentState.lastKeepAlive.setValue(now);
286         } else if (!Boolean.TRUE.equals(currentState.downloadMode.getValue())
287                 && (currentState.lastMessageTime.getValue() != null)
288                 && ((now - currentState.lastMessageTime.getValue()) > FIVE_MINUTES)) {
289             // In Standard mode: ping the panel every so often to detect disconnects
290             commManager.sendMessage(PowermaxSendType.STATUS);
291         }
292     }
293
294     private void tryReconnect() {
295         logger.info("Trying to connect or reconnect...");
296         closeConnection();
297         currentState = commManager.createNewState();
298         try {
299             openConnection();
300             logger.debug("openConnection(): connected");
301             updateStatus(ThingStatus.ONLINE);
302             updateChannelsFromAlarmState(currentState);
303             if (forceStandardMode) {
304                 currentState.powerlinkMode.setValue(false);
305                 updateChannelsFromAlarmState(MODE, currentState);
306                 processPanelSettings();
307             } else {
308                 commManager.startDownload();
309             }
310         } catch (Exception e) {
311             logger.debug("openConnection(): {}", e.getMessage(), e);
312             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
313             setAllChannelsOffline();
314         }
315     }
316
317     /**
318      * Open a TCP or Serial connection to the Powermax Alarm Panel
319      *
320      * @return true if the connection has been opened
321      */
322     private synchronized void openConnection() throws Exception {
323         if (commManager != null) {
324             commManager.addEventListener(this);
325             commManager.open();
326         }
327         remainingDownloadAttempts = MAX_DOWNLOAD_ATTEMPTS;
328     }
329
330     /**
331      * Close TCP or Serial connection to the Powermax Alarm Panel and remove the Event Listener
332      */
333     private synchronized void closeConnection() {
334         if (commManager != null) {
335             commManager.close();
336             commManager.removeEventListener(this);
337         }
338         logger.debug("closeConnection(): disconnected");
339     }
340
341     private boolean isConnected() {
342         return commManager == null ? false : commManager.isConnected();
343     }
344
345     @Override
346     public void handleCommand(ChannelUID channelUID, Command command) {
347         logger.debug("Received command {} from channel {}", command, channelUID.getId());
348
349         if (command instanceof RefreshType) {
350             updateChannelsFromAlarmState(channelUID.getId(), currentState);
351         } else {
352             switch (channelUID.getId()) {
353                 case ARM_MODE:
354                     try {
355                         PowermaxArmMode armMode = PowermaxArmMode.fromShortName(command.toString());
356                         armCommand(armMode);
357                     } catch (IllegalArgumentException e) {
358                         logger.debug("Powermax alarm binding: invalid command {}", command);
359                     }
360                     break;
361                 case SYSTEM_ARMED:
362                     if (command instanceof OnOffType) {
363                         armCommand(
364                                 command.equals(OnOffType.ON) ? PowermaxArmMode.ARMED_AWAY : PowermaxArmMode.DISARMED);
365                     } else {
366                         logger.debug("Command of type {} while OnOffType is expected. Command is ignored.",
367                                 command.getClass().getSimpleName());
368                     }
369                     break;
370                 case PGM_STATUS:
371                     pgmCommand(command);
372                     break;
373                 case UPDATE_EVENT_LOGS:
374                     downloadEventLog();
375                     break;
376                 case DOWNLOAD_SETUP:
377                     downloadSetup();
378                     break;
379                 default:
380                     logger.debug("No available command for channel {}. Command is ignored.", channelUID.getId());
381                     break;
382             }
383         }
384     }
385
386     private void armCommand(PowermaxArmMode armMode) {
387         if (!isConnected()) {
388             logger.debug("Powermax alarm binding not connected. Arm command is ignored.");
389         } else {
390             commManager.requestArmMode(armMode,
391                     Boolean.TRUE.equals(currentState.powerlinkMode.getValue()) ? getPanelSettings().getFirstPinCode()
392                             : pinCode);
393         }
394     }
395
396     private void pgmCommand(Command command) {
397         if (!isConnected()) {
398             logger.debug("Powermax alarm binding not connected. PGM command is ignored.");
399         } else {
400             commManager.sendPGMX10(command, null);
401         }
402     }
403
404     public void x10Command(Byte deviceNr, Command command) {
405         if (!isConnected()) {
406             logger.debug("Powermax alarm binding not connected. X10 command is ignored.");
407         } else {
408             commManager.sendPGMX10(command, deviceNr);
409         }
410     }
411
412     public void zoneBypassed(byte zoneNr, boolean bypassed) {
413         if (!isConnected()) {
414             logger.debug("Powermax alarm binding not connected. Zone bypass command is ignored.");
415         } else if (!Boolean.TRUE.equals(currentState.powerlinkMode.getValue())) {
416             logger.debug("Powermax alarm binding: Bypass option only supported in Powerlink mode");
417         } else if (!getPanelSettings().isBypassEnabled()) {
418             logger.debug("Powermax alarm binding: Bypass option not enabled in panel settings");
419         } else {
420             commManager.sendZoneBypass(bypassed, zoneNr, getPanelSettings().getFirstPinCode());
421         }
422     }
423
424     private void downloadEventLog() {
425         if (!isConnected()) {
426             logger.debug("Powermax alarm binding not connected. Event logs command is ignored.");
427         } else {
428             commManager.requestEventLog(
429                     Boolean.TRUE.equals(currentState.powerlinkMode.getValue()) ? getPanelSettings().getFirstPinCode()
430                             : pinCode);
431         }
432     }
433
434     public void downloadSetup() {
435         if (!isConnected()) {
436             logger.debug("Powermax alarm binding not connected. Download setup command is ignored.");
437         } else if (!Boolean.TRUE.equals(currentState.powerlinkMode.getValue())) {
438             logger.debug("Powermax alarm binding: download setup only supported in Powerlink mode");
439         } else if (commManager.isDownloadRunning()) {
440             logger.debug("Powermax alarm binding: download setup not started as one is in progress");
441         } else {
442             commManager.startDownload();
443             if (currentState.lastKeepAlive.getValue() != null) {
444                 currentState.lastKeepAlive.setValue(System.currentTimeMillis());
445             }
446         }
447     }
448
449     public String getInfoSetup() {
450         return (getPanelSettings() == null) ? "" : getPanelSettings().getInfo();
451     }
452
453     @Override
454     public void onNewStateEvent(EventObject event) {
455         PowermaxStateEvent stateEvent = (PowermaxStateEvent) event;
456         PowermaxState updateState = stateEvent.getState();
457
458         if (Boolean.TRUE.equals(currentState.powerlinkMode.getValue())
459                 && Boolean.TRUE.equals(updateState.downloadSetupRequired.getValue())) {
460             // After Enrolling Powerlink or if a reset is required
461             logger.debug("Powermax alarm binding: Reset");
462             commManager.startDownload();
463             updateState.downloadSetupRequired.setValue(false);
464             if (currentState.lastKeepAlive.getValue() != null) {
465                 currentState.lastKeepAlive.setValue(System.currentTimeMillis());
466             }
467         } else if (Boolean.FALSE.equals(currentState.powerlinkMode.getValue())
468                 && updateState.lastKeepAlive.getValue() != null) {
469             // Were are in standard mode but received a keep alive message
470             // so we switch in PowerLink mode
471             logger.debug("Powermax alarm binding: Switching to Powerlink mode");
472             commManager.startDownload();
473         }
474
475         boolean doProcessSettings = (updateState.powerlinkMode.getValue() != null);
476
477         getPanelSettings().getZoneRange().forEach(i -> {
478             if (Boolean.TRUE.equals(updateState.getZone(i).armed.getValue())
479                     && Boolean.TRUE.equals(currentState.getZone(i).bypassed.getValue())) {
480                 updateState.getZone(i).armed.setValue(false);
481             }
482         });
483
484         updateState.keepOnlyDifferencesWith(currentState);
485         updateChannelsFromAlarmState(updateState);
486         currentState.merge(updateState);
487
488         PowermaxPanelSettings panelSettings = getPanelSettings();
489         if (!updateState.getUpdatedZoneNames().isEmpty()) {
490             for (Integer zoneIdx : updateState.getUpdatedZoneNames().keySet()) {
491                 if (panelSettings.getZoneSettings(zoneIdx) != null) {
492                     for (PowermaxPanelSettingsListener listener : listeners) {
493                         listener.onZoneSettingsUpdated(zoneIdx, panelSettings);
494                     }
495                 }
496             }
497         }
498
499         if (doProcessSettings) {
500             // There is a change of mode (standard or Powerlink)
501             processPanelSettings();
502             commManager.exitDownload();
503         }
504     }
505
506     @Override
507     public void onCommunicationFailure(String message) {
508         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
509         setAllChannelsOffline();
510     }
511
512     private void processPanelSettings() {
513         if (commManager.processPanelSettings(Boolean.TRUE.equals(currentState.powerlinkMode.getValue()))) {
514             for (PowermaxPanelSettingsListener listener : listeners) {
515                 listener.onPanelSettingsUpdated(getPanelSettings());
516             }
517             remainingDownloadAttempts = 0;
518         } else {
519             logger.info("Powermax alarm binding: setup download failed!");
520             for (PowermaxPanelSettingsListener listener : listeners) {
521                 listener.onPanelSettingsUpdated(null);
522             }
523             remainingDownloadAttempts--;
524         }
525         updatePropertiesFromPanelSettings();
526         if (Boolean.TRUE.equals(currentState.powerlinkMode.getValue())) {
527             logger.info("Powermax alarm binding: running in Powerlink mode");
528             commManager.sendRestoreMessage();
529         } else {
530             logger.info("Powermax alarm binding: running in Standard mode");
531             commManager.getInfosWhenInStandardMode();
532         }
533     }
534
535     /**
536      * Update channels to match a new alarm system state
537      *
538      * @param state: the alarm system state
539      */
540     private void updateChannelsFromAlarmState(PowermaxState state) {
541         updateChannelsFromAlarmState(null, state);
542     }
543
544     /**
545      * Update channels to match a new alarm system state
546      *
547      * @param channel: filter on a particular channel; if null, consider all channels
548      * @param state: the alarm system state
549      */
550     private synchronized void updateChannelsFromAlarmState(String channel, PowermaxState state) {
551         if (state == null || !isConnected()) {
552             return;
553         }
554
555         for (Value<?> value : state.getValues()) {
556             String vChannel = value.getChannel();
557
558             if (((channel == null) || channel.equals(vChannel)) && (vChannel != null) && isLinked(vChannel)
559                     && (value.getValue() != null)) {
560                 updateState(vChannel, value.getState());
561             }
562         }
563
564         for (int i = 1; i <= NB_EVENT_LOG; i++) {
565             String channel2 = String.format(EVENT_LOG, i);
566             if (((channel == null) || channel.equals(channel2)) && isLinked(channel2)
567                     && (state.getEventLog(i) != null)) {
568                 updateState(channel2, new StringType(state.getEventLog(i)));
569             }
570         }
571
572         for (Thing thing : getThing().getThings()) {
573             if (thing.getHandler() != null) {
574                 PowermaxThingHandler handler = (PowermaxThingHandler) thing.getHandler();
575                 if (handler != null) {
576                     if (thing.getThingTypeUID().equals(THING_TYPE_ZONE)) {
577
578                         // All of the zone state objects will have the same list of values.
579                         // The use of getZone(1) here is just to get any PowermaxZoneState
580                         // and use it to get the list of zone channels.
581
582                         for (Value<?> value : state.getZone(1).getValues()) {
583                             String channelId = value.getChannel();
584                             if ((channelId != null) && ((channel == null) || channel.equals(channelId))) {
585                                 handler.updateChannelFromAlarmState(channelId, state);
586                             }
587                         }
588                     } else if (thing.getThingTypeUID().equals(THING_TYPE_X10)) {
589                         if ((channel == null) || channel.equals(X10_STATUS)) {
590                             handler.updateChannelFromAlarmState(X10_STATUS, state);
591                         }
592                     }
593                 }
594             }
595         }
596     }
597
598     /**
599      * Update all channels to an UNDEF state to indicate that communication with the panel is offline
600      */
601     private synchronized void setAllChannelsOffline() {
602         getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.UNDEF));
603     }
604
605     /**
606      * Update properties to match the alarm panel settings
607      */
608     private void updatePropertiesFromPanelSettings() {
609         String value;
610         Map<String, String> properties = editProperties();
611         PowermaxPanelSettings panelSettings = getPanelSettings();
612         value = (panelSettings.getPanelType() != null) ? panelSettings.getPanelType().getLabel() : null;
613         if (value != null && !value.isEmpty()) {
614             properties.put(Thing.PROPERTY_MODEL_ID, value);
615         }
616         value = panelSettings.getPanelSerial();
617         if (value != null && !value.isEmpty()) {
618             properties.put(Thing.PROPERTY_SERIAL_NUMBER, value);
619         }
620         value = panelSettings.getPanelEprom();
621         if (value != null && !value.isEmpty()) {
622             properties.put(Thing.PROPERTY_HARDWARE_VERSION, value);
623         }
624         value = panelSettings.getPanelSoftware();
625         if (value != null && !value.isEmpty()) {
626             properties.put(Thing.PROPERTY_FIRMWARE_VERSION, value);
627         }
628         updateProperties(properties);
629     }
630
631     public boolean registerPanelSettingsListener(PowermaxPanelSettingsListener listener) {
632         boolean inList = true;
633         if (!listeners.contains(listener)) {
634             inList = listeners.add(listener);
635         }
636         return inList;
637     }
638
639     public boolean unregisterPanelSettingsListener(PowermaxPanelSettingsListener listener) {
640         return listeners.remove(listener);
641     }
642
643     private boolean getBooleanSetting(Boolean value, boolean defaultValue) {
644         return value != null ? value.booleanValue() : defaultValue;
645     }
646
647     private long getMotionOffDelaySetting(Integer value, long defaultValue) {
648         return value != null ? value.intValue() * ONE_MINUTE : defaultValue;
649     }
650
651     private PowermaxPanelType getPanelTypeSetting(String value, PowermaxPanelType defaultValue) {
652         PowermaxPanelType result;
653         if (value != null) {
654             try {
655                 result = PowermaxPanelType.fromLabel(value);
656             } catch (IllegalArgumentException e) {
657                 result = defaultValue;
658                 logger.debug("Powermax alarm binding: panel type not configured correctly");
659             }
660         } else {
661             result = defaultValue;
662         }
663         return result;
664     }
665 }