Skip to content

Commit ad2865a

Browse files
authored
[paradoxalarm] Add panel system trouble channels (#20679)
* [paradoxalarm] Fix binding not polling data correctly after restart Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
1 parent 9d5dbc4 commit ad2865a

7 files changed

Lines changed: 253 additions & 14 deletions

File tree

bundles/org.openhab.binding.paradoxalarm/README.md

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,17 @@ Currently binding supports the following panels: EVO192, EVO48 (not tested), EVO
6363

6464
### Panel Channels
6565

66-
| Channel | Type | Description |
67-
|--------------------------|----------------------------|-------------------------------------------------------------------------------------------|
68-
| state | String | Overall panel state |
69-
| inputVoltage | Number:ElectricPotential | Supply Voltage |
70-
| boardVoltage | Number:ElectricPotential | Board DC Voltage |
71-
| batteryVoltage | Number:ElectricPotential | Battery Voltage |
72-
| panelTime | DateTime | Panel internal time (Timezone is set to the default zone of the Java virtual machine). |
66+
| Channel | Type | Description |
67+
|------------------------------|----------------------------|-------------------------------------------------------------------------------------------|
68+
| state | String | Overall panel state |
69+
| inputVoltage | Number:ElectricPotential | Supply Voltage |
70+
| boardVoltage | Number:ElectricPotential | Board DC Voltage |
71+
| batteryVoltage | Number:ElectricPotential | Battery Voltage |
72+
| panelTime | DateTime | Panel internal time (Timezone is set to the default zone of the Java virtual machine). |
73+
| acTrouble | Switch | AC power supply failure |
74+
| batteryTrouble | Switch | Backup battery failure |
75+
| moduleSupervisionTrouble | Switch | A bus module has lost supervision |
76+
| communicationTrouble | Switch | PC/reporting communication failure |
7377

7478
### Partition Channels
7579

@@ -155,10 +159,14 @@ Currently binding supports the following panels: EVO192, EVO48 (not tested), EVO
155159
String paradoxSendCommand "Send command to IP150" {channel="paradoxalarm:ip150:ip150:communicationCommand"}
156160
157161
String panelState "Paradox panel state: [%s]"<network> (Paradox) { channel = "paradoxalarm:ip150:ip150:communicationState" }
158-
Number paradoxAcVoltage “Input Voltage: [%.1f V]” (Paradox) { channel = “paradoxalarm:panel:ip150:panel:inputVoltage” }
159-
Number paradoxDcVoltage “Board DC Voltage: [%.1f V]” (Paradox) { channel = “paradoxalarm:panel:ip150:panel:boardVoltage” }
160-
Number paradoxBatteryVoltage “Battery Voltage: [%.1f V]” (Paradox) { channel = “paradoxalarm:panel:ip150:panel:batteryVoltage” }
161-
DateTime paradoxTime "Paradox Time: [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" <lock> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:panelTime" }
162+
Number paradoxAcVoltage "Input Voltage: [%.1f V]" <energy> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:inputVoltage" }
163+
Number paradoxDcVoltage "Board DC Voltage: [%.1f V]" <energy> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:boardVoltage" }
164+
Number paradoxBatteryVoltage "Battery Voltage: [%.1f V]" <energy> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:batteryVoltage" }
165+
DateTime paradoxTime "Paradox Time: [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" <time> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:panelTime" }
166+
Switch paradoxAcTrouble "AC Trouble [%s]" <alarm> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:acTrouble" }
167+
Switch paradoxBatteryTrouble "Battery Trouble [%s]" <alarm> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:batteryTrouble" }
168+
Switch paradoxModuleSupervisionTrouble "Module Supervision Trouble [%s]" <alarm> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:moduleSupervisionTrouble" }
169+
Switch paradoxCommunicationTrouble "Communication Trouble [%s]" <alarm> (Paradox) { channel = "paradoxalarm:panel:ip150:panel:communicationTrouble" }
162170
163171
//PARTITIONS
164172
String partition1State "Magnetic sensors - Floor 1: [%s]" (Partitions) { channel = "paradoxalarm:partition:ip150:partition1:state" }
@@ -182,6 +190,10 @@ Currently binding supports the following panels: EVO192, EVO48 (not tested), EVO
182190
Text item=paradoxAcVoltage
183191
Text item=paradoxDcVoltage
184192
Text item=paradoxBatteryVoltage
193+
Text item=paradoxAcTrouble label="AC Trouble [MAP(yesno.map):%s]" valuecolor=[paradoxAcTrouble=="ON"="red", paradoxAcTrouble=="OFF"="green"]
194+
Text item=paradoxBatteryTrouble label="Battery Trouble [MAP(yesno.map):%s]" valuecolor=[paradoxBatteryTrouble=="ON"="red", paradoxBatteryTrouble=="OFF"="green"]
195+
Text item=paradoxModuleSupervisionTrouble label="Module Supervision Trouble [MAP(yesno.map):%s]" valuecolor=[paradoxModuleSupervisionTrouble=="ON"="red", paradoxModuleSupervisionTrouble=="OFF"="green"]
196+
Text item=paradoxCommunicationTrouble label="Communication Trouble [MAP(yesno.map):%s]" valuecolor=[paradoxCommunicationTrouble=="ON"="red", paradoxCommunicationTrouble=="OFF"="green"]
185197
}
186198
Frame label="Partitions" {
187199
Text item=partition1State valuecolor=[partition1State=="Disarmed"="green", partition1State=="Armed"="red"]

bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxAlarmBindingConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public class ParadoxAlarmBindingConstants {
6666
public static final String PANEL_INPUT_VOLTAGE = "inputVoltage";
6767
public static final String PANEL_BOARD_VOLTAGE = "boardVoltage";
6868
public static final String PANEL_BATTERY_VOLTAGE = "batteryVoltage";
69+
public static final String PANEL_AC_TROUBLE = "acTrouble";
70+
public static final String PANEL_BATTERY_TROUBLE = "batteryTrouble";
71+
public static final String PANEL_MODULE_SUPERVISION_TROUBLE = "moduleSupervisionTrouble";
72+
public static final String PANEL_COMMUNICATION_TROUBLE = "communicationTrouble";
6973

7074
public static final String PARTITION_LABEL_CHANNEL_UID = "label";
7175
public static final String PARTITION_STATE_CHANNEL_UID = "state";

bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPanelHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.openhab.binding.paradoxalarm.internal.model.ParadoxInformation;
1919
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
2020
import org.openhab.core.library.types.DateTimeType;
21+
import org.openhab.core.library.types.OnOffType;
2122
import org.openhab.core.library.types.QuantityType;
2223
import org.openhab.core.library.types.StringType;
2324
import org.openhab.core.library.unit.Units;
@@ -60,6 +61,10 @@ protected void updateEntity() {
6061
updateState(PANEL_INPUT_VOLTAGE, new QuantityType<>(panel.getVdcLevel(), Units.VOLT));
6162
updateState(PANEL_BOARD_VOLTAGE, new QuantityType<>(panel.getDcLevel(), Units.VOLT));
6263
updateState(PANEL_BATTERY_VOLTAGE, new QuantityType<>(panel.getBatteryLevel(), Units.VOLT));
64+
updateState(PANEL_AC_TROUBLE, OnOffType.from(panel.isAcTrouble()));
65+
updateState(PANEL_BATTERY_TROUBLE, OnOffType.from(panel.isBatteryTrouble()));
66+
updateState(PANEL_MODULE_SUPERVISION_TROUBLE, OnOffType.from(panel.isModuleSupervisionTrouble()));
67+
updateState(PANEL_COMMUNICATION_TROUBLE, OnOffType.from(panel.isCommunicationTrouble()));
6368
}
6469
}
6570
}

bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ParadoxPanel.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public class ParadoxPanel implements IDataUpdateListener {
4646
private double batteryLevel;
4747
private double dcLevel;
4848
private ZonedDateTime panelTime;
49+
private boolean acTrouble;
50+
private boolean batteryTrouble;
51+
private boolean moduleSupervisionTrouble;
52+
private boolean communicationTrouble;
4953

5054
public ParadoxPanel() {
5155
this.parser = new EvoParser();
@@ -97,6 +101,15 @@ public void updateEntitiesStates() {
97101
vdcLevel = Math.max(0, (firstRamPage[25] & 0xFF) * (20.3 - 1.4) / 255.0 + 1.4);
98102
dcLevel = Math.max(0, (firstRamPage[27] & 0xFF) * 22.8 / 255);
99103
batteryLevel = Math.max(0, (firstRamPage[26] & 0xFF) * 22.8 / 255);
104+
105+
parseTroubleFlags(communicator.getMemoryMap().getElement(0));
106+
}
107+
108+
public void parseTroubleFlags(byte[] ramBlock1) {
109+
moduleSupervisionTrouble = (ramBlock1[13] & 0x08) != 0;
110+
batteryTrouble = (ramBlock1[14] & 0x02) != 0;
111+
acTrouble = (ramBlock1[14] & 0x01) != 0;
112+
communicationTrouble = (ramBlock1[15] & 0x20) != 0;
100113
}
101114

102115
protected ZonedDateTime constructPanelTime(byte[] firstPage) {
@@ -115,8 +128,11 @@ private List<Zone> createZones() {
115128
zones = new ArrayList<>();
116129
Map<Integer, String> zoneLabels = communicator.getZoneLabels();
117130
for (int i = 0; i < zoneLabels.size(); i++) {
118-
Zone zone = new Zone(this, i + 1, zoneLabels.get(i));
119-
zones.add(zone);
131+
String label = zoneLabels.get(i);
132+
if (label == null || label.isBlank()) {
133+
label = "Zone " + (i + 1);
134+
}
135+
zones.add(new Zone(this, i + 1, label));
120136
}
121137
return zones;
122138
}
@@ -125,7 +141,11 @@ private List<Partition> createPartitions() {
125141
partitions = new ArrayList<>();
126142
Map<Integer, String> partitionLabels = communicator.getPartitionLabels();
127143
for (int i = 0; i < partitionLabels.size(); i++) {
128-
Partition partition = new Partition(this, i + 1, partitionLabels.get(i));
144+
String label = partitionLabels.get(i);
145+
if (label == null || label.isBlank()) {
146+
label = "Partition " + (i + 1);
147+
}
148+
Partition partition = new Partition(this, i + 1, label);
129149
partitions.add(partition);
130150
logger.debug("Partition {}:\t{}", i + 1, partition.getState().getMainState());
131151
}
@@ -186,6 +206,22 @@ public ZonedDateTime getPanelTime() {
186206
return panelTime;
187207
}
188208

209+
public boolean isAcTrouble() {
210+
return acTrouble;
211+
}
212+
213+
public boolean isBatteryTrouble() {
214+
return batteryTrouble;
215+
}
216+
217+
public boolean isModuleSupervisionTrouble() {
218+
return moduleSupervisionTrouble;
219+
}
220+
221+
public boolean isCommunicationTrouble() {
222+
return communicationTrouble;
223+
}
224+
189225
public void setCommunicator(IParadoxCommunicator communicator) {
190226
this.communicator = communicator;
191227
}

bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/thing/panel.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,30 @@
1919
<channel id="boardVoltage" typeId="voltage"/>
2020
<channel id="batteryVoltage" typeId="voltage"/>
2121
<channel id="panelTime" typeId="panelTime"/>
22+
<channel id="acTrouble" typeId="troubleFlag">
23+
<label>AC Trouble</label>
24+
<description>AC power supply failure</description>
25+
</channel>
26+
<channel id="batteryTrouble" typeId="troubleFlag">
27+
<label>Battery Trouble</label>
28+
<description>Backup battery failure</description>
29+
</channel>
30+
<channel id="moduleSupervisionTrouble" typeId="troubleFlag">
31+
<label>Module Supervision Trouble</label>
32+
<description>A bus module has lost supervision</description>
33+
</channel>
34+
<channel id="communicationTrouble" typeId="troubleFlag">
35+
<label>Communication Trouble</label>
36+
<description>PC/reporting communication failure</description>
37+
</channel>
2238
</channels>
2339
<properties>
2440
<property name="serialNumber"/>
2541
<property name="panelType"/>
2642
<property name="hardwareVersion"/>
2743
<property name="applicationVersion"/>
2844
<property name="bootloaderVersion"/>
45+
<property name="thingTypeVersion">1</property>
2946
</properties>
3047
</thing-type>
3148

@@ -85,5 +102,14 @@
85102
</tags>
86103
<state readOnly="true"/>
87104
</channel-type>
105+
<channel-type id="troubleFlag">
106+
<item-type>Switch</item-type>
107+
<label>Trouble Flag</label>
108+
<description>Panel system trouble indicator</description>
109+
<tags>
110+
<tag>Alarm</tag>
111+
</tags>
112+
<state readOnly="true" pattern="%s"/>
113+
</channel-type>
88114

89115
</thing:thing-descriptions>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
2+
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
4+
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
5+
6+
<thing-type uid="paradoxalarm:panel">
7+
<instruction-set targetVersion="1">
8+
<add-channel id="acTrouble">
9+
<type>paradoxalarm:troubleFlag</type>
10+
<label>AC Trouble</label>
11+
<description>AC power supply failure</description>
12+
</add-channel>
13+
<add-channel id="batteryTrouble">
14+
<type>paradoxalarm:troubleFlag</type>
15+
<label>Battery Trouble</label>
16+
<description>Backup battery failure</description>
17+
</add-channel>
18+
<add-channel id="moduleSupervisionTrouble">
19+
<type>paradoxalarm:troubleFlag</type>
20+
<label>Module Supervision Trouble</label>
21+
<description>A bus module has lost supervision</description>
22+
</add-channel>
23+
<add-channel id="communicationTrouble">
24+
<type>paradoxalarm:troubleFlag</type>
25+
<label>Communication Trouble</label>
26+
<description>PC/reporting communication failure</description>
27+
</add-channel>
28+
</instruction-set>
29+
</thing-type>
30+
31+
</update:update-descriptions>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.paradoxalarm.internal;
14+
15+
import static org.junit.jupiter.api.Assertions.*;
16+
17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.junit.jupiter.api.Test;
19+
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
20+
21+
/**
22+
* Verifies that {@link ParadoxPanel#parseTroubleFlags} extracts the correct bits from RAM block 1.
23+
*
24+
* Byte/bit offsets are derived from the PAI EVO RAMDataParserMap[1] BitStruct
25+
* (paradox/hardware/evo/parsers.py) and validated against live EVO192 RAM captures.
26+
*
27+
* Trouble flags are in RAMDataParserMap[1] (RAM block number 2 in PAI), but stored at
28+
* memoryMap.getElement(0) (RAM block number 1 = first 64-byte page, 0-indexed).
29+
* The troubles BitStruct starts at byte offset 13 within that page.
30+
*
31+
* RAM block 1 layout (relevant bytes, construct BitStruct MSB-first ordering):
32+
* byte 13: module_supervision_trouble = bit 3 from LSB (mask 0x08)
33+
* byte 14: battery_failure_trouble = bit 1 from LSB (mask 0x02)
34+
* ac_trouble = bit 0 from LSB (mask 0x01)
35+
* byte 15: com_pc_trouble = bit 5 from LSB (mask 0x20)
36+
*/
37+
@NonNullByDefault
38+
public class TestParadoxPanelTroubleFlags {
39+
40+
private ParadoxPanel panel = new ParadoxPanel();
41+
42+
private byte[] emptyRamBlock() {
43+
return new byte[64];
44+
}
45+
46+
@Test
47+
public void testNoTroublesWhenAllBytesZero() {
48+
panel.parseTroubleFlags(emptyRamBlock());
49+
assertFalse(panel.isAcTrouble());
50+
assertFalse(panel.isBatteryTrouble());
51+
assertFalse(panel.isModuleSupervisionTrouble());
52+
assertFalse(panel.isCommunicationTrouble());
53+
}
54+
55+
@Test
56+
public void testAcTrouble() {
57+
byte[] ram = emptyRamBlock();
58+
ram[14] |= 0x01; // ac_trouble = bit 0 of byte 14
59+
panel.parseTroubleFlags(ram);
60+
assertTrue(panel.isAcTrouble());
61+
assertFalse(panel.isBatteryTrouble());
62+
assertFalse(panel.isModuleSupervisionTrouble());
63+
assertFalse(panel.isCommunicationTrouble());
64+
}
65+
66+
@Test
67+
public void testBatteryTrouble() {
68+
byte[] ram = emptyRamBlock();
69+
ram[14] |= 0x02; // battery_failure_trouble = bit 1 of byte 14
70+
panel.parseTroubleFlags(ram);
71+
assertFalse(panel.isAcTrouble());
72+
assertTrue(panel.isBatteryTrouble());
73+
assertFalse(panel.isModuleSupervisionTrouble());
74+
assertFalse(panel.isCommunicationTrouble());
75+
}
76+
77+
@Test
78+
public void testModuleSupervisionTrouble() {
79+
byte[] ram = emptyRamBlock();
80+
ram[13] |= 0x08; // module_supervision_trouble = bit 3 of byte 13
81+
panel.parseTroubleFlags(ram);
82+
assertFalse(panel.isAcTrouble());
83+
assertFalse(panel.isBatteryTrouble());
84+
assertTrue(panel.isModuleSupervisionTrouble());
85+
assertFalse(panel.isCommunicationTrouble());
86+
}
87+
88+
@Test
89+
public void testCommunicationTrouble() {
90+
byte[] ram = emptyRamBlock();
91+
ram[15] |= 0x20; // com_pc_trouble = bit 5 of byte 15
92+
panel.parseTroubleFlags(ram);
93+
assertFalse(panel.isAcTrouble());
94+
assertFalse(panel.isBatteryTrouble());
95+
assertFalse(panel.isModuleSupervisionTrouble());
96+
assertTrue(panel.isCommunicationTrouble());
97+
}
98+
99+
@Test
100+
public void testAdjacentBitsDoNotTriggerFalsePositives() {
101+
byte[] ram = emptyRamBlock();
102+
// Set every bit around the target bits to ensure no mask overlap
103+
ram[13] = (byte) 0xF7; // all bits set except bit 3 (module_supervision)
104+
ram[14] = (byte) 0xFC; // all bits set except bits 0 and 1 (ac + battery)
105+
ram[15] = (byte) 0xDF; // all bits set except bit 5 (com_pc)
106+
panel.parseTroubleFlags(ram);
107+
assertFalse(panel.isAcTrouble());
108+
assertFalse(panel.isBatteryTrouble());
109+
assertFalse(panel.isModuleSupervisionTrouble());
110+
assertFalse(panel.isCommunicationTrouble());
111+
}
112+
113+
@Test
114+
public void testAllTroublesSimultaneously() {
115+
byte[] ram = emptyRamBlock();
116+
ram[13] |= 0x08;
117+
ram[14] |= 0x03; // both ac (0x01) and battery (0x02)
118+
ram[15] |= 0x20;
119+
panel.parseTroubleFlags(ram);
120+
assertTrue(panel.isAcTrouble());
121+
assertTrue(panel.isBatteryTrouble());
122+
assertTrue(panel.isModuleSupervisionTrouble());
123+
assertTrue(panel.isCommunicationTrouble());
124+
}
125+
}

0 commit comments

Comments
 (0)