]> git.basschouten.com Git - openhab-addons.git/blob
206469ce8ef4fbc1b6a048cddf514786a483ff3b
[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.state;
14
15 import java.nio.charset.StandardCharsets;
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                             result += new String(data, i, 1, StandardCharsets.US_ASCII);
368                         } else {
369                             logger.debug("Unhandled character code {}", data[i]);
370                         }
371                         break;
372                 }
373                 if (endStr) {
374                     break;
375                 }
376             }
377             result = result.trim();
378         }
379         return result;
380     }
381
382     /**
383      * Process and store all the panel settings from the raw buffers
384      *
385      * @param PowerlinkMode true if in Powerlink mode or false if in standard mode
386      * @param defaultPanelType the default panel type to consider if not found in the raw buffers
387      * @param timeSet the time in milliseconds used to set time and date; 0 if no sync time requested
388      *
389      * @return true if no problem encountered to get all the settings; false if not
390      */
391     public boolean process(boolean PowerlinkMode, PowermaxPanelType defaultPanelType, long timeSet) {
392         logger.debug("Process settings Powerlink {}", PowerlinkMode);
393
394         boolean result = true;
395         boolean result2;
396         byte[] data;
397
398         // Identify panel type
399         panelType = defaultPanelType;
400         if (PowerlinkMode) {
401             data = readSettings(PowermaxSendType.DL_SERIAL, 7, 7);
402             if (data != null) {
403                 try {
404                     panelType = PowermaxPanelType.fromCode(data[0]);
405                 } catch (IllegalArgumentException e) {
406                     logger.debug("Powermax alarm binding: unknwon panel type for code {}", data[0] & 0x000000FF);
407                     panelType = defaultPanelType;
408                 }
409             } else {
410                 logger.debug("Cannot get panel type");
411                 result = false;
412             }
413         }
414
415         int zoneCnt = panelType.getWireless() + panelType.getWired();
416         int customCnt = panelType.getCustomZones();
417         int userCnt = panelType.getUserCodes();
418         int partitionCnt = panelType.getPartitions();
419         int sirenCnt = panelType.getSirens();
420         int keypad1wCnt = panelType.getKeypads1w();
421         int keypad2wCnt = panelType.getKeypads2w();
422
423         phoneNumbers = new String[4];
424         bellTime = 4;
425         silentPanic = false;
426         quickArm = false;
427         bypassEnabled = false;
428         partitionsEnabled = false;
429         String[] localPinCodes = new String[userCnt];
430         panelEprom = null;
431         panelSoftware = null;
432         panelSerial = null;
433         zoneSettings = new PowermaxZoneSettings[zoneCnt];
434         x10Settings = new PowermaxX10Settings[NB_PGM_X10_DEVICES];
435         boolean[] localKeypad1wEnrolled = new boolean[keypad1wCnt];
436         boolean[] localKeypad2wEnrolled = new boolean[keypad2wCnt];
437         boolean[] localSirensEnrolled = new boolean[sirenCnt];
438
439         if (PowerlinkMode) {
440             // Check time and date
441             data = readSettings(PowermaxSendType.DL_TIME, 0, 5);
442             if (data != null) {
443                 GregorianCalendar cal = new GregorianCalendar();
444                 cal.set(Calendar.MILLISECOND, 0);
445                 cal.set(Calendar.SECOND, data[0] & 0x000000FF);
446                 cal.set(Calendar.MINUTE, data[1] & 0x000000FF);
447                 cal.set(Calendar.HOUR_OF_DAY, data[2] & 0x000000FF);
448                 cal.set(Calendar.DAY_OF_MONTH, data[3] & 0x000000FF);
449                 cal.set(Calendar.MONTH, (data[4] & 0x000000FF) - 1);
450                 cal.set(Calendar.YEAR, (data[5] & 0x000000FF) + 2000);
451                 long timeRead = cal.getTimeInMillis();
452                 logger.debug("Powermax alarm binding: time {}",
453                         String.format("%02d/%02d/%04d %02d:%02d:%02d", cal.get(Calendar.DAY_OF_MONTH),
454                                 cal.get(Calendar.MONTH) + 1, cal.get(Calendar.YEAR), cal.get(Calendar.HOUR_OF_DAY),
455                                 cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND)));
456
457                 // Check if time sync was OK
458                 if (timeSet > 0) {
459                     long delta = (timeRead - timeSet) / 1000;
460                     if (delta <= 5) {
461                         logger.debug("Powermax alarm binding: time sync OK (delta {} s)", delta);
462                     } else {
463                         logger.info("Powermax alarm binding: time sync failed ! (delta {} s)", delta);
464                     }
465                 }
466             } else {
467                 logger.debug("Cannot get time and date settings");
468                 result = false;
469             }
470
471             // Process zone names
472             result2 = true;
473             for (int i = 0; i < (26 + customCnt); i++) {
474                 String str = readSettingsAsString(PowermaxSendType.DL_ZONESTR, i * 16, (i + 1) * 16 - 1);
475                 if (str != null) {
476                     try {
477                         PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
478                         zoneName.setName(str);
479                     } catch (IllegalArgumentException e) {
480                         logger.debug("Zone id out of bounds {}", i);
481                     }
482                 } else {
483                     result2 = false;
484                 }
485             }
486             if (!result2) {
487                 logger.debug("Cannot get all zone names");
488                 result = false;
489             }
490
491             // Process communication settings
492             result2 = true;
493             for (int i = 0; i < phoneNumbers.length; i++) {
494                 data = readSettings(PowermaxSendType.DL_PHONENRS, 8 * i, 8 * i + 7);
495                 if (data != null) {
496                     for (int j = 0; j < 8; j++) {
497                         if ((data[j] & 0x000000FF) != 0x000000FF) {
498                             if (j == 0) {
499                                 phoneNumbers[i] = "";
500                             }
501                             if (phoneNumbers[i] != null) {
502                                 phoneNumbers[i] += String.format("%02X", data[j] & 0x000000FF);
503                             }
504                         }
505                     }
506                 } else {
507                     result2 = false;
508                 }
509             }
510             if (!result2) {
511                 logger.debug("Cannot get all communication settings");
512                 result = false;
513             }
514
515             // Process alarm settings
516             data = readSettings(PowermaxSendType.DL_COMMDEF, 0, 0x1B);
517             if (data != null) {
518                 bellTime = data[3] & 0x000000FF;
519                 silentPanic = (data[0x19] & 0x00000010) == 0x00000010;
520                 quickArm = (data[0x1A] & 0x00000008) == 0x00000008;
521                 bypassEnabled = (data[0x1B] & 0x000000C0) != 0;
522             } else {
523                 logger.debug("Cannot get alarm settings");
524                 result = false;
525             }
526
527             // Process user PIN codes
528             data = readSettings(
529                     panelType.isPowerMaster() ? PowermaxSendType.DL_MR_PINCODES : PowermaxSendType.DL_PINCODES, 0,
530                     2 * userCnt - 1);
531             if (data != null) {
532                 for (int i = 0; i < userCnt; i++) {
533                     localPinCodes[i] = String.format("%02X%02X", data[i * 2] & 0x000000FF,
534                             data[i * 2 + 1] & 0x000000FF);
535                 }
536             } else {
537                 logger.debug("Cannot get PIN codes");
538                 result = false;
539             }
540
541             // Process EEPROM version
542             panelEprom = readSettingsAsString(PowermaxSendType.DL_PANELFW, 0, 15);
543             if (panelEprom == null) {
544                 logger.debug("Cannot get EEPROM version");
545                 result = false;
546             }
547
548             // Process software version
549             panelSoftware = readSettingsAsString(PowermaxSendType.DL_PANELFW, 16, 31);
550             if (panelSoftware == null) {
551                 logger.debug("Cannot get software version");
552                 result = false;
553             }
554
555             // Process serial ID
556             panelSerial = "";
557             data = readSettings(PowermaxSendType.DL_SERIAL, 0, 5);
558             if (data != null) {
559                 for (int i = 0; i <= 5; i++) {
560                     if ((data[i] & 0x000000FF) != 0x000000FF) {
561                         panelSerial += String.format("%02X", data[i] & 0x000000FF);
562                     } else {
563                         panelSerial += ".";
564                     }
565                 }
566             } else {
567                 logger.debug("Cannot get serial ID");
568                 result = false;
569             }
570
571             // Check if partitions are enabled (only on panels that support partitions)
572             byte[] partitions = null;
573             if (partitionCnt > 1) {
574                 partitions = readSettings(PowermaxSendType.DL_PARTITIONS, 0, 0x10 + zoneCnt);
575                 if (partitions != null) {
576                     partitionsEnabled = (partitions[0] & 0x000000FF) == 1;
577                 } else {
578                     logger.debug("Cannot get partitions information");
579                     result = false;
580                 }
581                 if (!partitionsEnabled) {
582                     partitionCnt = 1;
583                 }
584             }
585
586             // Process zone settings
587             data = readSettings(PowermaxSendType.DL_ZONES, 0, zoneCnt * 4 - 1);
588             byte[] zoneNr = null;
589             byte[] dataMr = null;
590             if (panelType.isPowerMaster()) {
591                 zoneNr = readSettings(PowermaxSendType.DL_MR_ZONENAMES, 0, zoneCnt - 1);
592                 dataMr = readSettings(PowermaxSendType.DL_MR_ZONES, 0, zoneCnt * 10 - 2);
593             } else {
594                 zoneNr = readSettings(PowermaxSendType.DL_ZONENAMES, 0, zoneCnt - 1);
595             }
596             if ((data != null) && (zoneNr != null)) {
597                 byte[] zero3 = new byte[] { 0, 0, 0 };
598                 byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
599
600                 for (int i = 0; i < zoneCnt; i++) {
601                     String zoneName;
602                     try {
603                         PowermaxZoneName zone = PowermaxZoneName.fromId(zoneNr[i] & 0x0000001F);
604                         zoneName = zone.getName();
605                     } catch (IllegalArgumentException e) {
606                         logger.debug("Zone id out of bounds {}", zoneNr[i] & 0x0000001F);
607                         zoneName = null;
608                     }
609
610                     boolean zoneEnrolled;
611                     byte zoneInfo;
612                     String sensorTypeStr;
613                     if (panelType.isPowerMaster()) {
614                         zoneInfo = data[i];
615                         if (dataMr != null) {
616                             zoneEnrolled = !Arrays.equals(Arrays.copyOfRange(dataMr, i * 10 + 4, i * 10 + 9), zero5);
617                             byte sensorTypeCode = dataMr[i * 10 + 5];
618                             try {
619                                 PowermasterSensorType sensorType = PowermasterSensorType.fromCode(sensorTypeCode);
620                                 sensorTypeStr = sensorType.getLabel();
621                             } catch (IllegalArgumentException e) {
622                                 sensorTypeStr = null;
623                             }
624                         } else {
625                             zoneEnrolled = false;
626                             sensorTypeStr = null;
627                         }
628                     } else {
629                         zoneEnrolled = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
630                         zoneInfo = data[i * 4 + 3];
631                         byte sensorTypeCode = data[i * 4 + 2];
632                         try {
633                             PowermaxSensorType sensorType = PowermaxSensorType
634                                     .fromCode((byte) (sensorTypeCode & 0x0000000F));
635                             sensorTypeStr = sensorType.getLabel();
636                         } catch (IllegalArgumentException e) {
637                             sensorTypeStr = null;
638                         }
639                     }
640                     if (zoneEnrolled) {
641                         byte zoneType = (byte) (zoneInfo & 0x0000000F);
642                         byte zoneChime = (byte) ((zoneInfo >> 4) & 0x00000003);
643
644                         boolean[] part = new boolean[partitionCnt];
645                         if (partitionCnt > 1) {
646                             for (int j = 0; j < partitionCnt; j++) {
647                                 part[j] = (partitions != null) ? ((partitions[0x11 + i] & (1 << j)) != 0) : true;
648                             }
649                         } else {
650                             part[0] = true;
651                         }
652
653                         zoneSettings[i] = new PowermaxZoneSettings(zoneName, zoneType, zoneChime, sensorTypeStr, part);
654                     }
655                 }
656             } else {
657                 logger.debug("Cannot get zone settings");
658                 result = false;
659             }
660
661             data = readSettings(PowermaxSendType.DL_PGMX10, 0, 148);
662             zoneNr = readSettings(PowermaxSendType.DL_X10NAMES, 0, NB_PGM_X10_DEVICES - 2);
663             if ((data != null) && (zoneNr != null)) {
664                 for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
665                     boolean enabled = false;
666                     String zoneName = null;
667                     for (int j = 0; j <= 8; j++) {
668                         if (data[5 + i + j * 0x10] != 0) {
669                             enabled = true;
670                             break;
671                         }
672                     }
673                     if (i > 0) {
674                         try {
675                             PowermaxZoneName zone = PowermaxZoneName.fromId(zoneNr[i - 1] & 0x0000001F);
676                             zoneName = zone.getName();
677                         } catch (IllegalArgumentException e) {
678                             logger.debug("Zone id out of bounds {}", zoneNr[i - 1] & 0x0000001F);
679                             zoneName = null;
680                         }
681                     }
682                     x10Settings[i] = new PowermaxX10Settings(zoneName, enabled);
683                 }
684             } else {
685                 logger.debug("Cannot get PGM / X10 settings");
686                 result = false;
687             }
688
689             if (panelType.isPowerMaster()) {
690                 // Process 2 way keypad settings
691                 data = readSettings(PowermaxSendType.DL_MR_KEYPADS, 0, keypad2wCnt * 10 - 1);
692                 if (data != null) {
693                     byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
694
695                     for (int i = 0; i < keypad2wCnt; i++) {
696                         localKeypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9),
697                                 zero5);
698                     }
699                 } else {
700                     logger.debug("Cannot get 2 way keypad settings");
701                     result = false;
702                 }
703                 // Process siren settings
704                 data = readSettings(PowermaxSendType.DL_MR_SIRENS, 0, sirenCnt * 10 - 1);
705                 if (data != null) {
706                     byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
707
708                     for (int i = 0; i < sirenCnt; i++) {
709                         localSirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9),
710                                 zero5);
711                     }
712                 } else {
713                     logger.debug("Cannot get siren settings");
714                     result = false;
715                 }
716             } else {
717                 // Process 1 way keypad settings
718                 data = readSettings(PowermaxSendType.DL_1WKEYPAD, 0, keypad1wCnt * 4 - 1);
719                 if (data != null) {
720                     byte[] zero2 = new byte[] { 0, 0 };
721
722                     for (int i = 0; i < keypad1wCnt; i++) {
723                         localKeypad1wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 2), zero2);
724                     }
725                 } else {
726                     logger.debug("Cannot get 1 way keypad settings");
727                     result = false;
728                 }
729                 // Process 2 way keypad settings
730                 data = readSettings(PowermaxSendType.DL_2WKEYPAD, 0, keypad2wCnt * 4 - 1);
731                 if (data != null) {
732                     byte[] zero3 = new byte[] { 0, 0, 0 };
733
734                     for (int i = 0; i < keypad2wCnt; i++) {
735                         localKeypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
736                     }
737                 } else {
738                     logger.debug("Cannot get 2 way keypad settings");
739                     result = false;
740                 }
741                 // Process siren settings
742                 data = readSettings(PowermaxSendType.DL_SIRENS, 0, sirenCnt * 4 - 1);
743                 if (data != null) {
744                     byte[] zero3 = new byte[] { 0, 0, 0 };
745
746                     for (int i = 0; i < sirenCnt; i++) {
747                         localSirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
748                     }
749                 } else {
750                     logger.debug("Cannot get siren settings");
751                     result = false;
752                 }
753             }
754         } else {
755             if (!partitionsEnabled) {
756                 partitionCnt = 1;
757             }
758             boolean[] part = new boolean[partitionCnt];
759             for (int j = 0; j < partitionCnt; j++) {
760                 part[j] = true;
761             }
762             for (int i = 0; i < zoneCnt; i++) {
763                 zoneSettings[i] = new PowermaxZoneSettings(null, (byte) 0xFF, (byte) 0xFF, null, part);
764             }
765             for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
766                 x10Settings[i] = new PowermaxX10Settings(null, true);
767             }
768         }
769
770         pinCodes = localPinCodes;
771         keypad1wEnrolled = localKeypad1wEnrolled;
772         keypad2wEnrolled = localKeypad2wEnrolled;
773         sirensEnrolled = localSirensEnrolled;
774
775         return result;
776     }
777
778     /**
779      * Update the name of a zone
780      *
781      * @param zoneIdx the zone index (first zone is index 1)
782      * @param zoneNameIdx the index in the table of zone names
783      */
784     public void updateZoneName(int zoneIdx, byte zoneNameIdx) {
785         PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
786         if (zone != null) {
787             String name;
788             try {
789                 PowermaxZoneName zoneName = PowermaxZoneName.fromId(zoneNameIdx & 0x0000001F);
790                 name = zoneName.getName();
791             } catch (IllegalArgumentException e) {
792                 logger.debug("Zone id out of bounds {}", zoneNameIdx & 0x0000001F);
793                 name = null;
794             }
795             zone.setName(name);
796         }
797     }
798
799     /**
800      * Update the type of a zone
801      *
802      * @param zoneIdx the zone index (first zone is index 1)
803      * @param zoneInfo the zone info as an internal code
804      */
805     public void updateZoneInfo(int zoneIdx, int zoneInfo) {
806         PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
807         if (zone != null) {
808             zone.setType((byte) (zoneInfo & 0x0000000F));
809         }
810     }
811
812     public String getInfo() {
813         String str = "\nPanel is of type " + panelType.getLabel();
814
815         int zoneCnt = panelType.getWireless() + panelType.getWired();
816         int partitionCnt = panelType.getPartitions();
817         int sirenCnt = panelType.getSirens();
818         int keypad1wCnt = panelType.getKeypads1w();
819         int keypad2wCnt = panelType.getKeypads2w();
820         // int customCnt = panelType.getCustomZones();
821
822         if (!partitionsEnabled) {
823             partitionCnt = 1;
824         }
825
826         // for (int i = 0; i < (26 + customCnt); i++) {
827         // String name;
828         // try {
829         // PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
830         // name = zoneName.getName();
831         // } catch (IllegalArgumentException e) {
832         // logger.debug("Zone id out of bounds {}", i);
833         // name = null;
834         // }
835         // str += String.format("\nZone name %d; %s", i + 1, name);
836         // }
837         for (int i = 0; i < phoneNumbers.length; i++) {
838             if (phoneNumbers[i] != null) {
839                 str += String.format("\nPhone number %d: %s", i + 1, phoneNumbers[i]);
840             }
841         }
842         str += String.format("\nBell time: %d minutes", bellTime);
843         str += String.format("\nSilent panic: %s", silentPanic ? "enabled" : "disabled");
844         str += String.format("\nQuick arm: %s", quickArm ? "enabled" : "disabled");
845         str += String.format("\nZone bypass: %s", bypassEnabled ? "enabled" : "disabled");
846         str += String.format("\nEPROM: %s", (panelEprom != null) ? panelEprom : "Undefined");
847         str += String.format("\nSW: %s", (panelSoftware != null) ? panelSoftware : "Undefined");
848         str += String.format("\nSerial: %s", (panelSerial != null) ? panelSerial : "Undefined");
849         str += String.format("\nUse partitions: %s", partitionsEnabled ? "enabled" : "disabled");
850         str += String.format("\nNumber of partitions: %d", partitionCnt);
851         for (int i = 0; i < zoneCnt; i++) {
852             if (zoneSettings[i] != null) {
853                 String partStr = "";
854                 for (int j = 1; j <= partitionCnt; j++) {
855                     if (zoneSettings[i].isInPartition(j)) {
856                         partStr += j + " ";
857                     }
858                 }
859                 str += String.format("\nZone %d %s: %s (chime = %s; sensor type = %s; partitions = %s)", i + 1,
860                         zoneSettings[i].getName(), zoneSettings[i].getType(), zoneSettings[i].getChime(),
861                         zoneSettings[i].getSensorType(), partStr);
862             }
863         }
864         for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
865             if (x10Settings[i] != null && x10Settings[i].isEnabled()) {
866                 str += String.format("\n%s: %s enabled", (i == 0) ? "PGM" : ("X10 " + i),
867                         (x10Settings[i].getName() != null) ? x10Settings[i].getName() : "");
868             }
869         }
870         for (int i = 1; i <= sirenCnt; i++) {
871             if (isSirenEnrolled(i)) {
872                 str += String.format("\nSiren %d enrolled", i);
873             }
874         }
875         for (int i = 1; i <= keypad1wCnt; i++) {
876             if (isKeypad1wEnrolled(i)) {
877                 str += String.format("\nKeypad 1w %d enrolled", i);
878             }
879         }
880         for (int i = 1; i <= keypad2wCnt; i++) {
881             if (isKeypad2wEnrolled(i)) {
882                 str += String.format("\nKeypad 2w %d enrolled", i);
883             }
884         }
885         return "Powermax alarm binding:" + str;
886     }
887 }