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