]> git.basschouten.com Git - openhab-addons.git/blob
ba4ef1f5394bd82c0a4c8228a5e99cfee17342b0
[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.state;
14
15 import java.io.UnsupportedEncodingException;
16 import java.util.Arrays;
17 import java.util.Calendar;
18 import java.util.GregorianCalendar;
19 import java.util.stream.IntStream;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.powermax.internal.message.PowermaxMessageConstants;
24 import org.openhab.binding.powermax.internal.message.PowermaxSendType;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 /**
29  * A class to store all the settings of the alarm system
30  *
31  * @author Laurent Garnier - Initial contribution
32  */
33 @NonNullByDefault
34 public class PowermaxPanelSettings {
35
36     /** Number of PGM and X10 devices managed by the system */
37     private static final int NB_PGM_X10_DEVICES = 16;
38
39     private final Logger logger = LoggerFactory.getLogger(PowermaxPanelSettings.class);
40
41     /** Raw buffers for settings */
42     private Byte[][] rawSettings;
43
44     private PowermaxPanelType panelType;
45     private String[] phoneNumbers;
46     private int bellTime;
47     private boolean silentPanic;
48     private boolean quickArm;
49     private boolean bypassEnabled;
50     private boolean partitionsEnabled;
51     private String @Nullable [] pinCodes;
52     private @Nullable String panelEprom;
53     private @Nullable String panelSoftware;
54     private @Nullable String panelSerial;
55     private PowermaxZoneSettings[] zoneSettings;
56     private PowermaxX10Settings[] x10Settings;
57     private boolean @Nullable [] keypad1wEnrolled;
58     private boolean @Nullable [] keypad2wEnrolled;
59     private boolean @Nullable [] sirensEnrolled;
60
61     /**
62      * Constructor
63      *
64      * @param defaultPanelType the default panel type to consider
65      */
66     public PowermaxPanelSettings(PowermaxPanelType defaultPanelType) {
67         rawSettings = new Byte[0x100][];
68         panelType = defaultPanelType;
69         phoneNumbers = new String[4];
70         bellTime = 4;
71         int zoneCnt = panelType.getWireless() + panelType.getWired();
72         zoneSettings = new PowermaxZoneSettings[zoneCnt];
73         x10Settings = new PowermaxX10Settings[NB_PGM_X10_DEVICES];
74     }
75
76     /**
77      * @return the panel type
78      */
79     public PowermaxPanelType getPanelType() {
80         return panelType;
81     }
82
83     /**
84      * @return the length of time the bell or siren sounds (in minutes)
85      */
86     public int getBellTime() {
87         return bellTime;
88     }
89
90     /**
91      * @return true if panic alarms are silent; false if audible
92      */
93     public boolean isSilentPanic() {
94         return silentPanic;
95     }
96
97     /**
98      * @return true if bypassing zones is enabled; false if not
99      */
100     public boolean isBypassEnabled() {
101         return bypassEnabled;
102     }
103
104     /**
105      * @return true if partitions usage is enabled; false if not
106      */
107     public boolean isPartitionsEnabled() {
108         return partitionsEnabled;
109     }
110
111     /**
112      * @return the panel EEPROM version
113      */
114     public @Nullable String getPanelEprom() {
115         return panelEprom;
116     }
117
118     /**
119      * @return the panel software version
120      */
121     public @Nullable String getPanelSoftware() {
122         return panelSoftware;
123     }
124
125     /**
126      * @return the panel serial ID
127      */
128     public @Nullable String getPanelSerial() {
129         return panelSerial;
130     }
131
132     /**
133      * @return the number of zones
134      */
135     public int getNbZones() {
136         return zoneSettings.length;
137     }
138
139     /**
140      * @return an integer stream for iterating over the range of zone numbers
141      */
142     public IntStream getZoneRange() {
143         return IntStream.rangeClosed(1, getNbZones());
144     }
145
146     /**
147      * Get the settings relative to a zone
148      *
149      * @param zone the zone index (from 1 to NumberOfZones)
150      *
151      * @return the settings of the zone
152      */
153     public @Nullable PowermaxZoneSettings getZoneSettings(int zone) {
154         return ((zone < 1) || (zone > zoneSettings.length)) ? null : zoneSettings[zone - 1];
155     }
156
157     /**
158      * Get a zone's display name
159      *
160      * @param zone the zone index (from 1 to NumberOfZones)
161      *
162      * @return the name of the zone
163      */
164     public @Nullable String getZoneName(int zone) {
165         PowermaxZoneSettings zoneSettings = getZoneSettings(zone);
166         return (zoneSettings == null) ? null : zoneSettings.getName();
167     }
168
169     /**
170      * Get a friendly display name for a zone, user, or device
171      * (any possible source for an event)
172      *
173      * @param zoneOrUser the zone, user, or device code
174      *
175      * @return the display name
176      */
177     public String getZoneOrUserName(int zoneOrUser) {
178         String zoneName = getZoneName(zoneOrUser);
179
180         if (zoneOrUser >= 1 && zoneOrUser <= zoneSettings.length && zoneName != null) {
181             return String.format("%s[%d]", zoneName, zoneOrUser);
182         } else {
183             return PowermaxMessageConstants.getZoneOrUser(zoneOrUser);
184         }
185     }
186
187     /**
188      * @return the number of PGM and X10 devices managed by the system
189      */
190     public int getNbPGMX10Devices() {
191         return NB_PGM_X10_DEVICES;
192     }
193
194     /**
195      * Get the settings relative to the PGM
196      *
197      * @return the settings of the PGM
198      */
199     public PowermaxX10Settings getPGMSettings() {
200         return x10Settings[0];
201     }
202
203     /**
204      * Get the settings relative to a X10 device
205      *
206      * @param idx the index (from 1 to 15)
207      *
208      * @return the settings of the X10 device
209      */
210     public @Nullable PowermaxX10Settings getX10Settings(int idx) {
211         return ((idx < 1) || (idx >= x10Settings.length)) ? null : x10Settings[idx];
212     }
213
214     /**
215      * @param idx the keypad index (first is 1)
216      *
217      * @return true if the 1 way keypad is enrolled; false if not
218      */
219     public boolean isKeypad1wEnrolled(int idx) {
220         boolean @Nullable [] localKeypad1wEnrolled = keypad1wEnrolled;
221         return ((localKeypad1wEnrolled == null) || (idx < 1) || (idx >= localKeypad1wEnrolled.length)) ? false
222                 : localKeypad1wEnrolled[idx - 1];
223     }
224
225     /**
226      * @param idx the keypad index (first is 1)
227      *
228      * @return true if the 2 way keypad is enrolled; false if not
229      */
230     public boolean isKeypad2wEnrolled(int idx) {
231         boolean @Nullable [] localKeypad2wEnrolled = keypad2wEnrolled;
232         return ((localKeypad2wEnrolled == null) || (idx < 1) || (idx >= localKeypad2wEnrolled.length)) ? false
233                 : localKeypad2wEnrolled[idx - 1];
234     }
235
236     /**
237      * @param idx the siren index (first is 1)
238      *
239      * @return true if the siren is enrolled; false if not
240      */
241     public boolean isSirenEnrolled(int idx) {
242         boolean @Nullable [] localSirensEnrolled = sirensEnrolled;
243         return ((localSirensEnrolled == null) || (idx < 1) || (idx >= localSirensEnrolled.length)) ? false
244                 : localSirensEnrolled[idx - 1];
245     }
246
247     /**
248      * @return the PIN code of the first user of an empty string if unknown (standard mode)
249      */
250     public String getFirstPinCode() {
251         String @Nullable [] localPinCodes = pinCodes;
252         return (localPinCodes == null || localPinCodes.length == 0) ? "" : localPinCodes[0];
253     }
254
255     public void updateRawSettings(byte[] data) {
256         if (data.length < 3) {
257             return;
258         }
259         int start = 0;
260         int end = data.length - 3;
261         int index = data[0] & 0x000000FF;
262         int page = data[1] & 0x000000FF;
263         int pageMin = page + (index + start) / 0x100;
264         int indexPageMin = (index + start) % 0x100;
265         int pageMax = page + (index + end) / 0x100;
266         int indexPageMax = (index + end) % 0x100;
267         index = 2;
268         for (int i = pageMin; i <= pageMax; i++) {
269             start = 0;
270             end = 0xFF;
271             if (i == pageMin) {
272                 start = indexPageMin;
273             }
274             if (i == pageMax) {
275                 end = indexPageMax;
276             }
277             if (rawSettings[i] == null) {
278                 rawSettings[i] = new Byte[0x100];
279             }
280             for (int j = start; j <= end; j++) {
281                 rawSettings[i][j] = data[index++];
282             }
283         }
284     }
285
286     private byte @Nullable [] readSettings(PowermaxSendType msgType, int start, int end) {
287         byte[] message = msgType.getMessage();
288         int page = message[2] & 0x000000FF;
289         int index = message[1] & 0x000000FF;
290         return readSettings(page, index + start, index + end);
291     }
292
293     private byte @Nullable [] readSettings(int page, int start, int end) {
294         int pageMin = page + start / 0x100;
295         int indexPageMin = start % 0x100;
296         int pageMax = page + end / 0x100;
297         int indexPageMax = end % 0x100;
298         int index = 0;
299         boolean missingData = false;
300         for (int i = pageMin; i <= pageMax; i++) {
301             int start2 = 0;
302             int end2 = 0xFF;
303             if (i == pageMin) {
304                 start2 = indexPageMin;
305             }
306             if (i == pageMax) {
307                 end2 = indexPageMax;
308             }
309             index += end2 - start2 + 1;
310             for (int j = start2; j <= end2; j++) {
311                 if ((rawSettings[i] == null) || (rawSettings[i][j] == null)) {
312                     missingData = true;
313                     break;
314                 }
315             }
316             if (missingData) {
317                 break;
318             }
319         }
320         if (missingData) {
321             logger.debug("readSettings({}, {}, {}): missing data", page, start, end);
322             return null;
323         }
324         byte[] result = new byte[index];
325         index = 0;
326         for (int i = pageMin; i <= pageMax; i++) {
327             int start2 = 0;
328             int end2 = 0xFF;
329             if (i == pageMin) {
330                 start2 = indexPageMin;
331             }
332             if (i == pageMax) {
333                 end2 = indexPageMax;
334             }
335             for (int j = start2; j <= end2; j++) {
336                 result[index++] = rawSettings[i][j];
337             }
338         }
339         return result;
340     }
341
342     private @Nullable String readSettingsAsString(PowermaxSendType msgType, int start, int end) {
343         byte[] message = msgType.getMessage();
344         int page = message[2] & 0x000000FF;
345         int index = message[1] & 0x000000FF;
346         String result = null;
347         byte[] data = readSettings(page, index + start, index + end);
348         if ((data != null) && ((data[0] & 0x000000FF) != 0x000000FF)) {
349             result = "";
350             for (int i = 0; i < data.length; i++) {
351                 boolean endStr = false;
352                 switch (data[i] & 0x000000FF) {
353                     case 0:
354                         endStr = true;
355                         break;
356                     case 1:
357                         result += "é";
358                         break;
359                     case 3:
360                         result += "è";
361                         break;
362                     case 5:
363                         result += "à";
364                         break;
365                     default:
366                         if ((data[i] & 0x000000FF) >= 0x20) {
367                             try {
368                                 result += new String(data, i, 1, "US-ASCII");
369                             } catch (UnsupportedEncodingException e) {
370                                 logger.debug("Unhandled character code {}", data[i]);
371                             }
372                         } else {
373                             logger.debug("Unhandled character code {}", data[i]);
374                         }
375                         break;
376                 }
377                 if (endStr) {
378                     break;
379                 }
380             }
381             result = result.trim();
382         }
383         return result;
384     }
385
386     /**
387      * Process and store all the panel settings from the raw buffers
388      *
389      * @param PowerlinkMode true if in Powerlink mode or false if in standard mode
390      * @param defaultPanelType the default panel type to consider if not found in the raw buffers
391      * @param timeSet the time in milliseconds used to set time and date; 0 if no sync time requested
392      *
393      * @return true if no problem encountered to get all the settings; false if not
394      */
395     public boolean process(boolean PowerlinkMode, PowermaxPanelType defaultPanelType, long timeSet) {
396         logger.debug("Process settings Powerlink {}", PowerlinkMode);
397
398         boolean result = true;
399         boolean result2;
400         byte[] data;
401
402         // Identify panel type
403         panelType = defaultPanelType;
404         if (PowerlinkMode) {
405             data = readSettings(PowermaxSendType.DL_SERIAL, 7, 7);
406             if (data != null) {
407                 try {
408                     panelType = PowermaxPanelType.fromCode(data[0]);
409                 } catch (IllegalArgumentException e) {
410                     logger.debug("Powermax alarm binding: unknwon panel type for code {}", data[0] & 0x000000FF);
411                     panelType = defaultPanelType;
412                 }
413             } else {
414                 logger.debug("Cannot get panel type");
415                 result = false;
416             }
417         }
418
419         int zoneCnt = panelType.getWireless() + panelType.getWired();
420         int customCnt = panelType.getCustomZones();
421         int userCnt = panelType.getUserCodes();
422         int partitionCnt = panelType.getPartitions();
423         int sirenCnt = panelType.getSirens();
424         int keypad1wCnt = panelType.getKeypads1w();
425         int keypad2wCnt = panelType.getKeypads2w();
426
427         phoneNumbers = new String[4];
428         bellTime = 4;
429         silentPanic = false;
430         quickArm = false;
431         bypassEnabled = false;
432         partitionsEnabled = false;
433         String[] localPinCodes = new String[userCnt];
434         panelEprom = null;
435         panelSoftware = null;
436         panelSerial = null;
437         zoneSettings = new PowermaxZoneSettings[zoneCnt];
438         x10Settings = new PowermaxX10Settings[NB_PGM_X10_DEVICES];
439         boolean[] localKeypad1wEnrolled = new boolean[keypad1wCnt];
440         boolean[] localKeypad2wEnrolled = new boolean[keypad2wCnt];
441         boolean[] localSirensEnrolled = new boolean[sirenCnt];
442
443         if (PowerlinkMode) {
444             // Check time and date
445             data = readSettings(PowermaxSendType.DL_TIME, 0, 5);
446             if (data != null) {
447                 GregorianCalendar cal = new GregorianCalendar();
448                 cal.set(Calendar.MILLISECOND, 0);
449                 cal.set(Calendar.SECOND, data[0] & 0x000000FF);
450                 cal.set(Calendar.MINUTE, data[1] & 0x000000FF);
451                 cal.set(Calendar.HOUR_OF_DAY, data[2] & 0x000000FF);
452                 cal.set(Calendar.DAY_OF_MONTH, data[3] & 0x000000FF);
453                 cal.set(Calendar.MONTH, (data[4] & 0x000000FF) - 1);
454                 cal.set(Calendar.YEAR, (data[5] & 0x000000FF) + 2000);
455                 long timeRead = cal.getTimeInMillis();
456                 logger.debug("Powermax alarm binding: time {}",
457                         String.format("%02d/%02d/%04d %02d:%02d:%02d", cal.get(Calendar.DAY_OF_MONTH),
458                                 cal.get(Calendar.MONTH) + 1, cal.get(Calendar.YEAR), cal.get(Calendar.HOUR_OF_DAY),
459                                 cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND)));
460
461                 // Check if time sync was OK
462                 if (timeSet > 0) {
463                     long delta = (timeRead - timeSet) / 1000;
464                     if (delta <= 5) {
465                         logger.debug("Powermax alarm binding: time sync OK (delta {} s)", delta);
466                     } else {
467                         logger.info("Powermax alarm binding: time sync failed ! (delta {} s)", delta);
468                     }
469                 }
470             } else {
471                 logger.debug("Cannot get time and date settings");
472                 result = false;
473             }
474
475             // Process zone names
476             result2 = true;
477             for (int i = 0; i < (26 + customCnt); i++) {
478                 String str = readSettingsAsString(PowermaxSendType.DL_ZONESTR, i * 16, (i + 1) * 16 - 1);
479                 if (str != null) {
480                     try {
481                         PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
482                         zoneName.setName(str);
483                     } catch (IllegalArgumentException e) {
484                         logger.debug("Zone id out of bounds {}", i);
485                     }
486                 } else {
487                     result2 = false;
488                 }
489             }
490             if (!result2) {
491                 logger.debug("Cannot get all zone names");
492                 result = false;
493             }
494
495             // Process communication settings
496             result2 = true;
497             for (int i = 0; i < phoneNumbers.length; i++) {
498                 data = readSettings(PowermaxSendType.DL_PHONENRS, 8 * i, 8 * i + 7);
499                 if (data != null) {
500                     for (int j = 0; j < 8; j++) {
501                         if ((data[j] & 0x000000FF) != 0x000000FF) {
502                             if (j == 0) {
503                                 phoneNumbers[i] = "";
504                             }
505                             if (phoneNumbers[i] != null) {
506                                 phoneNumbers[i] += String.format("%02X", data[j] & 0x000000FF);
507                             }
508                         }
509                     }
510                 } else {
511                     result2 = false;
512                 }
513             }
514             if (!result2) {
515                 logger.debug("Cannot get all communication settings");
516                 result = false;
517             }
518
519             // Process alarm settings
520             data = readSettings(PowermaxSendType.DL_COMMDEF, 0, 0x1B);
521             if (data != null) {
522                 bellTime = data[3] & 0x000000FF;
523                 silentPanic = (data[0x19] & 0x00000010) == 0x00000010;
524                 quickArm = (data[0x1A] & 0x00000008) == 0x00000008;
525                 bypassEnabled = (data[0x1B] & 0x000000C0) != 0;
526             } else {
527                 logger.debug("Cannot get alarm settings");
528                 result = false;
529             }
530
531             // Process user PIN codes
532             data = readSettings(
533                     panelType.isPowerMaster() ? PowermaxSendType.DL_MR_PINCODES : PowermaxSendType.DL_PINCODES, 0,
534                     2 * userCnt - 1);
535             if (data != null) {
536                 for (int i = 0; i < userCnt; i++) {
537                     localPinCodes[i] = String.format("%02X%02X", data[i * 2] & 0x000000FF,
538                             data[i * 2 + 1] & 0x000000FF);
539                 }
540             } else {
541                 logger.debug("Cannot get PIN codes");
542                 result = false;
543             }
544
545             // Process EEPROM version
546             panelEprom = readSettingsAsString(PowermaxSendType.DL_PANELFW, 0, 15);
547             if (panelEprom == null) {
548                 logger.debug("Cannot get EEPROM version");
549                 result = false;
550             }
551
552             // Process software version
553             panelSoftware = readSettingsAsString(PowermaxSendType.DL_PANELFW, 16, 31);
554             if (panelSoftware == null) {
555                 logger.debug("Cannot get software version");
556                 result = false;
557             }
558
559             // Process serial ID
560             panelSerial = "";
561             data = readSettings(PowermaxSendType.DL_SERIAL, 0, 5);
562             if (data != null) {
563                 for (int i = 0; i <= 5; i++) {
564                     if ((data[i] & 0x000000FF) != 0x000000FF) {
565                         panelSerial += String.format("%02X", data[i] & 0x000000FF);
566                     } else {
567                         panelSerial += ".";
568                     }
569                 }
570             } else {
571                 logger.debug("Cannot get serial ID");
572                 result = false;
573             }
574
575             // Check if partitions are enabled (only on panels that support partitions)
576             byte[] partitions = null;
577             if (partitionCnt > 1) {
578                 partitions = readSettings(PowermaxSendType.DL_PARTITIONS, 0, 0x10 + zoneCnt);
579                 if (partitions != null) {
580                     partitionsEnabled = (partitions[0] & 0x000000FF) == 1;
581                 } else {
582                     logger.debug("Cannot get partitions information");
583                     result = false;
584                 }
585                 if (!partitionsEnabled) {
586                     partitionCnt = 1;
587                 }
588             }
589
590             // Process zone settings
591             data = readSettings(PowermaxSendType.DL_ZONES, 0, zoneCnt * 4 - 1);
592             byte[] zoneNr = null;
593             byte[] dataMr = null;
594             if (panelType.isPowerMaster()) {
595                 zoneNr = readSettings(PowermaxSendType.DL_MR_ZONENAMES, 0, zoneCnt - 1);
596                 dataMr = readSettings(PowermaxSendType.DL_MR_ZONES, 0, zoneCnt * 10 - 2);
597             } else {
598                 zoneNr = readSettings(PowermaxSendType.DL_ZONENAMES, 0, zoneCnt - 1);
599             }
600             if ((data != null) && (zoneNr != null)) {
601                 byte[] zero3 = new byte[] { 0, 0, 0 };
602                 byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
603
604                 for (int i = 0; i < zoneCnt; i++) {
605                     String zoneName;
606                     try {
607                         PowermaxZoneName zone = PowermaxZoneName.fromId(zoneNr[i] & 0x0000001F);
608                         zoneName = zone.getName();
609                     } catch (IllegalArgumentException e) {
610                         logger.debug("Zone id out of bounds {}", zoneNr[i] & 0x0000001F);
611                         zoneName = null;
612                     }
613
614                     boolean zoneEnrolled;
615                     byte zoneInfo;
616                     String sensorTypeStr;
617                     if (panelType.isPowerMaster()) {
618                         zoneInfo = data[i];
619                         if (dataMr != null) {
620                             zoneEnrolled = !Arrays.equals(Arrays.copyOfRange(dataMr, i * 10 + 4, i * 10 + 9), zero5);
621                             byte sensorTypeCode = dataMr[i * 10 + 5];
622                             try {
623                                 PowermasterSensorType sensorType = PowermasterSensorType.fromCode(sensorTypeCode);
624                                 sensorTypeStr = sensorType.getLabel();
625                             } catch (IllegalArgumentException e) {
626                                 sensorTypeStr = null;
627                             }
628                         } else {
629                             zoneEnrolled = false;
630                             sensorTypeStr = null;
631                         }
632                     } else {
633                         zoneEnrolled = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
634                         zoneInfo = data[i * 4 + 3];
635                         byte sensorTypeCode = data[i * 4 + 2];
636                         try {
637                             PowermaxSensorType sensorType = PowermaxSensorType
638                                     .fromCode((byte) (sensorTypeCode & 0x0000000F));
639                             sensorTypeStr = sensorType.getLabel();
640                         } catch (IllegalArgumentException e) {
641                             sensorTypeStr = null;
642                         }
643                     }
644                     if (zoneEnrolled) {
645                         byte zoneType = (byte) (zoneInfo & 0x0000000F);
646                         byte zoneChime = (byte) ((zoneInfo >> 4) & 0x00000003);
647
648                         boolean[] part = new boolean[partitionCnt];
649                         if (partitionCnt > 1) {
650                             for (int j = 0; j < partitionCnt; j++) {
651                                 part[j] = (partitions != null) ? ((partitions[0x11 + i] & (1 << j)) != 0) : true;
652                             }
653                         } else {
654                             part[0] = true;
655                         }
656
657                         zoneSettings[i] = new PowermaxZoneSettings(zoneName, zoneType, zoneChime, sensorTypeStr, part);
658                     }
659                 }
660             } else {
661                 logger.debug("Cannot get zone settings");
662                 result = false;
663             }
664
665             data = readSettings(PowermaxSendType.DL_PGMX10, 0, 148);
666             zoneNr = readSettings(PowermaxSendType.DL_X10NAMES, 0, NB_PGM_X10_DEVICES - 2);
667             if ((data != null) && (zoneNr != null)) {
668                 for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
669                     boolean enabled = false;
670                     String zoneName = null;
671                     for (int j = 0; j <= 8; j++) {
672                         if (data[5 + i + j * 0x10] != 0) {
673                             enabled = true;
674                             break;
675                         }
676                     }
677                     if (i > 0) {
678                         try {
679                             PowermaxZoneName zone = PowermaxZoneName.fromId(zoneNr[i - 1] & 0x0000001F);
680                             zoneName = zone.getName();
681                         } catch (IllegalArgumentException e) {
682                             logger.debug("Zone id out of bounds {}", zoneNr[i - 1] & 0x0000001F);
683                             zoneName = null;
684                         }
685                     }
686                     x10Settings[i] = new PowermaxX10Settings(zoneName, enabled);
687                 }
688             } else {
689                 logger.debug("Cannot get PGM / X10 settings");
690                 result = false;
691             }
692
693             if (panelType.isPowerMaster()) {
694                 // Process 2 way keypad settings
695                 data = readSettings(PowermaxSendType.DL_MR_KEYPADS, 0, keypad2wCnt * 10 - 1);
696                 if (data != null) {
697                     byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
698
699                     for (int i = 0; i < keypad2wCnt; i++) {
700                         localKeypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9),
701                                 zero5);
702                     }
703                 } else {
704                     logger.debug("Cannot get 2 way keypad settings");
705                     result = false;
706                 }
707                 // Process siren settings
708                 data = readSettings(PowermaxSendType.DL_MR_SIRENS, 0, sirenCnt * 10 - 1);
709                 if (data != null) {
710                     byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
711
712                     for (int i = 0; i < sirenCnt; i++) {
713                         localSirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9),
714                                 zero5);
715                     }
716                 } else {
717                     logger.debug("Cannot get siren settings");
718                     result = false;
719                 }
720             } else {
721                 // Process 1 way keypad settings
722                 data = readSettings(PowermaxSendType.DL_1WKEYPAD, 0, keypad1wCnt * 4 - 1);
723                 if (data != null) {
724                     byte[] zero2 = new byte[] { 0, 0 };
725
726                     for (int i = 0; i < keypad1wCnt; i++) {
727                         localKeypad1wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 2), zero2);
728                     }
729                 } else {
730                     logger.debug("Cannot get 1 way keypad settings");
731                     result = false;
732                 }
733                 // Process 2 way keypad settings
734                 data = readSettings(PowermaxSendType.DL_2WKEYPAD, 0, keypad2wCnt * 4 - 1);
735                 if (data != null) {
736                     byte[] zero3 = new byte[] { 0, 0, 0 };
737
738                     for (int i = 0; i < keypad2wCnt; i++) {
739                         localKeypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
740                     }
741                 } else {
742                     logger.debug("Cannot get 2 way keypad settings");
743                     result = false;
744                 }
745                 // Process siren settings
746                 data = readSettings(PowermaxSendType.DL_SIRENS, 0, sirenCnt * 4 - 1);
747                 if (data != null) {
748                     byte[] zero3 = new byte[] { 0, 0, 0 };
749
750                     for (int i = 0; i < sirenCnt; i++) {
751                         localSirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
752                     }
753                 } else {
754                     logger.debug("Cannot get siren settings");
755                     result = false;
756                 }
757             }
758         } else {
759             if (!partitionsEnabled) {
760                 partitionCnt = 1;
761             }
762             boolean[] part = new boolean[partitionCnt];
763             for (int j = 0; j < partitionCnt; j++) {
764                 part[j] = true;
765             }
766             for (int i = 0; i < zoneCnt; i++) {
767                 zoneSettings[i] = new PowermaxZoneSettings(null, (byte) 0xFF, (byte) 0xFF, null, part);
768             }
769             for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
770                 x10Settings[i] = new PowermaxX10Settings(null, true);
771             }
772         }
773
774         pinCodes = localPinCodes;
775         keypad1wEnrolled = localKeypad1wEnrolled;
776         keypad2wEnrolled = localKeypad2wEnrolled;
777         sirensEnrolled = localSirensEnrolled;
778
779         return result;
780     }
781
782     /**
783      * Update the name of a zone
784      *
785      * @param zoneIdx the zone index (first zone is index 1)
786      * @param zoneNameIdx the index in the table of zone names
787      */
788     public void updateZoneName(int zoneIdx, byte zoneNameIdx) {
789         PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
790         if (zone != null) {
791             String name;
792             try {
793                 PowermaxZoneName zoneName = PowermaxZoneName.fromId(zoneNameIdx & 0x0000001F);
794                 name = zoneName.getName();
795             } catch (IllegalArgumentException e) {
796                 logger.debug("Zone id out of bounds {}", zoneNameIdx & 0x0000001F);
797                 name = null;
798             }
799             zone.setName(name);
800         }
801     }
802
803     /**
804      * Update the type of a zone
805      *
806      * @param zoneIdx the zone index (first zone is index 1)
807      * @param zoneInfo the zone info as an internal code
808      */
809     public void updateZoneInfo(int zoneIdx, int zoneInfo) {
810         PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
811         if (zone != null) {
812             zone.setType((byte) (zoneInfo & 0x0000000F));
813         }
814     }
815
816     public String getInfo() {
817         String str = "\nPanel is of type " + panelType.getLabel();
818
819         int zoneCnt = panelType.getWireless() + panelType.getWired();
820         int partitionCnt = panelType.getPartitions();
821         int sirenCnt = panelType.getSirens();
822         int keypad1wCnt = panelType.getKeypads1w();
823         int keypad2wCnt = panelType.getKeypads2w();
824         // int customCnt = panelType.getCustomZones();
825
826         if (!partitionsEnabled) {
827             partitionCnt = 1;
828         }
829
830         // for (int i = 0; i < (26 + customCnt); i++) {
831         // String name;
832         // try {
833         // PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
834         // name = zoneName.getName();
835         // } catch (IllegalArgumentException e) {
836         // logger.debug("Zone id out of bounds {}", i);
837         // name = null;
838         // }
839         // str += String.format("\nZone name %d; %s", i + 1, name);
840         // }
841         for (int i = 0; i < phoneNumbers.length; i++) {
842             if (phoneNumbers[i] != null) {
843                 str += String.format("\nPhone number %d: %s", i + 1, phoneNumbers[i]);
844             }
845         }
846         str += String.format("\nBell time: %d minutes", bellTime);
847         str += String.format("\nSilent panic: %s", silentPanic ? "enabled" : "disabled");
848         str += String.format("\nQuick arm: %s", quickArm ? "enabled" : "disabled");
849         str += String.format("\nZone bypass: %s", bypassEnabled ? "enabled" : "disabled");
850         str += String.format("\nEPROM: %s", (panelEprom != null) ? panelEprom : "Undefined");
851         str += String.format("\nSW: %s", (panelSoftware != null) ? panelSoftware : "Undefined");
852         str += String.format("\nSerial: %s", (panelSerial != null) ? panelSerial : "Undefined");
853         str += String.format("\nUse partitions: %s", partitionsEnabled ? "enabled" : "disabled");
854         str += String.format("\nNumber of partitions: %d", partitionCnt);
855         for (int i = 0; i < zoneCnt; i++) {
856             if (zoneSettings[i] != null) {
857                 String partStr = "";
858                 for (int j = 1; j <= partitionCnt; j++) {
859                     if (zoneSettings[i].isInPartition(j)) {
860                         partStr += j + " ";
861                     }
862                 }
863                 str += String.format("\nZone %d %s: %s (chime = %s; sensor type = %s; partitions = %s)", i + 1,
864                         zoneSettings[i].getName(), zoneSettings[i].getType(), zoneSettings[i].getChime(),
865                         zoneSettings[i].getSensorType(), partStr);
866             }
867         }
868         for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
869             if (x10Settings[i] != null && x10Settings[i].isEnabled()) {
870                 str += String.format("\n%s: %s enabled", (i == 0) ? "PGM" : ("X10 " + i),
871                         (x10Settings[i].getName() != null) ? x10Settings[i].getName() : "");
872             }
873         }
874         for (int i = 1; i <= sirenCnt; i++) {
875             if (isSirenEnrolled(i)) {
876                 str += String.format("\nSiren %d enrolled", i);
877             }
878         }
879         for (int i = 1; i <= keypad1wCnt; i++) {
880             if (isKeypad1wEnrolled(i)) {
881                 str += String.format("\nKeypad 1w %d enrolled", i);
882             }
883         }
884         for (int i = 1; i <= keypad2wCnt; i++) {
885             if (isKeypad2wEnrolled(i)) {
886                 str += String.format("\nKeypad 2w %d enrolled", i);
887             }
888         }
889         return "Powermax alarm binding:" + str;
890     }
891 }