[powermax] Introduce some new channels for better status reporting (#10624)

* [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>
This commit is contained in:
Ron Isaacson 2021-09-08 12:58:35 -04:00 committed by GitHub
parent 0a1f23f98d
commit d4983c4991
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 793 additions and 276 deletions

View 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";

View 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);

View 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());
}
}
}

View File

@ -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);
}
}

View 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());

View 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) {

View File

@ -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<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" };
}

View 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());
}
updatedState.troubleType.setValue(troubleStatus);
if (eventType == 0x60) {
// System reset
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();
}
if (sysEvent.isReset()) {
updatedState.clearAllActiveAlerts();
updatedState.downloadSetupRequired.setValue(true);
}
}

View 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;
}
}

View File

@ -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);
}
}

View 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
*/

View 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;
}
}

View 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) {

View 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" };

View 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();

View File

@ -17,6 +17,20 @@
</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>
@ -31,6 +45,13 @@
<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>
@ -107,6 +128,36 @@
<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>
@ -121,6 +172,20 @@
<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>

View File

@ -17,8 +17,11 @@
<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"/>

View File

@ -17,8 +17,11 @@
<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"/>

View File

@ -21,6 +21,12 @@
<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>