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