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