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