2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.powermax.internal.state;
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;
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;
29 * A class to store all the settings of the alarm system
31 * @author Laurent Garnier - Initial contribution
34 public class PowermaxPanelSettings {
36 /** Number of PGM and X10 devices managed by the system */
37 private static final int NB_PGM_X10_DEVICES = 16;
39 private final Logger logger = LoggerFactory.getLogger(PowermaxPanelSettings.class);
41 /** Raw buffers for settings */
42 private Byte[][] rawSettings;
44 private PowermaxPanelType panelType;
45 private String[] phoneNumbers;
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;
64 * @param defaultPanelType the default panel type to consider
66 public PowermaxPanelSettings(PowermaxPanelType defaultPanelType) {
67 rawSettings = new Byte[0x100][];
68 panelType = defaultPanelType;
69 phoneNumbers = new String[4];
71 int zoneCnt = panelType.getWireless() + panelType.getWired();
72 zoneSettings = new PowermaxZoneSettings[zoneCnt];
73 x10Settings = new PowermaxX10Settings[NB_PGM_X10_DEVICES];
77 * @return the panel type
79 public PowermaxPanelType getPanelType() {
84 * @return the length of time the bell or siren sounds (in minutes)
86 public int getBellTime() {
91 * @return true if panic alarms are silent; false if audible
93 public boolean isSilentPanic() {
98 * @return true if bypassing zones is enabled; false if not
100 public boolean isBypassEnabled() {
101 return bypassEnabled;
105 * @return true if partitions usage is enabled; false if not
107 public boolean isPartitionsEnabled() {
108 return partitionsEnabled;
112 * @return the panel EEPROM version
114 public @Nullable String getPanelEprom() {
119 * @return the panel software version
121 public @Nullable String getPanelSoftware() {
122 return panelSoftware;
126 * @return the panel serial ID
128 public @Nullable String getPanelSerial() {
133 * @return the number of zones
135 public int getNbZones() {
136 return zoneSettings.length;
140 * @return an integer stream for iterating over the range of zone numbers
142 public IntStream getZoneRange() {
143 return IntStream.rangeClosed(1, getNbZones());
147 * Get the settings relative to a zone
149 * @param zone the zone index (from 1 to NumberOfZones)
151 * @return the settings of the zone
153 public @Nullable PowermaxZoneSettings getZoneSettings(int zone) {
154 return ((zone < 1) || (zone > zoneSettings.length)) ? null : zoneSettings[zone - 1];
158 * Get a zone's display name
160 * @param zone the zone index (from 1 to NumberOfZones)
162 * @return the name of the zone
164 public @Nullable String getZoneName(int zone) {
165 PowermaxZoneSettings zoneSettings = getZoneSettings(zone);
166 return (zoneSettings == null) ? null : zoneSettings.getName();
170 * Get a friendly display name for a zone, user, or device
171 * (any possible source for an event)
173 * @param zoneOrUser the zone, user, or device code
175 * @return the display name
177 public String getZoneOrUserName(int zoneOrUser) {
178 String zoneName = getZoneName(zoneOrUser);
180 if (zoneOrUser >= 1 && zoneOrUser <= zoneSettings.length && zoneName != null) {
181 return String.format("%s[%d]", zoneName, zoneOrUser);
183 return PowermaxMessageConstants.getZoneOrUser(zoneOrUser);
188 * @return the number of PGM and X10 devices managed by the system
190 public int getNbPGMX10Devices() {
191 return NB_PGM_X10_DEVICES;
195 * Get the settings relative to the PGM
197 * @return the settings of the PGM
199 public PowermaxX10Settings getPGMSettings() {
200 return x10Settings[0];
204 * Get the settings relative to a X10 device
206 * @param idx the index (from 1 to 15)
208 * @return the settings of the X10 device
210 public @Nullable PowermaxX10Settings getX10Settings(int idx) {
211 return ((idx < 1) || (idx >= x10Settings.length)) ? null : x10Settings[idx];
215 * @param idx the keypad index (first is 1)
217 * @return true if the 1 way keypad is enrolled; false if not
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];
226 * @param idx the keypad index (first is 1)
228 * @return true if the 2 way keypad is enrolled; false if not
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];
237 * @param idx the siren index (first is 1)
239 * @return true if the siren is enrolled; false if not
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];
248 * @return the PIN code of the first user of an empty string if unknown (standard mode)
250 public String getFirstPinCode() {
251 String @Nullable [] localPinCodes = pinCodes;
252 return (localPinCodes == null || localPinCodes.length == 0) ? "" : localPinCodes[0];
255 public void updateRawSettings(byte[] data) {
256 if (data.length < 3) {
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;
268 for (int i = pageMin; i <= pageMax; i++) {
272 start = indexPageMin;
277 if (rawSettings[i] == null) {
278 rawSettings[i] = new Byte[0x100];
280 for (int j = start; j <= end; j++) {
281 rawSettings[i][j] = data[index++];
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);
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;
299 boolean missingData = false;
300 for (int i = pageMin; i <= pageMax; i++) {
304 start2 = indexPageMin;
309 index += end2 - start2 + 1;
310 for (int j = start2; j <= end2; j++) {
311 if ((rawSettings[i] == null) || (rawSettings[i][j] == null)) {
321 logger.debug("readSettings({}, {}, {}): missing data", page, start, end);
324 byte[] result = new byte[index];
326 for (int i = pageMin; i <= pageMax; i++) {
330 start2 = indexPageMin;
335 for (int j = start2; j <= end2; j++) {
336 result[index++] = rawSettings[i][j];
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)) {
350 for (int i = 0; i < data.length; i++) {
351 boolean endStr = false;
352 switch (data[i] & 0x000000FF) {
366 if ((data[i] & 0x000000FF) >= 0x20) {
368 result += new String(data, i, 1, "US-ASCII");
369 } catch (UnsupportedEncodingException e) {
370 logger.debug("Unhandled character code {}", data[i]);
373 logger.debug("Unhandled character code {}", data[i]);
381 result = result.trim();
387 * Process and store all the panel settings from the raw buffers
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
393 * @return true if no problem encountered to get all the settings; false if not
395 public boolean process(boolean PowerlinkMode, PowermaxPanelType defaultPanelType, long timeSet) {
396 logger.debug("Process settings Powerlink {}", PowerlinkMode);
398 boolean result = true;
402 // Identify panel type
403 panelType = defaultPanelType;
405 data = readSettings(PowermaxSendType.DL_SERIAL, 7, 7);
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;
414 logger.debug("Cannot get panel type");
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();
427 phoneNumbers = new String[4];
431 bypassEnabled = false;
432 partitionsEnabled = false;
433 String[] localPinCodes = new String[userCnt];
435 panelSoftware = 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];
444 // Check time and date
445 data = readSettings(PowermaxSendType.DL_TIME, 0, 5);
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)));
461 // Check if time sync was OK
463 long delta = (timeRead - timeSet) / 1000;
465 logger.debug("Powermax alarm binding: time sync OK (delta {} s)", delta);
467 logger.info("Powermax alarm binding: time sync failed ! (delta {} s)", delta);
471 logger.debug("Cannot get time and date settings");
475 // Process zone names
477 for (int i = 0; i < (26 + customCnt); i++) {
478 String str = readSettingsAsString(PowermaxSendType.DL_ZONESTR, i * 16, (i + 1) * 16 - 1);
481 PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
482 zoneName.setName(str);
483 } catch (IllegalArgumentException e) {
484 logger.debug("Zone id out of bounds {}", i);
491 logger.debug("Cannot get all zone names");
495 // Process communication settings
497 for (int i = 0; i < phoneNumbers.length; i++) {
498 data = readSettings(PowermaxSendType.DL_PHONENRS, 8 * i, 8 * i + 7);
500 for (int j = 0; j < 8; j++) {
501 if ((data[j] & 0x000000FF) != 0x000000FF) {
503 phoneNumbers[i] = "";
505 if (phoneNumbers[i] != null) {
506 phoneNumbers[i] += String.format("%02X", data[j] & 0x000000FF);
515 logger.debug("Cannot get all communication settings");
519 // Process alarm settings
520 data = readSettings(PowermaxSendType.DL_COMMDEF, 0, 0x1B);
522 bellTime = data[3] & 0x000000FF;
523 silentPanic = (data[0x19] & 0x00000010) == 0x00000010;
524 quickArm = (data[0x1A] & 0x00000008) == 0x00000008;
525 bypassEnabled = (data[0x1B] & 0x000000C0) != 0;
527 logger.debug("Cannot get alarm settings");
531 // Process user PIN codes
533 panelType.isPowerMaster() ? PowermaxSendType.DL_MR_PINCODES : PowermaxSendType.DL_PINCODES, 0,
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);
541 logger.debug("Cannot get PIN codes");
545 // Process EEPROM version
546 panelEprom = readSettingsAsString(PowermaxSendType.DL_PANELFW, 0, 15);
547 if (panelEprom == null) {
548 logger.debug("Cannot get EEPROM version");
552 // Process software version
553 panelSoftware = readSettingsAsString(PowermaxSendType.DL_PANELFW, 16, 31);
554 if (panelSoftware == null) {
555 logger.debug("Cannot get software version");
561 data = readSettings(PowermaxSendType.DL_SERIAL, 0, 5);
563 for (int i = 0; i <= 5; i++) {
564 if ((data[i] & 0x000000FF) != 0x000000FF) {
565 panelSerial += String.format("%02X", data[i] & 0x000000FF);
571 logger.debug("Cannot get serial ID");
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;
582 logger.debug("Cannot get partitions information");
585 if (!partitionsEnabled) {
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);
598 zoneNr = readSettings(PowermaxSendType.DL_ZONENAMES, 0, zoneCnt - 1);
600 if ((data != null) && (zoneNr != null)) {
601 byte[] zero3 = new byte[] { 0, 0, 0 };
602 byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
604 for (int i = 0; i < zoneCnt; i++) {
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);
614 boolean zoneEnrolled;
616 String sensorTypeStr;
617 if (panelType.isPowerMaster()) {
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];
623 PowermasterSensorType sensorType = PowermasterSensorType.fromCode(sensorTypeCode);
624 sensorTypeStr = sensorType.getLabel();
625 } catch (IllegalArgumentException e) {
626 sensorTypeStr = null;
629 zoneEnrolled = false;
630 sensorTypeStr = null;
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];
637 PowermaxSensorType sensorType = PowermaxSensorType
638 .fromCode((byte) (sensorTypeCode & 0x0000000F));
639 sensorTypeStr = sensorType.getLabel();
640 } catch (IllegalArgumentException e) {
641 sensorTypeStr = null;
645 byte zoneType = (byte) (zoneInfo & 0x0000000F);
646 byte zoneChime = (byte) ((zoneInfo >> 4) & 0x00000003);
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;
657 zoneSettings[i] = new PowermaxZoneSettings(zoneName, zoneType, zoneChime, sensorTypeStr, part);
661 logger.debug("Cannot get zone settings");
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) {
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);
686 x10Settings[i] = new PowermaxX10Settings(zoneName, enabled);
689 logger.debug("Cannot get PGM / X10 settings");
693 if (panelType.isPowerMaster()) {
694 // Process 2 way keypad settings
695 data = readSettings(PowermaxSendType.DL_MR_KEYPADS, 0, keypad2wCnt * 10 - 1);
697 byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
699 for (int i = 0; i < keypad2wCnt; i++) {
700 localKeypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9),
704 logger.debug("Cannot get 2 way keypad settings");
707 // Process siren settings
708 data = readSettings(PowermaxSendType.DL_MR_SIRENS, 0, sirenCnt * 10 - 1);
710 byte[] zero5 = new byte[] { 0, 0, 0, 0, 0 };
712 for (int i = 0; i < sirenCnt; i++) {
713 localSirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 10 + 4, i * 10 + 9),
717 logger.debug("Cannot get siren settings");
721 // Process 1 way keypad settings
722 data = readSettings(PowermaxSendType.DL_1WKEYPAD, 0, keypad1wCnt * 4 - 1);
724 byte[] zero2 = new byte[] { 0, 0 };
726 for (int i = 0; i < keypad1wCnt; i++) {
727 localKeypad1wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 2), zero2);
730 logger.debug("Cannot get 1 way keypad settings");
733 // Process 2 way keypad settings
734 data = readSettings(PowermaxSendType.DL_2WKEYPAD, 0, keypad2wCnt * 4 - 1);
736 byte[] zero3 = new byte[] { 0, 0, 0 };
738 for (int i = 0; i < keypad2wCnt; i++) {
739 localKeypad2wEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
742 logger.debug("Cannot get 2 way keypad settings");
745 // Process siren settings
746 data = readSettings(PowermaxSendType.DL_SIRENS, 0, sirenCnt * 4 - 1);
748 byte[] zero3 = new byte[] { 0, 0, 0 };
750 for (int i = 0; i < sirenCnt; i++) {
751 localSirensEnrolled[i] = !Arrays.equals(Arrays.copyOfRange(data, i * 4, i * 4 + 3), zero3);
754 logger.debug("Cannot get siren settings");
759 if (!partitionsEnabled) {
762 boolean[] part = new boolean[partitionCnt];
763 for (int j = 0; j < partitionCnt; j++) {
766 for (int i = 0; i < zoneCnt; i++) {
767 zoneSettings[i] = new PowermaxZoneSettings(null, (byte) 0xFF, (byte) 0xFF, null, part);
769 for (int i = 0; i < NB_PGM_X10_DEVICES; i++) {
770 x10Settings[i] = new PowermaxX10Settings(null, true);
774 pinCodes = localPinCodes;
775 keypad1wEnrolled = localKeypad1wEnrolled;
776 keypad2wEnrolled = localKeypad2wEnrolled;
777 sirensEnrolled = localSirensEnrolled;
783 * Update the name of a zone
785 * @param zoneIdx the zone index (first zone is index 1)
786 * @param zoneNameIdx the index in the table of zone names
788 public void updateZoneName(int zoneIdx, byte zoneNameIdx) {
789 PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
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);
804 * Update the type of a zone
806 * @param zoneIdx the zone index (first zone is index 1)
807 * @param zoneInfo the zone info as an internal code
809 public void updateZoneInfo(int zoneIdx, int zoneInfo) {
810 PowermaxZoneSettings zone = getZoneSettings(zoneIdx);
812 zone.setType((byte) (zoneInfo & 0x0000000F));
816 public String getInfo() {
817 String str = "\nPanel is of type " + panelType.getLabel();
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();
826 if (!partitionsEnabled) {
830 // for (int i = 0; i < (26 + customCnt); i++) {
833 // PowermaxZoneName zoneName = PowermaxZoneName.fromId(i);
834 // name = zoneName.getName();
835 // } catch (IllegalArgumentException e) {
836 // logger.debug("Zone id out of bounds {}", i);
839 // str += String.format("\nZone name %d; %s", i + 1, name);
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]);
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) {
858 for (int j = 1; j <= partitionCnt; j++) {
859 if (zoneSettings[i].isInPartition(j)) {
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);
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() : "");
874 for (int i = 1; i <= sirenCnt; i++) {
875 if (isSirenEnrolled(i)) {
876 str += String.format("\nSiren %d enrolled", i);
879 for (int i = 1; i <= keypad1wCnt; i++) {
880 if (isKeypad1wEnrolled(i)) {
881 str += String.format("\nKeypad 1w %d enrolled", i);
884 for (int i = 1; i <= keypad2wCnt; i++) {
885 if (isKeypad2wEnrolled(i)) {
886 str += String.format("\nKeypad 2w %d enrolled", i);
889 return "Powermax alarm binding:" + str;