]> git.basschouten.com Git - openhab-addons.git/commitdiff
[powermax] Introduce some new channels for better status reporting (#10624)
authorRon Isaacson <isaacson.ron@gmail.com>
Wed, 8 Sep 2021 16:58:35 +0000 (12:58 -0400)
committerGitHub <noreply@github.com>
Wed, 8 Sep 2021 16:58:35 +0000 (18:58 +0200)
* [powermax] Introduce some new channels for better status reporting

* New panel channels:
    - Ringing indicator (the siren is currently sounding)
    - Date/time the last message was received from the panel
    - List of all active alarms and alerts (similar to panel's
      Memory list, but items get removed from the list as conditions
      resolve)
* New zone channels:
    - Alarmed indicator (zone is in alarm, or has an alarm in memory)
    - Tamper alarm indicator (same but for a tamper condition)
    - Inactive indicator
    - Tamper indicator (zone is actively tampered right now)
    - Last status message received for this zone, and when
* Use descriptive names for zones in log messages. If you create a
  Thing for a zone, it will use that Thing's label in all reporting
  for that zone. If there's no Thing then it will attempt to use the
  zone label from the panel (e.g. "Basement").
* Clear all channels during startup to keep from displaying stale
  values loaded from persistence
* Also includes some minor SAT fixes (checkstyle, spotbugs)

Signed-off-by: Ron Isaacson <isaacson.ron@gmail.com>
* Incorporate review feedback from lolodomo

Signed-off-by: Ron Isaacson <isaacson.ron@gmail.com>
19 files changed:
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxThingHandler.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxAlarmType.java [deleted file]
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxCommManager.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxEventLogMessage.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxMessageConstants.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxPanelMessage.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxStatusMessage.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxTroubleType.java [deleted file]
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxPanelSettings.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxStateContainer.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneSettings.java
bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneState.java
bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml
bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml
bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml
bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml

index a0a2f1d25958568f99975485f4dfbd90032c7a13..94a462406ec231f46090d9e29ac40840d3a6b8e0 100644 (file)
@@ -47,8 +47,11 @@ public class PowermaxBindingConstants {
 
     // List of all Channel ids
     public static final String MODE = "mode";
+    public static final String LAST_MESSAGE_TIME = "last_message_time";
+    public static final String ACTIVE_ALERTS = "active_alerts";
     public static final String TROUBLE = "trouble";
     public static final String ALERT_IN_MEMORY = "alert_in_memory";
+    public static final String RINGING = "ringing";
     public static final String SYSTEM_STATUS = "system_status";
     public static final String READY = "ready";
     public static final String WITH_ZONES_BYPASSED = "with_zones_bypassed";
@@ -58,8 +61,14 @@ public class PowermaxBindingConstants {
     public static final String TRIPPED = "tripped";
     public static final String LAST_TRIP = "last_trip";
     public static final String BYPASSED = "bypassed";
+    public static final String ALARMED = "alarmed";
+    public static final String TAMPER_ALARM = "tamper_alarm";
+    public static final String INACTIVE = "inactive";
+    public static final String TAMPERED = "tampered";
     public static final String ARMED = "armed";
     public static final String LOCKED = "locked";
+    public static final String ZONE_LAST_MESSAGE = "last_message";
+    public static final String ZONE_LAST_MESSAGE_TIME = "last_message_time";
     public static final String LOW_BATTERY = "low_battery";
     public static final String PGM_STATUS = "pgm_status";
     public static final String X10_STATUS = "x10_status";
index 6ff9b22c9c65e05278678511d3cb5bc168bce6b6..405c2b97d705f799779b2ea1c3127c80fa4591bb 100644 (file)
@@ -143,6 +143,7 @@ public class PowermaxBridgeHandler extends BaseBridgeHandler implements Powermax
                     try {
                         logger.trace("Powermax job...");
                         updateMotionSensorState();
+                        updateRingingState();
                         if (isConnected()) {
                             checkKeepAlive();
                             commManager.retryDownloadSetup(remainingDownloadAttempts);
@@ -254,6 +255,23 @@ public class PowermaxBridgeHandler extends BaseBridgeHandler implements Powermax
         }
     }
 
+    /**
+     * Turn off the Ringing flag when the bell time expires
+     */
+    private void updateRingingState() {
+        if (currentState != null && Boolean.TRUE.equals(currentState.ringing.getValue())) {
+            long now = System.currentTimeMillis();
+            long bellTime = getPanelSettings().getBellTime() * ONE_MINUTE;
+
+            if ((currentState.ringingSince.getValue() + bellTime) < now) {
+                PowermaxState updateState = commManager.createNewState();
+                updateState.ringing.setValue(false);
+                updateChannelsFromAlarmState(RINGING, updateState);
+                currentState.merge(updateState);
+            }
+        }
+    }
+
     /*
      * Check that we're actively communicating with the panel
      */
@@ -266,8 +284,8 @@ public class PowermaxBridgeHandler extends BaseBridgeHandler implements Powermax
             commManager.sendRestoreMessage();
             currentState.lastKeepAlive.setValue(now);
         } else if (!Boolean.TRUE.equals(currentState.downloadMode.getValue())
-                && (currentState.lastMessageReceived.getValue() != null)
-                && ((now - currentState.lastMessageReceived.getValue()) > FIVE_MINUTES)) {
+                && (currentState.lastMessageTime.getValue() != null)
+                && ((now - currentState.lastMessageTime.getValue()) > FIVE_MINUTES)) {
             // In Standard mode: ping the panel every so often to detect disconnects
             commManager.sendMessage(PowermaxSendType.STATUS);
         }
@@ -281,6 +299,7 @@ public class PowermaxBridgeHandler extends BaseBridgeHandler implements Powermax
             openConnection();
             logger.debug("openConnection(): connected");
             updateStatus(ThingStatus.ONLINE);
+            updateChannelsFromAlarmState(currentState);
             if (forceStandardMode) {
                 currentState.powerlinkMode.setValue(false);
                 updateChannelsFromAlarmState(MODE, currentState);
@@ -455,12 +474,12 @@ public class PowermaxBridgeHandler extends BaseBridgeHandler implements Powermax
 
         boolean doProcessSettings = (updateState.powerlinkMode.getValue() != null);
 
-        for (int i = 1; i <= getPanelSettings().getNbZones(); i++) {
+        getPanelSettings().getZoneRange().forEach(i -> {
             if (Boolean.TRUE.equals(updateState.getZone(i).armed.getValue())
                     && Boolean.TRUE.equals(currentState.getZone(i).bypassed.getValue())) {
                 updateState.getZone(i).armed.setValue(false);
             }
-        }
+        });
 
         updateState.keepOnlyDifferencesWith(currentState);
         updateChannelsFromAlarmState(updateState);
index 4e20a14184328ac200e1e78dc91934c16eca97a5..1b2f7f179ad825bfe778a31db1326f3901ac8418 100644 (file)
@@ -184,10 +184,10 @@ public class PowermaxThingHandler extends BaseThingHandler implements PowermaxPa
             int num = getConfigAs(PowermaxZoneConfiguration.class).zoneNumber.intValue();
 
             for (Value<?> value : state.getZone(num).getValues()) {
-                String v_channel = value.getChannel();
+                String vChannel = value.getChannel();
 
-                if (channel.equals(v_channel) && (value.getValue() != null)) {
-                    updateState(v_channel, value.getState());
+                if (channel.equals(vChannel) && (value.getValue() != null)) {
+                    updateState(vChannel, value.getState());
                 }
             }
         } else if (getThing().getThingTypeUID().equals(THING_TYPE_X10)) {
@@ -256,6 +256,9 @@ public class PowermaxThingHandler extends BaseThingHandler implements PowermaxPa
                     updateStatus(ThingStatus.ONLINE);
                     logger.debug("Set handler status to ONLINE for thing {} (zone number {} paired)",
                             getThing().getUID(), config.zoneNumber);
+
+                    logger.debug("Using name '{}' for {}", getThing().getLabel(), getThing().getUID());
+                    zoneSettings.setName(getThing().getLabel());
                 }
             }
         }
diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxAlarmType.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxAlarmType.java
deleted file mode 100644 (file)
index ea43b20..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.powermax.internal.message;
-
-/**
- * All defined alarm types
- *
- * @author Laurent Garnier - Initial contribution
- */
-public enum PowermaxAlarmType {
-
-    ALARM_TYPE_1(0x01, "Intruder"),
-    ALARM_TYPE_2(0x02, "Intruder"),
-    ALARM_TYPE_3(0x03, "Intruder"),
-    ALARM_TYPE_4(0x04, "Intruder"),
-    ALARM_TYPE_5(0x05, "Intruder"),
-    ALARM_TYPE_6(0x06, "Tamper"),
-    ALARM_TYPE_7(0x07, "Tamper"),
-    ALARM_TYPE_8(0x08, "Tamper"),
-    ALARM_TYPE_9(0x09, "Tamper"),
-    ALARM_TYPE_10(0x0B, "Panic"),
-    ALARM_TYPE_11(0x0C, "Panic"),
-    ALARM_TYPE_12(0x20, "Fire"),
-    ALARM_TYPE_13(0x23, "Emergency"),
-    ALARM_TYPE_14(0x49, "Gas"),
-    ALARM_TYPE_15(0x4D, "Flood");
-
-    private int code;
-    private String label;
-
-    private PowermaxAlarmType(int code, String label) {
-        this.code = code;
-        this.label = label;
-    }
-
-    /**
-     * @return the code identifying the alarm type
-     */
-    public int getCode() {
-        return code;
-    }
-
-    /**
-     * @return the label associated to the alarm type
-     */
-    public String getLabel() {
-        return label;
-    }
-
-    /**
-     * Get the ENUM value from its identifying code
-     *
-     * @param code the identifying code
-     *
-     * @return the corresponding ENUM value
-     *
-     * @throws IllegalArgumentException if no ENUM value corresponds to this code
-     */
-    public static PowermaxAlarmType fromCode(int code) throws IllegalArgumentException {
-        for (PowermaxAlarmType alarmType : PowermaxAlarmType.values()) {
-            if (alarmType.getCode() == code) {
-                return alarmType;
-            }
-        }
-
-        throw new IllegalArgumentException("Invalid code: " + code);
-    }
-}
index 64c6aeb6adfa36bb15a7e3f9e377892f6d48f8f8..175f6879d1c53c020fe8c76ad7c6c35b84d240b5 100644 (file)
@@ -259,7 +259,7 @@ public class PowermaxCommManager implements PowermaxMessageEventListener {
             updateState = createNewState();
         }
 
-        updateState.lastMessageReceived.setValue(System.currentTimeMillis());
+        updateState.lastMessageTime.setValue(System.currentTimeMillis());
 
         if (updateState.getUpdateSettings() != null) {
             panelSettings.updateRawSettings(updateState.getUpdateSettings());
index 1b24d77d5794e3bebe20ed1c2c18226bda8f0287..05180eeacb62b8e4c325b42852e046b383a98c97 100644 (file)
@@ -61,8 +61,8 @@ public class PowermaxEventLogMessage extends PowermaxBaseMessage {
             String timestamp = String.format("%02d/%02d/%04d %02d:%02d:%02d", day, month, year, hour, minute, second);
             byte eventZone = message[10];
             byte logEvent = message[11];
-            String logEventStr = PowermaxMessageConstants.getSystemEventString(logEvent & 0x000000FF);
-            String logUserStr = PowermaxMessageConstants.getZoneOrUserString(eventZone & 0x000000FF);
+            String logEventStr = PowermaxMessageConstants.getSystemEvent(logEvent & 0x000000FF).toString();
+            String logUserStr = panelSettings.getZoneOrUserName(eventZone & 0x000000FF);
 
             String eventStr;
             if (panelSettings.getPanelType().getPartitions() > 1) {
index f4209c9faab24909f9d1a49e1a5bcb1bb8884887..25592e57ff024f01bb4c2c164a902f91a9c29e85 100644 (file)
  */
 package org.openhab.binding.powermax.internal.message;
 
+import static java.util.Map.entry;
+
+import java.util.Map;
+
 /**
  * Constants used in Powermax messages
  *
@@ -22,81 +26,377 @@ public class PowermaxMessageConstants {
     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" };
 }
index a08313472e23f2713d89faeb78e23306050408c5..711570374c0526e41d1c37cac5e4aced95f2bcd3 100644 (file)
@@ -12,6 +12,7 @@
  */
 package org.openhab.binding.powermax.internal.message;
 
+import org.openhab.binding.powermax.internal.message.PowermaxMessageConstants.PowermaxSysEvent;
 import org.openhab.binding.powermax.internal.state.PowermaxState;
 
 /**
@@ -48,33 +49,37 @@ public class PowermaxPanelMessage extends PowermaxBaseMessage {
             byte eventZone = message[2 + 2 * i];
             byte logEvent = message[3 + 2 * i];
             int eventType = logEvent & 0x0000007F;
-            String logEventStr = PowermaxMessageConstants.getSystemEventString(eventType);
-            String logUserStr = PowermaxMessageConstants.getZoneOrUserString(eventZone & 0x000000FF);
-            updatedState.panelStatus.setValue(logEventStr + " (" + logUserStr + ")");
+            PowermaxSysEvent sysEvent = PowermaxMessageConstants.getSystemEvent(eventType);
+            String logEventStr = sysEvent.toString();
+            String logUserStr = commManager.getPanelSettings().getZoneOrUserName(eventZone & 0x000000FF);
 
             debug("Event " + i + " zone code", eventZone, logUserStr);
             debug("Event " + i + " event code", eventType, logEventStr);
 
-            String alarmStatus;
-            try {
-                PowermaxAlarmType alarmType = PowermaxAlarmType.fromCode(eventType);
-                alarmStatus = alarmType.getLabel();
-            } catch (IllegalArgumentException e) {
-                alarmStatus = "None";
+            if (sysEvent.isAlarm() || sysEvent.isSilentAlarm() || sysEvent.isAlert() || sysEvent.isPanic()
+                    || sysEvent.isTrouble()) {
+                updatedState.addActiveAlert(eventZone, eventType);
             }
-            updatedState.alarmType.setValue(alarmStatus);
-
-            String troubleStatus;
-            try {
-                PowermaxTroubleType troubleType = PowermaxTroubleType.fromCode(eventType);
-                troubleStatus = troubleType.getLabel();
-            } catch (IllegalArgumentException e) {
-                troubleStatus = "None";
+
+            if (sysEvent.isAlarm() || (sysEvent.isPanic() && !commManager.getPanelSettings().isSilentPanic())) {
+                updatedState.ringing.setValue(true);
+                updatedState.ringingSince.setValue(System.currentTimeMillis());
+            }
+
+            if (sysEvent.isCancel() || sysEvent.isGeneralRestore() || sysEvent.isReset()) {
+                updatedState.ringing.setValue(false);
+            }
+
+            if (sysEvent.isRestore()) {
+                updatedState.clearActiveAlert(eventZone, sysEvent.getRestoreFor());
+            }
+
+            if (sysEvent.isGeneralRestore() || sysEvent.isReset()) {
+                updatedState.clearAllActiveAlerts();
             }
-            updatedState.troubleType.setValue(troubleStatus);
 
-            if (eventType == 0x60) {
-                // System reset
+            if (sysEvent.isReset()) {
+                updatedState.clearAllActiveAlerts();
                 updatedState.downloadSetupRequired.setValue(true);
             }
         }
index d30bf0951e309abb462375e19c5e64410cac5edd..24c5e65eabe890d1013e6821f1b5edb2f143f227 100644 (file)
@@ -18,6 +18,7 @@ import java.util.List;
 
 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;
 
@@ -33,12 +34,11 @@ public class PowermaxStatusMessage extends PowermaxBaseMessage {
     }
 
     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;
@@ -78,11 +78,43 @@ public class PowermaxStatusMessage extends PowermaxBaseMessage {
 
         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]);
 
@@ -92,32 +124,62 @@ public class PowermaxStatusMessage extends PowermaxBaseMessage {
             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());
@@ -141,6 +203,12 @@ public class PowermaxStatusMessage extends PowermaxBaseMessage {
                 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, ";
@@ -153,9 +221,9 @@ public class PowermaxStatusMessage extends PowermaxBaseMessage {
                 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, ";
@@ -165,7 +233,7 @@ public class PowermaxStatusMessage extends PowermaxBaseMessage {
                 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 + ", ";
                 }
@@ -196,33 +264,42 @@ public class PowermaxStatusMessage extends PowermaxBaseMessage {
             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;
     }
 }
diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxTroubleType.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxTroubleType.java
deleted file mode 100644 (file)
index a9cadab..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.powermax.internal.message;
-
-/**
- * All defined trouble types
- *
- * @author Laurent Garnier - Initial contribution
- */
-public enum PowermaxTroubleType {
-
-    TROUBLE_TYPE_1(0x0A, "Communication"),
-    TROUBLE_TYPE_2(0x0F, "General"),
-    TROUBLE_TYPE_3(0x29, "Battery"),
-    TROUBLE_TYPE_4(0x2B, "Power"),
-    TROUBLE_TYPE_5(0x2D, "Battery"),
-    TROUBLE_TYPE_6(0x2F, "Jamming"),
-    TROUBLE_TYPE_7(0x31, "Communication"),
-    TROUBLE_TYPE_8(0x33, "Telephone"),
-    TROUBLE_TYPE_9(0x36, "Power"),
-    TROUBLE_TYPE_10(0x38, "Battery"),
-    TROUBLE_TYPE_11(0x3B, "Battery"),
-    TROUBLE_TYPE_12(0x3C, "Battery"),
-    TROUBLE_TYPE_13(0x40, "Battery"),
-    TROUBLE_TYPE_14(0x43, "Battery");
-
-    private int code;
-    private String label;
-
-    private PowermaxTroubleType(int code, String label) {
-        this.code = code;
-        this.label = label;
-    }
-
-    /**
-     * @return the code identifying the trouble type
-     */
-    public int getCode() {
-        return code;
-    }
-
-    /**
-     * @return the label associated to the trouble type
-     */
-    public String getLabel() {
-        return label;
-    }
-
-    /**
-     * Get the ENUM value from its identifying code
-     *
-     * @param code the identifying code
-     *
-     * @return the corresponding ENUM value
-     *
-     * @throws IllegalArgumentException if no ENUM value corresponds to this code
-     */
-    public static PowermaxTroubleType fromCode(int code) throws IllegalArgumentException {
-        for (PowermaxTroubleType troubleType : PowermaxTroubleType.values()) {
-            if (troubleType.getCode() == code) {
-                return troubleType;
-            }
-        }
-
-        throw new IllegalArgumentException("Invalid code: " + code);
-    }
-}
index 14785583510ee901941e6e7d3bf553b8054801ef..8fed886270c88fee7c0b818b4cf34e5defadb6f8 100644 (file)
@@ -16,7 +16,9 @@ import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
+import java.util.stream.IntStream;
 
+import org.openhab.binding.powermax.internal.message.PowermaxMessageConstants;
 import org.openhab.binding.powermax.internal.message.PowermaxSendType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -75,6 +77,20 @@ public class PowermaxPanelSettings {
         return panelType;
     }
 
+    /**
+     * @return the length of time the bell or siren sounds (in minutes)
+     */
+    public int getBellTime() {
+        return bellTime;
+    }
+
+    /**
+     * @return true if panic alarms are silent; false if audible
+     */
+    public boolean isSilentPanic() {
+        return silentPanic;
+    }
+
     /**
      * @return true if bypassing zones is enabled; false if not
      */
@@ -117,6 +133,13 @@ public class PowermaxPanelSettings {
         return zoneSettings.length;
     }
 
+    /**
+     * @return an integer stream for iterating over the range of zone numbers
+     */
+    public IntStream getZoneRange() {
+        return IntStream.rangeClosed(1, getNbZones());
+    }
+
     /**
      * Get the settings relative to a zone
      *
@@ -128,6 +151,36 @@ public class PowermaxPanelSettings {
         return ((zone < 1) || (zone > zoneSettings.length)) ? null : zoneSettings[zone - 1];
     }
 
+    /**
+     * Get a zone's display name
+     *
+     * @param zone the zone index (from 1 to NumberOfZones)
+     *
+     * @return the name of the zone
+     */
+    public String getZoneName(int zone) {
+        PowermaxZoneSettings zoneSettings = getZoneSettings(zone);
+        return (zoneSettings == null) ? null : zoneSettings.getName();
+    }
+
+    /**
+     * Get a friendly display name for a zone, user, or device
+     * (any possible source for an event)
+     *
+     * @param zoneOrUser the zone, user, or device code
+     *
+     * @return the display name
+     */
+    public String getZoneOrUserName(int zoneOrUser) {
+        String zoneName = getZoneName(zoneOrUser);
+
+        if (zoneOrUser >= 1 && zoneOrUser <= zoneSettings.length && zoneName != null) {
+            return String.format("%s[%d]", zoneName, zoneOrUser);
+        } else {
+            return PowermaxMessageConstants.getZoneOrUser(zoneOrUser);
+        }
+    }
+
     /**
      * @return the number of PGM and X10 devices managed by the system
      */
index 5cfbc3bc72b774ef5f0ada6c9e1583cb6defe0a0..b1273073d0a5a786287bb97860c609c471e80795 100644 (file)
@@ -14,9 +14,13 @@ package org.openhab.binding.powermax.internal.state;
 
 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;
@@ -43,14 +47,13 @@ public class PowermaxState extends PowermaxStateContainer {
     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();
@@ -70,24 +73,52 @@ public class PowermaxState extends PowermaxStateContainer {
         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++) {
@@ -96,6 +127,15 @@ public class PowermaxState extends PowermaxStateContainer {
         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);
     }
 
     /**
@@ -214,6 +254,76 @@ public class PowermaxState extends PowermaxStateContainer {
         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
      *
@@ -324,6 +434,10 @@ public class PowermaxState extends PowermaxStateContainer {
                 thisValue.setValue(null);
             }
         }
+
+        if (hasActiveAlertsQueued()) {
+            resolveActiveAlerts(otherState);
+        }
     }
 
     /**
@@ -370,6 +484,10 @@ public class PowermaxState extends PowermaxStateContainer {
                 setEventLog(i, update.getEventLog(i));
             }
         }
+
+        if (update.hasActiveAlertsQueued()) {
+            copyActiveAlertsFrom(update);
+        }
     }
 
     @Override
@@ -379,11 +497,11 @@ public class PowermaxState extends PowermaxStateContainer {
         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 + ")";
                 }
             }
@@ -400,11 +518,11 @@ public class PowermaxState extends PowermaxStateContainer {
             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 + ")";
                     }
                 }
@@ -417,6 +535,9 @@ public class PowermaxState extends PowermaxStateContainer {
             }
         }
 
+        String alarms = getActiveAlerts();
+        str += "\n - active alarms/alerts = " + (alarms == null ? "null" : alarms);
+
         return str;
     }
 }
index 21015ce6c59e1e1678e0b920f33151ec2646e689..f5cb70b918a4543036ec73c3d07c7cb0e2668fbb 100644 (file)
@@ -36,7 +36,7 @@ public abstract class PowermaxStateContainer {
     protected List<Value<?>> values;
 
     public abstract class Value<T> {
-        protected T value;
+        protected @Nullable T value;
         protected final String channel;
 
         public Value(PowermaxStateContainer parent, String channel) {
index 480a601869234d2914382dff1e64f1d143b55489..d5802e5394c61266f92196a03a3bf7baa5ea8187 100644 (file)
@@ -19,6 +19,8 @@ package org.openhab.binding.powermax.internal.state;
  */
 public class PowermaxZoneSettings {
 
+    // Note: PowermaxStatusMessage contains hardcoded references to some of these strings
+
     private static final String[] ZONE_TYPES = { "Non-Alarm", "Emergency", "Flood", "Gas", "Delay 1", "Delay 2",
             "Interior-Follow", "Perimeter", "Perimeter-Follow", "24 Hours Silent", "24 Hours Audible", "Fire",
             "Interior", "Home Delay", "Temperature", "Outdoor" };
index b6a1bebf11b964f20f118aabf0936c0514666067..b49fb0e0c641ec67612863e60b46238394bdaf59 100644 (file)
@@ -28,7 +28,13 @@ public class PowermaxZoneState extends PowermaxStateContainer {
     public DateTimeValue lastTripped = new DateTimeValue(this, LAST_TRIP);
     public BooleanValue lowBattery = new BooleanValue(this, LOW_BATTERY);
     public BooleanValue bypassed = new BooleanValue(this, BYPASSED);
+    public BooleanValue alarmed = new BooleanValue(this, ALARMED);
+    public BooleanValue tamperAlarm = new BooleanValue(this, TAMPER_ALARM);
+    public BooleanValue inactive = new BooleanValue(this, INACTIVE);
+    public BooleanValue tampered = new BooleanValue(this, TAMPERED);
     public BooleanValue armed = new BooleanValue(this, ARMED);
+    public StringValue lastMessage = new StringValue(this, ZONE_LAST_MESSAGE);
+    public DateTimeValue lastMessageTime = new DateTimeValue(this, ZONE_LAST_MESSAGE_TIME);
 
     public DynamicValue<Boolean> locked = new DynamicValue<>(this, LOCKED, () -> {
         return armed.getValue();
index 9c42f44d15940fe72a119278b81d9e6cf6b1879d..4ba68cfb0d4ab175e8d64a64349cf48b21d066e1 100644 (file)
                </state>
        </channel-type>
 
+       <channel-type id="last_message_time" advanced="true">
+               <item-type>DateTime</item-type>
+               <label>Last Message Time</label>
+               <description>Timestamp when the most recent message of any kind was received from the panel</description>
+               <state readOnly="true" pattern="%1$tH:%1$tM"></state>
+       </channel-type>
+
+       <channel-type id="active_alerts">
+               <item-type>String</item-type>
+               <label>Active Alarms and Alerts</label>
+               <description>List of active alarms and alerts</description>
+               <state readOnly="true" pattern="%s"></state>
+       </channel-type>
+
        <channel-type id="trouble">
                <item-type>Switch</item-type>
                <label>Trouble Detected</label>
                <state readOnly="true"></state>
        </channel-type>
 
+       <channel-type id="ringing">
+               <item-type>Switch</item-type>
+               <label>Ringing</label>
+               <description>Whether or not the alarm siren is currently ringing</description>
+               <state readOnly="true"></state>
+       </channel-type>
+
        <channel-type id="system_status">
                <item-type>String</item-type>
                <label>System Status</label>
                <description>Whether or not the zone is bypassed</description>
        </channel-type>
 
+       <channel-type id="alarmed">
+               <item-type>Switch</item-type>
+               <label>Zone Alarmed</label>
+               <description>Whether or not the zone has an active alarm condition, or has had an active alarm since the memory was
+                       last cleared</description>
+               <state readOnly="true"></state>
+       </channel-type>
+
+       <channel-type id="tamper_alarm">
+               <item-type>Switch</item-type>
+               <label>Zone Tamper Alarm</label>
+               <description>Whether or not the zone's sensor has an active tamper condition, or has had an active tamper condition
+                       since the memory was last cleared</description>
+               <state readOnly="true"></state>
+       </channel-type>
+
+       <channel-type id="inactive">
+               <item-type>Switch</item-type>
+               <label>Zone Inactive</label>
+               <description>Whether or not the zone's sensor is inactive (loss of supervision)</description>
+               <state readOnly="true"></state>
+       </channel-type>
+
+       <channel-type id="tampered">
+               <item-type>Switch</item-type>
+               <label>Zone Tampered</label>
+               <description>Whether or not the zone's sensor is reporting a tamper condition</description>
+               <state readOnly="true"></state>
+       </channel-type>
+
        <channel-type id="armed">
                <item-type>Switch</item-type>
                <label>Zone Armed (Switch)</label>
                <state readOnly="true"></state>
        </channel-type>
 
+       <channel-type id="zone_last_message">
+               <item-type>String</item-type>
+               <label>Zone Last Status Message</label>
+               <description>The most recent status message reported by the zone</description>
+               <state readOnly="true" pattern="%s"></state>
+       </channel-type>
+
+       <channel-type id="zone_last_message_time">
+               <item-type>DateTime</item-type>
+               <label>Zone Last Status Time</label>
+               <description>Timestamp when Zone Last Status Message was received</description>
+               <state readOnly="true" pattern="%1$tH:%1$tM"></state>
+       </channel-type>
+
        <channel-type id="pgm_status" advanced="true">
                <item-type>Switch</item-type>
                <label>PGM Status</label>
index 91d2bb0fc00794550932e25dc00050aff55810e4..b9863f246b4c53e646d15eb66794da22f47cd9d6 100644 (file)
                        <channel id="with_zones_bypassed" typeId="with_zones_bypassed"/>
                        <channel id="trouble" typeId="trouble"/>
                        <channel id="alert_in_memory" typeId="alert_in_memory"/>
+                       <channel id="ringing" typeId="ringing"/>
                        <channel id="pgm_status" typeId="pgm_status"/>
                        <channel id="mode" typeId="mode"/>
+                       <channel id="last_message_time" typeId="last_message_time"/>
+                       <channel id="active_alerts" typeId="active_alerts"/>
                        <channel id="event_log_1" typeId="event_log"/>
                        <channel id="event_log_2" typeId="event_log"/>
                        <channel id="event_log_3" typeId="event_log"/>
index db9b4829f45492541ebf3e1cb97aa92597829e60..1d02c972ca81bed3007cd65bab9b2cbe37fa091f 100644 (file)
                        <channel id="with_zones_bypassed" typeId="with_zones_bypassed"/>
                        <channel id="trouble" typeId="trouble"/>
                        <channel id="alert_in_memory" typeId="alert_in_memory"/>
+                       <channel id="ringing" typeId="ringing"/>
                        <channel id="pgm_status" typeId="pgm_status"/>
                        <channel id="mode" typeId="mode"/>
+                       <channel id="last_message_time" typeId="last_message_time"/>
+                       <channel id="active_alerts" typeId="active_alerts"/>
                        <channel id="event_log_1" typeId="event_log"/>
                        <channel id="event_log_2" typeId="event_log"/>
                        <channel id="event_log_3" typeId="event_log"/>
index e85a0c78024679afe765d4f4a2cb3e41dd25ee91..97f72d01ad316a99787e191b449bcc47e006b318 100644 (file)
                        <channel id="last_trip" typeId="last_trip"/>
                        <channel id="low_battery" typeId="system.low-battery"/>
                        <channel id="bypassed" typeId="bypassed"/>
+                       <channel id="alarmed" typeId="alarmed"/>
+                       <channel id="tamper_alarm" typeId="tamper_alarm"/>
+                       <channel id="inactive" typeId="inactive"/>
+                       <channel id="tampered" typeId="tampered"/>
+                       <channel id="last_message" typeId="zone_last_message"/>
+                       <channel id="last_message_time" typeId="zone_last_message_time"/>
                </channels>
 
                <config-description>