*/
package org.openhab.binding.powermax.internal.message;
+import static java.util.Map.entry;
+
+import java.util.Map;
+
/**
* Constants used in Powermax messages
*
private PowermaxMessageConstants() {
}
- private static String getValue(String[] table, int index) {
- return (((index >= 0) && (index < table.length)) ? table[index] : "UNKNOWN");
+ // System events
+
+ public static enum PowermaxSysEventType {
+ NONE,
+ ALARM,
+ SILENT_ALARM,
+ ALERT,
+ PANIC,
+ TROUBLE,
+ RESTORE,
+ GENERAL_RESTORE,
+ CANCEL,
+ RESET;
+ }
+
+ public static class PowermaxSysEvent {
+ private final String name;
+ private final PowermaxSysEventType type;
+ private final int restoreFor;
+
+ protected PowermaxSysEvent(String name, PowermaxSysEventType type, int restoreFor) {
+ this.name = name;
+ this.type = type;
+ this.restoreFor = restoreFor;
+ }
+
+ protected static PowermaxSysEvent of(String name) {
+ return new PowermaxSysEvent(name, PowermaxSysEventType.NONE, 0);
+ }
+
+ protected static PowermaxSysEvent of(String name, PowermaxSysEventType type) {
+ return new PowermaxSysEvent(name, type, 0);
+ }
+
+ protected static PowermaxSysEvent of(String name, PowermaxSysEventType type, int restoreFor) {
+ return new PowermaxSysEvent(name, type, restoreFor);
+ }
+
+ public PowermaxSysEventType getType() {
+ return this.type;
+ }
+
+ public int getRestoreFor() {
+ return this.restoreFor;
+ }
+
+ public boolean isAlarm() {
+ return (this.type == PowermaxSysEventType.ALARM);
+ }
+
+ public boolean isSilentAlarm() {
+ return (this.type == PowermaxSysEventType.SILENT_ALARM);
+ }
+
+ public boolean isAlert() {
+ return (this.type == PowermaxSysEventType.ALERT);
+ }
+
+ public boolean isPanic() {
+ return (this.type == PowermaxSysEventType.PANIC);
+ }
+
+ public boolean isTrouble() {
+ return (this.type == PowermaxSysEventType.TROUBLE);
+ }
+
+ public boolean isRestore() {
+ return (this.type == PowermaxSysEventType.RESTORE);
+ }
+
+ public boolean isGeneralRestore() {
+ return (this.type == PowermaxSysEventType.GENERAL_RESTORE);
+ }
+
+ public boolean isCancel() {
+ return (this.type == PowermaxSysEventType.CANCEL);
+ }
+
+ public boolean isReset() {
+ return (this.type == PowermaxSysEventType.RESET);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
}
+ // Important note: in all of the following lists, each entry line ends
+ // with an empty "//" comment. This is to prevent the "spotless" code
+ // formatter from trying to wrap these lines in a way that makes them
+ // much less readable.
+
+ private static final PowermaxSysEvent UNKNOWN_SYSTEM_EVENT = PowermaxSysEvent.of("UNKNOWN");
+
+ private static final Map<Integer, PowermaxSysEvent> SYSTEM_EVENTS = Map.ofEntries( //
+ entry(0x00, PowermaxSysEvent.of("None")), //
+ entry(0x01, PowermaxSysEvent.of("Interior Alarm", PowermaxSysEventType.ALARM)), //
+ entry(0x02, PowermaxSysEvent.of("Perimeter Alarm", PowermaxSysEventType.ALARM)), //
+ entry(0x03, PowermaxSysEvent.of("Delay Alarm", PowermaxSysEventType.ALARM)), //
+ entry(0x04, PowermaxSysEvent.of("24h Silent Alarm", PowermaxSysEventType.SILENT_ALARM)), //
+ entry(0x05, PowermaxSysEvent.of("24h Audible Alarm", PowermaxSysEventType.ALARM)), //
+ entry(0x06, PowermaxSysEvent.of("Tamper", PowermaxSysEventType.ALERT)), //
+ entry(0x07, PowermaxSysEvent.of("Control Panel Tamper", PowermaxSysEventType.ALARM)), //
+ entry(0x08, PowermaxSysEvent.of("Tamper Alarm", PowermaxSysEventType.ALARM)), //
+ entry(0x09, PowermaxSysEvent.of("Tamper Alarm", PowermaxSysEventType.TROUBLE)), //
+ entry(0x0A, PowermaxSysEvent.of("Communication Loss", PowermaxSysEventType.ALARM)), //
+ entry(0x0B, PowermaxSysEvent.of("Panic From KeyKeyfob", PowermaxSysEventType.PANIC)), //
+ entry(0x0C, PowermaxSysEvent.of("Panic From Control Panel", PowermaxSysEventType.PANIC)), //
+ entry(0x0D, PowermaxSysEvent.of("Duress", PowermaxSysEventType.SILENT_ALARM)), //
+ entry(0x0E, PowermaxSysEvent.of("Confirm Alarm", PowermaxSysEventType.ALARM)), //
+ entry(0x0F, PowermaxSysEvent.of("General Trouble", PowermaxSysEventType.TROUBLE)), //
+ entry(0x10, PowermaxSysEvent.of("General Trouble Restore", PowermaxSysEventType.RESTORE, 0x0F)), //
+ entry(0x11, PowermaxSysEvent.of("Interior Restore")), //
+ entry(0x12, PowermaxSysEvent.of("Perimeter Restore")), //
+ entry(0x13, PowermaxSysEvent.of("Delay Restore")), //
+ entry(0x14, PowermaxSysEvent.of("24h Silent Restore")), //
+ entry(0x15, PowermaxSysEvent.of("24h Audible Restore")), //
+ entry(0x16, PowermaxSysEvent.of("Tamper Restore", PowermaxSysEventType.RESTORE, 0x06)), //
+ entry(0x17, PowermaxSysEvent.of("Control Panel Tamper Restore")), //
+ entry(0x18, PowermaxSysEvent.of("Tamper Restore")), //
+ entry(0x19, PowermaxSysEvent.of("Tamper Restore")), //
+ entry(0x1A, PowermaxSysEvent.of("Communication Restore")), //
+ entry(0x1B, PowermaxSysEvent.of("Cancel Alarm", PowermaxSysEventType.CANCEL)), //
+ entry(0x1C, PowermaxSysEvent.of("General Restore", PowermaxSysEventType.GENERAL_RESTORE)), //
+ entry(0x1D, PowermaxSysEvent.of("Trouble Restore")), //
+ entry(0x1E, PowermaxSysEvent.of("Not used")), //
+ entry(0x1F, PowermaxSysEvent.of("Recent Close")), //
+ entry(0x20, PowermaxSysEvent.of("Fire", PowermaxSysEventType.ALARM)), //
+ entry(0x21, PowermaxSysEvent.of("Fire Restore")), //
+ entry(0x22, PowermaxSysEvent.of("No Activity", PowermaxSysEventType.ALERT)), //
+ entry(0x23, PowermaxSysEvent.of("Emergency", PowermaxSysEventType.ALERT)), //
+ entry(0x24, PowermaxSysEvent.of("Not used")), //
+ entry(0x25, PowermaxSysEvent.of("Disarm Latchkey", PowermaxSysEventType.ALERT)), //
+ entry(0x26, PowermaxSysEvent.of("Panic Restore")), //
+ entry(0x27, PowermaxSysEvent.of("Supervision (Inactive)", PowermaxSysEventType.TROUBLE)), //
+ entry(0x28, PowermaxSysEvent.of("Supervision Restore (Active)", PowermaxSysEventType.RESTORE, 0x27)), //
+ entry(0x29, PowermaxSysEvent.of("Low Battery", PowermaxSysEventType.TROUBLE)), //
+ entry(0x2A, PowermaxSysEvent.of("Low Battery Restore", PowermaxSysEventType.RESTORE, 0x29)), //
+ entry(0x2B, PowermaxSysEvent.of("AC Fail", PowermaxSysEventType.TROUBLE)), //
+ entry(0x2C, PowermaxSysEvent.of("AC Restore", PowermaxSysEventType.RESTORE, 0x2B)), //
+ entry(0x2D, PowermaxSysEvent.of("Control Panel Low Battery", PowermaxSysEventType.TROUBLE)), //
+ entry(0x2E, PowermaxSysEvent.of("Control Panel Low Battery Restore", PowermaxSysEventType.RESTORE, 0x2D)), //
+ entry(0x2F, PowermaxSysEvent.of("RF Jamming", PowermaxSysEventType.TROUBLE)), //
+ entry(0x30, PowermaxSysEvent.of("RF Jamming Restore", PowermaxSysEventType.RESTORE, 0x2F)), //
+ entry(0x31, PowermaxSysEvent.of("Communications Failure", PowermaxSysEventType.TROUBLE)), //
+ entry(0x32, PowermaxSysEvent.of("Communications Restore", PowermaxSysEventType.RESTORE, 0x31)), //
+ entry(0x33, PowermaxSysEvent.of("Telephone Line Failure", PowermaxSysEventType.TROUBLE)), //
+ entry(0x34, PowermaxSysEvent.of("Telephone Line Restore", PowermaxSysEventType.RESTORE, 0x33)), //
+ entry(0x35, PowermaxSysEvent.of("Auto Test")), //
+ entry(0x36, PowermaxSysEvent.of("Fuse Failure", PowermaxSysEventType.TROUBLE)), //
+ entry(0x37, PowermaxSysEvent.of("Fuse Restore", PowermaxSysEventType.RESTORE, 0x36)), //
+ entry(0x38, PowermaxSysEvent.of("KeyKeyfob Low Battery", PowermaxSysEventType.TROUBLE)), //
+ entry(0x39, PowermaxSysEvent.of("KeyKeyfob Low Battery Restore", PowermaxSysEventType.RESTORE, 0x38)), //
+ entry(0x3A, PowermaxSysEvent.of("Engineer Reset")), //
+ entry(0x3B, PowermaxSysEvent.of("Battery Disconnect")), //
+ entry(0x3C, PowermaxSysEvent.of("1-Way Keypad Low Battery", PowermaxSysEventType.TROUBLE)), //
+ entry(0x3D, PowermaxSysEvent.of("1-Way Keypad Low Battery Restore", PowermaxSysEventType.RESTORE, 0x3C)), //
+ entry(0x3E, PowermaxSysEvent.of("1-Way Keypad Inactive", PowermaxSysEventType.TROUBLE)), //
+ entry(0x3F, PowermaxSysEvent.of("1-Way Keypad Restore Active", PowermaxSysEventType.RESTORE, 0x3E)), //
+ entry(0x40, PowermaxSysEvent.of("Low Battery")), //
+ entry(0x41, PowermaxSysEvent.of("Clean Me", PowermaxSysEventType.TROUBLE)), //
+ entry(0x42, PowermaxSysEvent.of("Fire Trouble", PowermaxSysEventType.TROUBLE)), //
+ entry(0x43, PowermaxSysEvent.of("Low Battery", PowermaxSysEventType.TROUBLE)), //
+ entry(0x44, PowermaxSysEvent.of("Battery Restore", PowermaxSysEventType.RESTORE, 0x43)), //
+ entry(0x45, PowermaxSysEvent.of("AC Fail", PowermaxSysEventType.TROUBLE)), //
+ entry(0x46, PowermaxSysEvent.of("AC Restore", PowermaxSysEventType.RESTORE, 0x45)), //
+ entry(0x47, PowermaxSysEvent.of("Supervision (Inactive)", PowermaxSysEventType.TROUBLE)), //
+ entry(0x48, PowermaxSysEvent.of("Supervision Restore (Active)", PowermaxSysEventType.RESTORE, 0x47)), //
+ entry(0x49, PowermaxSysEvent.of("Gas Alert", PowermaxSysEventType.ALARM)), //
+ entry(0x4A, PowermaxSysEvent.of("Gas Alert Restore")), //
+ entry(0x4B, PowermaxSysEvent.of("Gas Trouble", PowermaxSysEventType.TROUBLE)), //
+ entry(0x4C, PowermaxSysEvent.of("Gas Trouble Restore", PowermaxSysEventType.RESTORE, 0x4B)), //
+ entry(0x4D, PowermaxSysEvent.of("Flood Alert", PowermaxSysEventType.ALARM)), //
+ entry(0x4E, PowermaxSysEvent.of("Flood Alert Restore")), //
+ entry(0x4F, PowermaxSysEvent.of("X-10 Trouble", PowermaxSysEventType.TROUBLE)), //
+ entry(0x50, PowermaxSysEvent.of("X-10 Trouble Restore", PowermaxSysEventType.RESTORE, 0x4F)), //
+ entry(0x51, PowermaxSysEvent.of("Arm Home")), //
+ entry(0x52, PowermaxSysEvent.of("Arm Away")), //
+ entry(0x53, PowermaxSysEvent.of("Quick Arm Home")), //
+ entry(0x54, PowermaxSysEvent.of("Quick Arm Away")), //
+ entry(0x55, PowermaxSysEvent.of("Disarm")), //
+ entry(0x56, PowermaxSysEvent.of("Fail To Auto-Arm")), //
+ entry(0x57, PowermaxSysEvent.of("Enter To Test Mode")), //
+ entry(0x58, PowermaxSysEvent.of("Exit From Test Mode")), //
+ entry(0x59, PowermaxSysEvent.of("Force Arm")), //
+ entry(0x5A, PowermaxSysEvent.of("Auto Arm")), //
+ entry(0x5B, PowermaxSysEvent.of("Instant Arm")), //
+ entry(0x5C, PowermaxSysEvent.of("Bypass")), //
+ entry(0x5D, PowermaxSysEvent.of("Fail To Arm")), //
+ entry(0x5E, PowermaxSysEvent.of("Door Open")), //
+ entry(0x5F, PowermaxSysEvent.of("Communication Established By Control Panel")), //
+ entry(0x60, PowermaxSysEvent.of("System Reset", PowermaxSysEventType.RESET)), //
+ entry(0x61, PowermaxSysEvent.of("Installer Programming")), //
+ entry(0x62, PowermaxSysEvent.of("Wrong Password")), //
+ entry(0x63, PowermaxSysEvent.of("Not Sys Event")), //
+ entry(0x64, PowermaxSysEvent.of("Not Sys Event")), //
+ entry(0x65, PowermaxSysEvent.of("Extreme Hot Alert")), //
+ entry(0x66, PowermaxSysEvent.of("Extreme Hot Alert Restore")), //
+ entry(0x67, PowermaxSysEvent.of("Freeze Alert")), //
+ entry(0x68, PowermaxSysEvent.of("Freeze Alert Restore")), //
+ entry(0x69, PowermaxSysEvent.of("Human Cold Alert")), //
+ entry(0x6A, PowermaxSysEvent.of("Human Cold Alert Restore")), //
+ entry(0x6B, PowermaxSysEvent.of("Human Hot Alert")), //
+ entry(0x6C, PowermaxSysEvent.of("Human Hot Alert Restore")), //
+ entry(0x6D, PowermaxSysEvent.of("Temperature Sensor Trouble")), //
+ entry(0x6E, PowermaxSysEvent.of("Temperature Sensor Trouble Restore")), //
+ entry(0x6F, PowermaxSysEvent.of("PIR Mask")), //
+ entry(0x70, PowermaxSysEvent.of("PIR Mask Restore")), //
+ entry(0x7B, PowermaxSysEvent.of("Alarmed")), //
+ entry(0x7C, PowermaxSysEvent.of("Restore")), //
+ entry(0x7D, PowermaxSysEvent.of("Alarmed")), //
+ entry(0x7E, PowermaxSysEvent.of("Restore")), //
+ entry(0x8E, PowermaxSysEvent.of("Exit Installer")), //
+ entry(0x8F, PowermaxSysEvent.of("Enter Installer")) //
+ );
+
/**
* System event lookup
*/
- public static String getSystemEventString(int code) {
- return getValue(SYSTEM_EVENT_TABLE, code);
+ public static PowermaxSysEvent getSystemEvent(int code) {
+ return SYSTEM_EVENTS.getOrDefault(code, UNKNOWN_SYSTEM_EVENT);
}
- private static final String[] SYSTEM_EVENT_TABLE = new String[] { "None", "Interior Alarm", "Perimeter Alarm",
- "Delay Alarm", "24h Silent Alarm", "24h Audible Alarm", "Tamper", "Control Panel Tamper", "Tamper Alarm",
- "Tamper Alarm", "Communication Loss", "Panic From Keyfob", "Panic From Control Panel", "Duress",
- "Confirm Alarm", "General Trouble", "General Trouble Restore", "Interior Restore", "Perimeter Restore",
- "Delay Restore", "24h Silent Restore", "24h Audible Restore", "Tamper Restore",
- "Control Panel Tamper Restore", "Tamper Restore", "Tamper Restore", "Communication Restore", "Cancel Alarm",
- "General Restore", "Trouble Restore", "Not used", "Recent Close", "Fire", "Fire Restore", "No Active",
- "Emergency", "No used", "Disarm Latchkey", "Panic Restore", "Supervision (Inactive)",
- "Supervision Restore (Active)", "Low Battery", "Low Battery Restore", "AC Fail", "AC Restore",
- "Control Panel Low Battery", "Control Panel Low Battery Restore", "RF Jamming", "RF Jamming Restore",
- "Communications Failure", "Communications Restore", "Telephone Line Failure", "Telephone Line Restore",
- "Auto Test", "Fuse Failure", "Fuse Restore", "Keyfob Low Battery", "Keyfob Low Battery Restore",
- "Engineer Reset", "Battery Disconnect", "1-Way Keypad Low Battery", "1-Way Keypad Low Battery Restore",
- "1-Way Keypad Inactive", "1-Way Keypad Restore Active", "Low Battery", "Clean Me", "Fire Trouble",
- "Low Battery", "Battery Restore", "AC Fail", "AC Restore", "Supervision (Inactive)",
- "Supervision Restore (Active)", "Gas Alert", "Gas Alert Restore", "Gas Trouble", "Gas Trouble Restore",
- "Flood Alert", "Flood Alert Restore", "X-10 Trouble", "X-10 Trouble Restore", "Arm Home", "Arm Away",
- "Quick Arm Home", "Quick Arm Away", "Disarm", "Fail To Auto-Arm", "Enter To Test Mode",
- "Exit From Test Mode", "Force Arm", "Auto Arm", "Instant Arm", "Bypass", "Fail To Arm", "Door Open",
- "Communication Established By Control Panel", "System Reset", "Installer Programming", "Wrong Password",
- "Not Sys Event", "Not Sys Event", "Extreme Hot Alert", "Extreme Hot Alert Restore", "Freeze Alert",
- "Freeze Alert Restore", "Human Cold Alert", "Human Cold Alert Restore", "Human Hot Alert",
- "Human Hot Alert Restore", "Temperature Sensor Trouble", "Temperature Sensor Trouble Restore",
- // new values partition models
- "PIR Mask", "PIR Mask Restore", "", "", "", "", "", "", "", "", "", "", "Alarmed", "Restore", "Alarmed",
- "Restore", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Exit Installer", "Enter Installer",
- "", "", "", "", "" };
+ // Zone/User codes
+
+ private static final Map<Integer, String> ZONES_OR_USERS = Map.ofEntries( //
+ entry(0x00, "System"), //
+ entry(0x01, "Zone 1"), //
+ entry(0x02, "Zone 2"), //
+ entry(0x03, "Zone 3"), //
+ entry(0x04, "Zone 4"), //
+ entry(0x05, "Zone 5"), //
+ entry(0x06, "Zone 6"), //
+ entry(0x07, "Zone 7"), //
+ entry(0x08, "Zone 8"), //
+ entry(0x09, "Zone 9"), //
+ entry(0x0A, "Zone 10"), //
+ entry(0x0B, "Zone 11"), //
+ entry(0x0C, "Zone 12"), //
+ entry(0x0D, "Zone 13"), //
+ entry(0x0E, "Zone 14"), //
+ entry(0x0F, "Zone 15"), //
+ entry(0x10, "Zone 16"), //
+ entry(0x11, "Zone 17"), //
+ entry(0x12, "Zone 18"), //
+ entry(0x13, "Zone 19"), //
+ entry(0x14, "Zone 20"), //
+ entry(0x15, "Zone 21"), //
+ entry(0x16, "Zone 22"), //
+ entry(0x17, "Zone 23"), //
+ entry(0x18, "Zone 24"), //
+ entry(0x19, "Zone 25"), //
+ entry(0x1A, "Zone 26"), //
+ entry(0x1B, "Zone 27"), //
+ entry(0x1C, "Zone 28"), //
+ entry(0x1D, "Zone 29"), //
+ entry(0x1E, "Zone 30"), //
+ entry(0x1F, "Keyfob 1"), //
+ entry(0x20, "Keyfob 2"), //
+ entry(0x21, "Keyfob 3"), //
+ entry(0x22, "Keyfob 4"), //
+ entry(0x23, "Keyfob 5"), //
+ entry(0x24, "Keyfob 6"), //
+ entry(0x25, "Keyfob 7"), //
+ entry(0x26, "Keyfob 8"), //
+ entry(0x27, "User 1"), //
+ entry(0x28, "User 2"), //
+ entry(0x29, "User 3"), //
+ entry(0x2A, "User 4"), //
+ entry(0x2B, "User 5"), //
+ entry(0x2C, "User 6"), //
+ entry(0x2D, "User 7"), //
+ entry(0x2E, "User 8"), //
+ entry(0x2F, "Wireless Commander 1"), //
+ entry(0x30, "Wireless Commander 2"), //
+ entry(0x31, "Wireless Commander 3"), //
+ entry(0x32, "Wireless Commander 4"), //
+ entry(0x33, "Wireless Commander 5"), //
+ entry(0x34, "Wireless Commander 6"), //
+ entry(0x35, "Wireless Commander 7"), //
+ entry(0x36, "Wireless Commander 8"), //
+ entry(0x37, "Wireless Siren 1"), //
+ entry(0x38, "Wireless Siren 2"), //
+ entry(0x39, "Two-Way Wireless Keypad 1"), //
+ entry(0x3A, "Two-Way Wireless Keypad 2"), //
+ entry(0x3B, "Two-Way Wireless Keypad 3"), //
+ entry(0x3C, "Two-Way Wireless Keypad 4"), //
+ entry(0x3D, "X10 1"), //
+ entry(0x3E, "X10 2"), //
+ entry(0x3F, "X10 3"), //
+ entry(0x40, "X10 4"), //
+ entry(0x41, "X10 5"), //
+ entry(0x42, "X10 6"), //
+ entry(0x43, "X10 7"), //
+ entry(0x44, "X10 8"), //
+ entry(0x45, "X10 9"), //
+ entry(0x46, "X10 10"), //
+ entry(0x47, "X10 11"), //
+ entry(0x48, "X10 12"), //
+ entry(0x49, "X10 13"), //
+ entry(0x4A, "X10 14"), //
+ entry(0x4B, "X10 15"), //
+ entry(0x4C, "PGM"), //
+ entry(0x4D, "GSM"), //
+ entry(0x4E, "Powerlink"), //
+ entry(0x4F, "Proxy Tag 1"), //
+ entry(0x50, "Proxy Tag 2"), //
+ entry(0x51, "Proxy Tag 3"), //
+ entry(0x52, "Proxy Tag 4"), //
+ entry(0x53, "Proxy Tag 5"), //
+ entry(0x54, "Proxy Tag 6"), //
+ entry(0x55, "Proxy Tag 7"), //
+ entry(0x56, "Proxy Tag 8") //
+ );
/**
* Zone/User lookup
*/
- public static String getZoneOrUserString(int code) {
- return getValue(ZONE_OR_USER_TABLE, code);
+ public static String getZoneOrUser(int code) {
+ return ZONES_OR_USERS.getOrDefault(code, "UNKNOWN");
}
- private static final String[] ZONE_OR_USER_TABLE = new String[] { "System", "Zone 1", "Zone 2", "Zone 3", "Zone 4",
- "Zone 5", "Zone 6", "Zone 7", "Zone 8", "Zone 9", "Zone 10", "Zone 11", "Zone 12", "Zone 13", "Zone 14",
- "Zone 15", "Zone 16", "Zone 17", "Zone 18", "Zone 19", "Zone 20", "Zone 21", "Zone 22", "Zone 23",
- "Zone 24", "Zone 25", "Zone 26", "Zone 27", "Zone 28", "Zone 29", "Zone 30", "Fob 1", "Fob 2", "Fob 3",
- "Fob 4", "Fob 5", "Fob 6", "Fob 7", "Fob 8", "User 1", "User 2", "User 3", "User 4", "User 5", "User 6",
- "User 7", "User 8", "Pad 1", "Pad 2", "Pad 3", "Pad 4", "Pad 5", "Pad 6", "Pad 7", "Pad 8", "Siren 1",
- "Siren 2", "2Pad 1", "2Pad 2", "2Pad 3", "2Pad 4", "X10 1", "X10 2", "X10 3", "X10 4", "X10 5", "X10 6",
- "X10 7", "X10 8", "X10 9", "X10 10", "X10 11", "X10 12", "X10 13", "X10 14", "X10 15", "PGM", "GSM",
- "P-LINK", "PTag 1", "PTag 2", "PTag 3", "PTag 4", "PTag 5", "PTag 6", "PTag 7", "PTag 8" };
+ // Zone events
+
+ private static final Map<Integer, String> ZONE_EVENTS = Map.ofEntries( //
+ entry(0x00, "None"), //
+ entry(0x01, "Tamper Alarm"), //
+ entry(0x02, "Tamper Restore"), //
+ entry(0x03, "Open"), //
+ entry(0x04, "Closed"), //
+ entry(0x05, "Violated (Motion)"), //
+ entry(0x06, "Panic Alarm"), //
+ entry(0x07, "RF Jamming"), //
+ entry(0x08, "Tamper Open"), //
+ entry(0x09, "Communication Failure"), //
+ entry(0x0A, "Line Failure"), //
+ entry(0x0B, "Fuse"), //
+ entry(0x0C, "Not Active"), //
+ entry(0x0D, "Low Battery"), //
+ entry(0x0E, "AC Failure"), //
+ entry(0x0F, "Fire Alarm"), //
+ entry(0x10, "Emergency"), //
+ entry(0x11, "Siren Tamper"), //
+ entry(0x12, "Siren Tamper Restore"), //
+ entry(0x13, "Siren Low Battery"), //
+ entry(0x14, "Siren AC Fail") //
+ );
/**
- * Zone event lookup
+ * Zone Event lookup
*/
- public static String getZoneEventString(int code) {
- return getValue(ZONE_EVENT_TABLE, code);
+ public static String getZoneEvent(int code) {
+ return ZONE_EVENTS.getOrDefault(code, "UNKNOWN");
}
- private static final String[] ZONE_EVENT_TABLE = new String[] { "None", "Tamper Alarm", "Tamper Restore", "Open",
- "Closed", "Violated (Motion)", "Panic Alarm", "RF Jamming", "Tamper Open", "Communication Failure",
- "Line Failure", "Fuse", "Not Active", "Low Battery", "AC Failure", "Fire Alarm", "Emergency",
- "Siren Tamper", "Siren Tamper Restore", "Siren Low Battery", "Siren AC Fail" };
+ // Message types
+
+ private static final Map<Integer, String> ZONE_EVENT_TYPES = Map.ofEntries( //
+ entry(0x00, "None"), //
+ entry(0x01, "Alarm Message"), //
+ entry(0x02, "Open/Battery Message"), //
+ entry(0x03, "Inactive/Tamper Message"), //
+ entry(0x04, "Zone Message"), //
+ entry(0x06, "Enroll/Bypass Message") //
+ );
/**
* Message type lookup
*/
- public static String getMessageTypeString(int code) {
- return getValue(MESSAGE_TYPE_TABLE, code);
+ public static String getZoneEventType(int code) {
+ return ZONE_EVENT_TYPES.getOrDefault(code, "UNKNOWN");
}
-
- private static final String[] MESSAGE_TYPE_TABLE = new String[] { "None", "Log Message", "Status Message",
- "Tamper Message", "Zone Message", "Unknown", "Enroll/Bypass Message" };
}
import org.openhab.binding.powermax.internal.state.PowermaxArmMode;
import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
+import org.openhab.binding.powermax.internal.state.PowermaxSensorType;
import org.openhab.binding.powermax.internal.state.PowermaxState;
import org.openhab.binding.powermax.internal.state.PowermaxZoneSettings;
}
private static boolean[] zoneBits(byte[] zoneBytes) {
- boolean[] zones = new boolean[32];
- char[] binary = new BigInteger(zoneBytes).toString(2).toCharArray();
- int len = binary.length - 1;
+ boolean[] zones = new boolean[33];
+ BigInteger bigint = new BigInteger(1, zoneBytes);
- for (int i = len; i >= 0; i--) {
- zones[len - i + 1] = (binary[i] == '1');
+ for (int i = 1; i <= 32; i++) {
+ zones[i] = bigint.testBit(i - 1);
}
return zones;
byte[] message = getRawData();
byte eventType = message[3];
- String eventTypeStr = PowermaxMessageConstants.getMessageTypeString(eventType & 0x000000FF);
+ String eventTypeStr = PowermaxMessageConstants.getZoneEventType(eventType & 0x000000FF);
debug("Event type", eventType, eventTypeStr);
- if (eventType == 0x02) {
+ // Each event type except 0x04 contains two sets of zone bitmasks.
+ // Each set is four bytes (32 bits) where each bit indicates the state
+ // of the corresponding zone (1 = set, 0 = unset).
+
+ if (eventType == 0x01) {
+ // These bits are set when a zone causes an alarm
+ //
+ // Set 1: Alarm caused by zone being open/tripped
+ // Set 2: Alarm caused by a tamper
+ //
+ // Note: active alarms are cleared when the Memory flag is turned off
+ // (the panel won't send a follow-up event with these bits set to zero)
+
+ byte[] alarmStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
+ byte[] tamperStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
+
+ boolean[] alarmStatus = zoneBits(alarmStatusBytes);
+ boolean[] tamperStatus = zoneBits(tamperStatusBytes);
+
+ String alarmStatusStr = zoneList(alarmStatusBytes);
+ String tamperStatusStr = zoneList(tamperStatusBytes);
+
+ panelSettings.getZoneRange().forEach(i -> {
+ updatedState.getZone(i).alarmed.setValue(alarmStatus[i]);
+ updatedState.getZone(i).tamperAlarm.setValue(tamperStatus[i]);
+ });
+
+ debug("Alarm status", alarmStatusBytes, alarmStatusStr);
+ debug("Tamper alarm status", tamperStatusBytes, tamperStatusStr);
+ } else if (eventType == 0x02) {
+ // Set 1: List of zones that are open/tripped
+ // Set 2: List of zones that have a low-battery condition
+
byte[] zoneStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
byte[] batteryStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
String zoneStatusStr = zoneList(zoneStatusBytes);
String batteryStatusStr = zoneList(batteryStatusBytes);
- for (int i = 1; i <= panelSettings.getNbZones(); i++) {
+ panelSettings.getZoneRange().forEach(i -> {
updatedState.getZone(i).tripped.setValue(zoneStatus[i]);
updatedState.getZone(i).lowBattery.setValue(batteryStatus[i]);
- }
+ });
debug("Zone status", zoneStatusBytes, zoneStatusStr);
debug("Battery status", batteryStatusBytes, batteryStatusStr);
+ } else if (eventType == 0x03) {
+ // Set 1: Inactivity / loss of supervision
+ // Set 2: Zone has an active tamper condition
+
+ byte[] inactiveStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
+ byte[] tamperStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
+
+ boolean[] inactiveStatus = zoneBits(inactiveStatusBytes);
+ boolean[] tamperStatus = zoneBits(tamperStatusBytes);
+
+ String inactiveStatusStr = zoneList(inactiveStatusBytes);
+ String tamperStatusStr = zoneList(tamperStatusBytes);
+
+ panelSettings.getZoneRange().forEach(i -> {
+ updatedState.getZone(i).inactive.setValue(inactiveStatus[i]);
+ updatedState.getZone(i).tampered.setValue(tamperStatus[i]);
+ });
+
+ debug("Inactive status", inactiveStatusBytes, inactiveStatusStr);
+ debug("Tamper status", tamperStatusBytes, tamperStatusStr);
} else if (eventType == 0x04) {
+ // System & zone status message (not like the other event types)
+
byte sysStatus = message[4];
byte sysFlags = message[5];
- byte eventZone = message[6];
- byte zoneEType = message[7];
+ int eventZone = message[6] & 0x000000FF;
+ int zoneEType = message[7] & 0x000000FF;
int x10Status = (message[10] & 0x000000FF) | ((message[11] << 8) & 0x0000FF00);
- String eventZoneStr = PowermaxMessageConstants.getZoneOrUserString(eventZone & 0x000000FF);
- String zoneETypeStr = PowermaxMessageConstants.getZoneEventString(zoneEType & 0x000000FF);
+ String eventZoneStr = panelSettings.getZoneOrUserName(eventZone);
+ String zoneETypeStr = PowermaxMessageConstants.getZoneEvent(zoneEType);
+
+ if (zoneEType != 0x00 && eventZone > 0 && eventZone <= panelSettings.getNbZones()) {
+ updatedState.getZone(eventZone).lastMessage.setValue(zoneETypeStr);
+ updatedState.getZone(eventZone).lastMessageTime.setValue(System.currentTimeMillis());
+ }
if (zoneEType == 0x03) {
+ // Open
updatedState.getZone(eventZone).tripped.setValue(true);
updatedState.getZone(eventZone).lastTripped.setValue(System.currentTimeMillis());
} else if (zoneEType == 0x04) {
+ // Closed
updatedState.getZone(eventZone).tripped.setValue(false);
} else if (zoneEType == 0x05) {
+ // Violated (Motion)
PowermaxZoneSettings zone = panelSettings.getZoneSettings(eventZone);
if ((zone != null) && zone.getSensorType().equalsIgnoreCase("unknown")) {
- zone.setSensorType("Motion");
+ zone.setSensorType(PowermaxSensorType.MOTION_SENSOR_1.getLabel());
}
updatedState.getZone(eventZone).tripped.setValue(true);
updatedState.getZone(eventZone).lastTripped.setValue(System.currentTimeMillis());
updatedState.alertInMemory.setValue(true);
} else {
updatedState.alertInMemory.setValue(false);
+
+ // When the memory flag is cleared, also clear all zone alarms and tamper alarms
+ panelSettings.getZoneRange().forEach(i -> {
+ updatedState.getZone(i).alarmed.setValue(false);
+ updatedState.getZone(i).tamperAlarm.setValue(false);
+ });
}
if (((sysFlags >> 2) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Trouble, ";
updatedState.bypass.setValue(true);
} else {
updatedState.bypass.setValue(false);
- for (int i = 1; i <= panelSettings.getNbZones(); i++) {
+ panelSettings.getZoneRange().forEach(i -> {
updatedState.getZone(i).bypassed.setValue(false);
- }
+ });
}
if (((sysFlags >> 4) & 0x1) == 1) {
sysStatusStr = sysStatusStr + "Last 10 seconds, ";
if (eventZone == 0xFF) {
sysStatusStr = sysStatusStr + " from Panel, ";
} else if (eventZone > 0) {
- sysStatusStr = sysStatusStr + String.format(" in Zone %d, ", eventZone);
+ sysStatusStr = sysStatusStr + String.format(" in %s, ", eventZoneStr);
} else {
sysStatusStr = sysStatusStr + ", ";
}
debug("Zone event type", zoneEType, zoneETypeStr);
debug("X10 status", x10Status);
- for (int i = 1; i <= panelSettings.getNbZones(); i++) {
+ panelSettings.getZoneRange().forEach(i -> {
PowermaxZoneSettings zone = panelSettings.getZoneSettings(i);
if (zone != null) {
- // mode: armed or not: 4=armed home; 5=armed away
+ // mode: armed or not
int mode = sysStatus & 0x0000000F;
// Zone is shown as armed if
// the sensor type always triggers an alarm
- // or the system is armed away (mode = 5)
- // or the system is armed home (mode = 4) and the zone is not interior(-follow)
- boolean armed = (!zone.getType().equalsIgnoreCase("Non-Alarm") && (zone.isAlwaysInAlarm()
- || (mode == 0x5) || ((mode == 0x4) && !zone.getType().equalsIgnoreCase("Interior-Follow")
- && !zone.getType().equalsIgnoreCase("Interior"))));
+ // or the system is armed away
+ // or the system is armed home and the zone is not interior(-follow)
+ boolean armed = (!zone.getType().equalsIgnoreCase("Non-Alarm")
+ && (zone.isAlwaysInAlarm() || (mode == PowermaxArmMode.ARMED_AWAY.getCode())
+ || ((mode == PowermaxArmMode.ARMED_HOME.getCode())
+ && !zone.getType().equalsIgnoreCase("Interior-Follow")
+ && !zone.getType().equalsIgnoreCase("Interior"))));
updatedState.getZone(i).armed.setValue(armed);
}
- }
+ });
} else if (eventType == 0x06) {
+ // Set 1: List of zones that are enrolled (we don't currently use this)
+ // Set 2: List of zones that are bypassed
+
byte[] zoneBypassBytes = zoneBytes(message[8], message[9], message[10], message[11]);
boolean[] zoneBypass = zoneBits(zoneBypassBytes);
String zoneBypassStr = zoneList(zoneBypassBytes);
- for (int i = 1; i <= panelSettings.getNbZones(); i++) {
+ panelSettings.getZoneRange().forEach(i -> {
updatedState.getZone(i).bypassed.setValue(zoneBypass[i]);
- }
+ });
debug("Zone bypass", zoneBypassBytes, zoneBypassStr);
}
+ // Note: in response to a STATUS request, the panel will also send
+ // messages with eventType = 0x05, 0x07, 0x08, and 0x09 but these
+ // haven't been decoded yet
+
return updatedState;
}
}
import static org.openhab.binding.powermax.internal.PowermaxBindingConstants.*;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.powermax.internal.message.PowermaxMessageConstants;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
public BooleanValue alarmActive = new BooleanValue(this, ALARM_ACTIVE);
public BooleanValue trouble = new BooleanValue(this, TROUBLE);
public BooleanValue alertInMemory = new BooleanValue(this, ALERT_IN_MEMORY);
+ public BooleanValue ringing = new BooleanValue(this, RINGING);
+ public DateTimeValue ringingSince = new DateTimeValue(this, "_ringing_since");
public StringValue statusStr = new StringValue(this, SYSTEM_STATUS);
public StringValue armMode = new StringValue(this, "_arm_mode");
public BooleanValue downloadSetupRequired = new BooleanValue(this, "_download_setup_required");
public DateTimeValue lastKeepAlive = new DateTimeValue(this, "_last_keepalive");
- public DateTimeValue lastMessageReceived = new DateTimeValue(this, "_last_message_received");
- public StringValue panelStatus = new StringValue(this, "_panel_status");
- public StringValue alarmType = new StringValue(this, "_alarm_type");
- public StringValue troubleType = new StringValue(this, "_trouble_type");
+ public DateTimeValue lastMessageTime = new DateTimeValue(this, LAST_MESSAGE_TIME);
public DynamicValue<Boolean> isArmed = new DynamicValue<>(this, SYSTEM_ARMED, () -> {
return isArmed();
return new StringType(getShortArmMode());
});
+ public DynamicValue<String> activeAlerts = new DynamicValue<>(this, ACTIVE_ALERTS, () -> {
+ return getActiveAlerts();
+ }, () -> {
+ return new StringType(getActiveAlerts());
+ });
+
public DynamicValue<Boolean> pgmStatus = new DynamicValue<>(this, PGM_STATUS, () -> {
return getPGMX10DeviceStatus(0);
}, () -> {
return getPGMX10DeviceStatus(0) ? OnOffType.ON : OnOffType.OFF;
});
+ private PowermaxPanelSettings panelSettings;
private PowermaxZoneState[] zones;
private Boolean[] pgmX10DevicesStatus;
private byte[] updateSettings;
private String[] eventLog;
private Map<Integer, Byte> updatedZoneNames;
private Map<Integer, Integer> updatedZoneInfos;
+ private List<PowermaxActiveAlert> activeAlertList;
+ private List<PowermaxActiveAlert> activeAlertQueue;
+
+ private enum PowermaxAlertAction {
+ ADD,
+ CLEAR,
+ CLEAR_ALL
+ }
+
+ private class PowermaxActiveAlert {
+ public final @Nullable PowermaxAlertAction action;
+ public final int zone;
+ public final int code;
+
+ public PowermaxActiveAlert(@Nullable PowermaxAlertAction action, int zone, int code) {
+ this.action = action;
+ this.zone = zone;
+ this.code = code;
+ }
+ }
/**
* Constructor (default values)
*/
public PowermaxState(PowermaxPanelSettings panelSettings, TimeZoneProvider timeZoneProvider) {
super(timeZoneProvider);
+ this.panelSettings = panelSettings;
zones = new PowermaxZoneState[panelSettings.getNbZones()];
for (int i = 0; i < panelSettings.getNbZones(); i++) {
pgmX10DevicesStatus = new Boolean[panelSettings.getNbPGMX10Devices()];
updatedZoneNames = new HashMap<>();
updatedZoneInfos = new HashMap<>();
+ activeAlertList = new ArrayList<>();
+ activeAlertQueue = new ArrayList<>();
+
+ // Most fields will get populated by the initial download, but we set
+ // the ringing indicator in response to an alarm message. We have no
+ // other way to know if the siren is ringing so we'll initialize it to
+ // false.
+
+ this.ringing.setValue(false);
}
/**
this.updatedZoneInfos.put(zoneIdx, zoneInfo);
}
+ // This is an attempt to add persistence to an otherwise (mostly) stateless class.
+ // All of the other values are either present or null, and it's easy to build a
+ // delta state based only on which values are non-null. But these system events
+ // are different because each event can be set by one message and cleared by a
+ // later message. So to preserve the semantics of the state class, we'll keep a
+ // queue of incoming changes, and apply them only when the delta state is resolved.
+
+ public boolean hasActiveAlertsQueued() {
+ return !activeAlertQueue.isEmpty();
+ }
+
+ public String getActiveAlerts() {
+ if (activeAlertList.isEmpty()) {
+ return "None";
+ }
+
+ List<String> alerts = new ArrayList<>();
+
+ activeAlertList.forEach(e -> {
+ String message = PowermaxMessageConstants.getSystemEvent(e.code).toString();
+ String alert = e.zone == 0 ? message
+ : String.format("%s (%s)", message, panelSettings.getZoneOrUserName(e.zone));
+
+ alerts.add(alert);
+ });
+
+ return String.join(", ", alerts);
+ }
+
+ public void addActiveAlert(int zoneIdx, int code) {
+ PowermaxActiveAlert alert = new PowermaxActiveAlert(PowermaxAlertAction.ADD, zoneIdx, code);
+ activeAlertQueue.add(alert);
+ }
+
+ public void clearActiveAlert(int zoneIdx, int code) {
+ PowermaxActiveAlert alert = new PowermaxActiveAlert(PowermaxAlertAction.CLEAR, zoneIdx, code);
+ activeAlertQueue.add(alert);
+ }
+
+ public void clearAllActiveAlerts() {
+ PowermaxActiveAlert alert = new PowermaxActiveAlert(PowermaxAlertAction.CLEAR_ALL, 0, 0);
+ activeAlertQueue.add(alert);
+ }
+
+ public void resolveActiveAlerts(@Nullable PowermaxState previousState) {
+ copyActiveAlertsFrom(previousState);
+
+ activeAlertQueue.forEach(alert -> {
+ if (alert.action == PowermaxAlertAction.CLEAR_ALL) {
+ activeAlertList.clear();
+ } else {
+ activeAlertList.removeIf(e -> e.zone == alert.zone && e.code == alert.code);
+
+ if (alert.action == PowermaxAlertAction.ADD) {
+ activeAlertList.add(new PowermaxActiveAlert(null, alert.zone, alert.code));
+ }
+ }
+ });
+ }
+
+ private void copyActiveAlertsFrom(@Nullable PowermaxState state) {
+ activeAlertList = new ArrayList<>();
+
+ if (state != null) {
+ state.activeAlertList.forEach(alert -> {
+ activeAlertList.add(new PowermaxActiveAlert(null, alert.zone, alert.code));
+ });
+ }
+ }
+
/**
* Get the panel mode
*
thisValue.setValue(null);
}
}
+
+ if (hasActiveAlertsQueued()) {
+ resolveActiveAlerts(otherState);
+ }
}
/**
setEventLog(i, update.getEventLog(i));
}
}
+
+ if (update.hasActiveAlertsQueued()) {
+ copyActiveAlertsFrom(update);
+ }
}
@Override
for (Value<?> value : getValues()) {
if ((value.getChannel() != null) && (value.getValue() != null)) {
String channel = value.getChannel();
- String v_str = value.getValue().toString();
+ String vStr = value.getValue().toString();
String state = value.getState().toString();
- str += "\n - " + channel + " = " + v_str;
- if (!v_str.equals(state)) {
+ str += "\n - " + channel + " = " + vStr;
+ if (!vStr.equals(state)) {
str += " (" + state + ")";
}
}
for (Value<?> value : zones[i - 1].getValues()) {
if ((value.getChannel() != null) && (value.getValue() != null)) {
String channel = value.getChannel();
- String v_str = value.getValue().toString();
+ String vStr = value.getValue().toString();
String state = value.getState().toString();
- str += String.format("\n - sensor zone %d %s = %s", i, channel, v_str);
- if (!v_str.equals(state)) {
+ str += String.format("\n - sensor zone %d %s = %s", i, channel, vStr);
+ if (!vStr.equals(state)) {
str += " (" + state + ")";
}
}
}
}
+ String alarms = getActiveAlerts();
+ str += "\n - active alarms/alerts = " + (alarms == null ? "null" : alarms);
+
return str;
}
}