From 59a41d1251e3568b9f2ad992c09719412fb52776 Mon Sep 17 00:00:00 2001 From: Ron Isaacson Date: Fri, 30 Apr 2021 23:36:24 -0400 Subject: [PATCH 1/2] [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 --- .../internal/PowermaxBindingConstants.java | 9 + .../handler/PowermaxBridgeHandler.java | 24 +- .../handler/PowermaxThingHandler.java | 9 +- .../internal/message/PowermaxAlarmType.java | 78 ---- .../message/PowermaxEventLogMessage.java | 4 +- .../message/PowermaxMessageConstants.java | 408 +++++++++++++++--- .../message/PowermaxPanelMessage.java | 45 +- .../message/PowermaxStatusMessage.java | 131 ++++-- .../internal/message/PowermaxTroubleType.java | 77 ---- .../internal/state/PowermaxPanelSettings.java | 53 +++ .../internal/state/PowermaxState.java | 167 ++++++- .../state/PowermaxStateContainer.java | 2 +- .../internal/state/PowermaxZoneSettings.java | 2 + .../internal/state/PowermaxZoneState.java | 6 + .../main/resources/OH-INF/thing/channels.xml | 65 +++ .../src/main/resources/OH-INF/thing/ip.xml | 3 + .../main/resources/OH-INF/thing/serial.xml | 3 + .../src/main/resources/OH-INF/thing/zone.xml | 6 + 18 files changed, 818 insertions(+), 274 deletions(-) delete mode 100644 bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxAlarmType.java delete mode 100644 bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxTroubleType.java diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java index a0a2f1d25958..75d22d30d1f7 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java @@ -47,8 +47,11 @@ public class PowermaxBindingConstants { // List of all Channel ids public static final String MODE = "mode"; + public static final String LAST_MESSAGE = "last_message"; + 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 = "zone_last_message"; + public static final String ZONE_LAST_MESSAGE_TIME = "zone_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"; diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java index 6ff9b22c9c65..3497ce32e226 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java @@ -143,6 +143,7 @@ public void initialize() { try { logger.trace("Powermax job..."); updateMotionSensorState(); + updateRingingState(); if (isConnected()) { checkKeepAlive(); commManager.retryDownloadSetup(remainingDownloadAttempts); @@ -254,6 +255,23 @@ private void updateMotionSensorState() { } } + /** + * 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 */ @@ -277,10 +295,12 @@ private void tryReconnect() { logger.info("Trying to connect or reconnect..."); closeConnection(); currentState = commManager.createNewState(); + currentState.setInitialState(); try { openConnection(); logger.debug("openConnection(): connected"); updateStatus(ThingStatus.ONLINE); + updateChannelsFromAlarmState(currentState); if (forceStandardMode) { currentState.powerlinkMode.setValue(false); updateChannelsFromAlarmState(MODE, currentState); @@ -455,12 +475,12 @@ public void onNewStateEvent(EventObject event) { 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); diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxThingHandler.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxThingHandler.java index 4e20a1418432..1b2f7f179ad8 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxThingHandler.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxThingHandler.java @@ -184,10 +184,10 @@ public void updateChannelFromAlarmState(String channel, PowermaxState state) { 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 void onZoneSettingsUpdated(int zoneNumber, @Nullable PowermaxPanelSetting 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 index ea43b207435b..000000000000 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxAlarmType.java +++ /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); - } -} diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxEventLogMessage.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxEventLogMessage.java index 1b24d77d5794..05180eeacb62 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxEventLogMessage.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxEventLogMessage.java @@ -61,8 +61,8 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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) { diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxMessageConstants.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxMessageConstants.java index f4209c9faab2..25592e57ff02 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxMessageConstants.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxMessageConstants.java @@ -12,6 +12,10 @@ */ 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 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 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 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 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" }; } diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxPanelMessage.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxPanelMessage.java index a08313472e23..711570374c05 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxPanelMessage.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxPanelMessage.java @@ -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 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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); } } diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxStatusMessage.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxStatusMessage.java index d30bf0951e30..24c5e65eabe8 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxStatusMessage.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxStatusMessage.java @@ -18,6 +18,7 @@ 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 @@ private static byte[] zoneBytes(byte zones1, byte zones9, byte zones17, byte zon } 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 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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 @@ protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) { 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 index a9cadab83429..000000000000 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxTroubleType.java +++ /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); - } -} diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxPanelSettings.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxPanelSettings.java index 14785583510e..8fed886270c8 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxPanelSettings.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxPanelSettings.java @@ -16,7 +16,9 @@ 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 PowermaxPanelType getPanelType() { 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 int getNbZones() { 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 PowermaxZoneSettings getZoneSettings(int zone) { 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 */ diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java index 5cfbc3bc72b7..7771e3855028 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java @@ -14,9 +14,14 @@ 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 java.util.function.Consumer; +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 +48,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 lastMessageReceived = new DateTimeValue(this, LAST_MESSAGE); public DynamicValue isArmed = new DynamicValue<>(this, SYSTEM_ARMED, () -> { return isArmed(); @@ -70,24 +74,52 @@ public class PowermaxState extends PowermaxStateContainer { return new StringType(getShortArmMode()); }); + public DynamicValue activeAlerts = new DynamicValue<>(this, ACTIVE_ALERTS, () -> { + return getActiveAlerts(); + }, () -> { + return new StringType(getActiveAlerts()); + }); + public DynamicValue 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 updatedZoneNames; private Map updatedZoneInfos; + private List activeAlertList; + private List 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 +128,36 @@ public PowermaxState(PowermaxPanelSettings panelSettings, TimeZoneProvider timeZ pgmX10DevicesStatus = new Boolean[panelSettings.getNbPGMX10Devices()]; updatedZoneNames = new HashMap<>(); updatedZoneInfos = new HashMap<>(); + activeAlertQueue = new ArrayList<>(); + } + + /** + * New PowermaxState objects are created with null values for most fields. + * When the binding starts up, to prevent fields from being shown as null + * or using persisted values which may be stale, we'll initialize a known + * empty state for many fields. + */ + public void setInitialState() { + Consumer> initialize = value -> { + if (value.getChannel().startsWith("_")) { + return; + } + + if (value instanceof BooleanValue) { + ((BooleanValue) value).setValue(false); + } else if (value instanceof StringValue) { + ((StringValue) value).setValue("Unknown"); + } + }; + + values.forEach(initialize); + + for (int zone = 1; zone <= zones.length; zone++) { + PowermaxZoneState thisZone = getZone(zone); + thisZone.values.forEach(initialize); + } + + resolveActiveAlerts(null); } /** @@ -214,6 +276,80 @@ public void updateZoneInfo(int zoneIdx, int zoneInfo) { 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 == null) { + return null; + } + + if (activeAlertList.isEmpty()) { + return "None"; + } + + List 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 +460,10 @@ && getPGMX10DeviceStatus(i).equals(otherState.getPGMX10DeviceStatus(i))) { thisValue.setValue(null); } } + + if (hasActiveAlertsQueued()) { + resolveActiveAlerts(otherState); + } } /** @@ -370,6 +510,10 @@ public void merge(PowermaxState update) { setEventLog(i, update.getEventLog(i)); } } + + if (update.hasActiveAlertsQueued()) { + copyActiveAlertsFrom(update); + } } @Override @@ -379,11 +523,11 @@ public String toString() { 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 +544,11 @@ public String toString() { 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 +561,9 @@ public String toString() { } } + String alarms = getActiveAlerts(); + str += "\n - active alarms/alerts = " + (alarms == null ? "null" : alarms); + return str; } } diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxStateContainer.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxStateContainer.java index 21015ce6c59e..f5cb70b918a4 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxStateContainer.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxStateContainer.java @@ -36,7 +36,7 @@ public abstract class PowermaxStateContainer { protected List> values; public abstract class Value { - protected T value; + protected @Nullable T value; protected final String channel; public Value(PowermaxStateContainer parent, String channel) { diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneSettings.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneSettings.java index 480a60186923..d5802e5394c6 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneSettings.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneSettings.java @@ -19,6 +19,8 @@ */ 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" }; diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneState.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneState.java index b6a1bebf11b9..b49fb0e0c641 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneState.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxZoneState.java @@ -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 locked = new DynamicValue<>(this, LOCKED, () -> { return armed.getValue(); diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml index 9c42f44d1594..18469da9ce66 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml @@ -17,6 +17,20 @@ + + DateTime + + Timestamp when the most recent message of any kind was received from the panel + + + + + String + + List of active alarms and alerts + + + Switch @@ -31,6 +45,13 @@ + + Switch + + Whether or not the alarm siren is currently ringing + + + String @@ -107,6 +128,36 @@ Whether or not the zone is bypassed + + Switch + + Whether or not the zone has an active alarm condition, or has had an active alarm since the memory was + last cleared + + + + + Switch + + 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 + + + + + Switch + + Whether or not the zone's sensor is inactive (loss of supervision) + + + + + Switch + + Whether or not the zone's sensor is reporting a tamper condition + + + Switch @@ -121,6 +172,20 @@ + + String + + The most recent status message reported by the zone + + + + + DateTime + + Timestamp when Zone Last Status Message was received + + + Switch diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml index 91d2bb0fc007..9b5de6696e31 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml @@ -17,8 +17,11 @@ + + + diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml index db9b4829f454..911f2be62960 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml @@ -17,8 +17,11 @@ + + + diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml index e85a0c780246..b5f5b1fb6936 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml @@ -21,6 +21,12 @@ + + + + + + From 69ce3d25acb2c8e66635604fbd83b60add2ed337 Mon Sep 17 00:00:00 2001 From: Ron Isaacson Date: Wed, 8 Sep 2021 01:51:46 -0400 Subject: [PATCH 2/2] Incorporate review feedback from lolodomo Signed-off-by: Ron Isaacson --- .../internal/PowermaxBindingConstants.java | 6 +-- .../handler/PowermaxBridgeHandler.java | 5 +-- .../internal/message/PowermaxCommManager.java | 2 +- .../internal/state/PowermaxState.java | 40 ++++--------------- .../main/resources/OH-INF/thing/channels.xml | 6 +-- .../src/main/resources/OH-INF/thing/ip.xml | 2 +- .../main/resources/OH-INF/thing/serial.xml | 2 +- .../src/main/resources/OH-INF/thing/zone.xml | 4 +- 8 files changed, 20 insertions(+), 47 deletions(-) diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java index 75d22d30d1f7..94a462406ec2 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/PowermaxBindingConstants.java @@ -47,7 +47,7 @@ public class PowermaxBindingConstants { // List of all Channel ids public static final String MODE = "mode"; - public static final String LAST_MESSAGE = "last_message"; + 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"; @@ -67,8 +67,8 @@ public class PowermaxBindingConstants { 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 = "zone_last_message"; - public static final String ZONE_LAST_MESSAGE_TIME = "zone_last_message_time"; + 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"; diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java index 3497ce32e226..405c2b97d705 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/handler/PowermaxBridgeHandler.java @@ -284,8 +284,8 @@ private void checkKeepAlive() { 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); } @@ -295,7 +295,6 @@ private void tryReconnect() { logger.info("Trying to connect or reconnect..."); closeConnection(); currentState = commManager.createNewState(); - currentState.setInitialState(); try { openConnection(); logger.debug("openConnection(): connected"); diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxCommManager.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxCommManager.java index 64c6aeb6adfa..175f6879d1c5 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxCommManager.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/message/PowermaxCommManager.java @@ -259,7 +259,7 @@ public void onNewMessageEvent(EventObject event) { updateState = createNewState(); } - updateState.lastMessageReceived.setValue(System.currentTimeMillis()); + updateState.lastMessageTime.setValue(System.currentTimeMillis()); if (updateState.getUpdateSettings() != null) { panelSettings.updateRawSettings(updateState.getUpdateSettings()); diff --git a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java index 7771e3855028..b1273073d0a5 100644 --- a/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java +++ b/bundles/org.openhab.binding.powermax/src/main/java/org/openhab/binding/powermax/internal/state/PowermaxState.java @@ -18,7 +18,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.powermax.internal.message.PowermaxMessageConstants; @@ -54,7 +53,7 @@ public class PowermaxState extends PowermaxStateContainer { 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); + public DateTimeValue lastMessageTime = new DateTimeValue(this, LAST_MESSAGE_TIME); public DynamicValue isArmed = new DynamicValue<>(this, SYSTEM_ARMED, () -> { return isArmed(); @@ -128,36 +127,15 @@ public PowermaxState(PowermaxPanelSettings panelSettings, TimeZoneProvider timeZ pgmX10DevicesStatus = new Boolean[panelSettings.getNbPGMX10Devices()]; updatedZoneNames = new HashMap<>(); updatedZoneInfos = new HashMap<>(); + activeAlertList = new ArrayList<>(); activeAlertQueue = new ArrayList<>(); - } - - /** - * New PowermaxState objects are created with null values for most fields. - * When the binding starts up, to prevent fields from being shown as null - * or using persisted values which may be stale, we'll initialize a known - * empty state for many fields. - */ - public void setInitialState() { - Consumer> initialize = value -> { - if (value.getChannel().startsWith("_")) { - return; - } - - if (value instanceof BooleanValue) { - ((BooleanValue) value).setValue(false); - } else if (value instanceof StringValue) { - ((StringValue) value).setValue("Unknown"); - } - }; - values.forEach(initialize); + // 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. - for (int zone = 1; zone <= zones.length; zone++) { - PowermaxZoneState thisZone = getZone(zone); - thisZone.values.forEach(initialize); - } - - resolveActiveAlerts(null); + this.ringing.setValue(false); } /** @@ -288,10 +266,6 @@ public boolean hasActiveAlertsQueued() { } public String getActiveAlerts() { - if (activeAlertList == null) { - return null; - } - if (activeAlertList.isEmpty()) { return "None"; } diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml index 18469da9ce66..4ba68cfb0d4a 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/channels.xml @@ -17,9 +17,9 @@ - + DateTime - + Timestamp when the most recent message of any kind was received from the panel @@ -45,7 +45,7 @@ - + Switch Whether or not the alarm siren is currently ringing diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml index 9b5de6696e31..b9863f246b4c 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/ip.xml @@ -20,7 +20,7 @@ - + diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml index 911f2be62960..1d02c972ca81 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/serial.xml @@ -20,7 +20,7 @@ - + diff --git a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml index b5f5b1fb6936..97f72d01ad31 100644 --- a/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml +++ b/bundles/org.openhab.binding.powermax/src/main/resources/OH-INF/thing/zone.xml @@ -25,8 +25,8 @@ - - + +