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