### Zone channels:
-| Channel | Type | Description |
-|-----------------|---------|--------------------------------------------------------------------------------|
-| zoneLabel | String | Label of zone inside Paradox configuration |
-| openedState | Contact | Zone opened / closed |
-| tamperedState | Switch | Zone is tampered / not tampered |
+| Channel | Type | Description |
+|------------------------------------|---------|--------------------------------------------------------------------------------|
+| zoneLabel | String | Label of zone inside Paradox configuration |
+| openedState | Contact | Zone opened / closed |
+| tamperedState | Switch | Zone is tampered |
+| supervisionTrouble | Switch | Zone is in supervision trouble |
+| inTxDelay | Switch | Zone is in txDelay |
+| shutdown | Switch | Zone is shutdown |
+| bypassed | Switch | Zone is bypassed |
+| hasActivatedIntellizoneDelay | Switch | Zone is has an activated Intellizone delay |
+| hasActivatedEntryDelay | Switch | Zone is has an activated entry delay |
+| presentlyInAlarm | Switch | Zone is currently in alarm |
+| generatedAlarm | Switch | Zone has generated an alarm |
+| command | String | Command for zone. Can be (BYPASS, CLEAR_BYPASS) |
+
## Example things configuration
if (payload != null && payload.length >= RAM_BLOCK_SIZE) {
RamRequest request = (RamRequest) response.getRequest();
int ramBlockNumber = request.getRamBlockNumber();
- memoryMap.updateElement(ramBlockNumber, payload);
+ memoryMap.updateElement(ramBlockNumber - 1, payload);
if (logger.isTraceEnabled()) {
logger.trace("Result for ramBlock={} is [{}]", ramBlockNumber, ParadoxUtil.byteArrayToString(payload));
}
byte[] firstPage = memoryMap.getElement(0);
byte[] secondPage = memoryMap.getElement(8);
+ createZoneOpenedFlags(result, firstPage, secondPage);
+ createZoneTamperedFlags(result, firstPage, secondPage);
+ createZoneLowbatteryFlags(result, firstPage, secondPage);
+ createSpecialZoneFlags(result, memoryMap);
+
+ ParadoxUtil.printByteArray("Zone opened flags", result.getZonesOpened());
+ ParadoxUtil.printByteArray("Zone tampered flags", result.getZonesTampered());
+ ParadoxUtil.printByteArray("Zone low battery flags", result.getZonesLowBattery());
+ ParadoxUtil.printByteArray("Zone special flags", result.getZoneSpecialFlags());
+
+ return result;
+ }
+
+ private void createZoneOpenedFlags(ZoneStateFlags result, byte[] firstPage, byte[] secondPage) {
int pageOffset = panelType == PanelType.EVO48 ? 34 : 40;
byte[] firstBlock = Arrays.copyOfRange(firstPage, 28, pageOffset);
if (panelType != PanelType.EVO192) {
byte[] zonesOpened = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
result.setZonesOpened(zonesOpened);
}
+ }
- pageOffset = panelType == PanelType.EVO48 ? 46 : 52;
- firstBlock = Arrays.copyOfRange(firstPage, 40, pageOffset);
+ private void createZoneTamperedFlags(ZoneStateFlags result, byte[] firstPage, byte[] secondPage) {
+ int pageOffset = panelType == PanelType.EVO48 ? 46 : 52;
+ byte[] firstBlock = Arrays.copyOfRange(firstPage, 40, pageOffset);
if (panelType != PanelType.EVO192) {
result.setZonesTampered(firstBlock);
} else {
byte[] zonesTampered = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
result.setZonesTampered(zonesTampered);
}
+ }
- pageOffset = panelType == PanelType.EVO48 ? 58 : 64;
- firstBlock = Arrays.copyOfRange(firstPage, 52, pageOffset);
+ private void createZoneLowbatteryFlags(ZoneStateFlags result, byte[] firstPage, byte[] secondPage) {
+ int pageOffset = panelType == PanelType.EVO48 ? 58 : 64;
+ byte[] firstBlock = Arrays.copyOfRange(firstPage, 52, pageOffset);
if (panelType != PanelType.EVO192) {
- result.setZonesTampered(firstBlock);
+ result.setZonesLowBattery(firstBlock);
} else {
byte[] secondBlock = Arrays.copyOfRange(secondPage, 24, 36);
byte[] zonesLowBattery = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
result.setZonesLowBattery(zonesLowBattery);
}
+ }
- return result;
+ @SuppressWarnings("incomplete-switch")
+ private void createSpecialZoneFlags(ZoneStateFlags result, MemoryMap memoryMap) {
+ byte[] page2 = memoryMap.getElement(1);
+ byte[] page3 = memoryMap.getElement(2);
+ byte[] page7 = memoryMap.getElement(8);
+ byte[] page8 = memoryMap.getElement(9);
+ byte[] page9 = memoryMap.getElement(10);
+
+ switch (panelType) {
+ case EVO48:
+ byte[] firstBlock = Arrays.copyOfRange(page2, 0, 48);
+ result.setZoneSpecialFlags(firstBlock);
+ break;
+ case EVO96:
+ firstBlock = Arrays.copyOf(page2, 64);
+ byte[] secondBlock = Arrays.copyOfRange(page3, 0, 32);
+ byte[] specialZoneFlags = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
+ result.setZoneSpecialFlags(specialZoneFlags);
+ break;
+ case EVO192:
+ case EVOHD:
+ firstBlock = Arrays.copyOf(page2, 64);
+ secondBlock = Arrays.copyOfRange(page3, 0, 32);
+ byte[] thirdBlock = Arrays.copyOfRange(page7, 36, 64);
+ byte[] fourthBlock = Arrays.copyOf(page8, 64);
+ byte[] fifthBlock = Arrays.copyOfRange(page9, 0, 4);
+ specialZoneFlags = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock, thirdBlock, fourthBlock,
+ fifthBlock);
+ result.setZoneSpecialFlags(specialZoneFlags);
+ break;
+ }
}
public void initializeMemoryMap() {
}
public synchronized void updateElement(int index, byte[] elementValue) {
- ramCache.set(index - 1, elementValue);
+ ramCache.set(index, elementValue);
}
}
LOGON_SEQUENCE,
RAM,
EPROM,
- PARTITION_COMMAND
+ PARTITION_COMMAND,
+ ZONE_COMMAND
}
byte highNibble = ParadoxUtil.getHighNibble(receivedCommand);
RequestType requestType = request.getType();
- // For EPROM and RAM messages received command must be 0x5x
- if (requestType == RequestType.EPROM || requestType == RequestType.RAM) {
- if (highNibble == 0x5) {
- header = Arrays.copyOfRange(packetBytes, 0, 22);
- payload = Arrays.copyOfRange(packetBytes, 22, packetBytes.length - 1);
- return;
- }
+ switch (requestType) {
+ // For EPROM and RAM messages received command must be 0x5x
+ case EPROM:
+ case RAM:
+ if (highNibble == 0x5) {
+ header = Arrays.copyOfRange(packetBytes, 0, 22);
+ payload = Arrays.copyOfRange(packetBytes, 22, packetBytes.length - 1);
+ return;
+ }
+ break;
+
// For logon sequence packets there are various commands but their high nibbles should be either 0x0, 0x1 or
// 0x7
- } else if (requestType == RequestType.LOGON_SEQUENCE) {
- switch (highNibble) {
- case 0x0:
- case 0x1:
- case 0x7:
+ case LOGON_SEQUENCE:
+ switch (highNibble) {
+ case 0x0:
+ case 0x1:
+ case 0x7:
+ header = Arrays.copyOfRange(packetBytes, 0, 16);
+ payload = Arrays.copyOfRange(packetBytes, 16, packetBytes.length);
+ return;
+ }
+ break;
+
+ case PARTITION_COMMAND:
+ if (highNibble == 0x4) {
+ header = Arrays.copyOfRange(packetBytes, 0, 16);
+ payload = Arrays.copyOfRange(packetBytes, 16, 16 + packetBytes[1]);
+ logger.debug("Received a valid response for partition command");
+ return;
+ }
+ break;
+
+ case ZONE_COMMAND:
+ if (highNibble == 0xD) {
header = Arrays.copyOfRange(packetBytes, 0, 16);
- payload = Arrays.copyOfRange(packetBytes, 16, packetBytes.length);
+ payload = Arrays.copyOfRange(packetBytes, 16, 16 + packetBytes[1]);
+ logger.debug("Received a valid response for zone command");
return;
- }
- } else if (requestType == RequestType.PARTITION_COMMAND) {
- if (highNibble == 0x4) {
- header = Arrays.copyOfRange(packetBytes, 0, 16);
- payload = Arrays.copyOfRange(packetBytes, 16, 16 + packetBytes[1]);
- logger.debug("Received valid response for partition command");
- return;
- }
+ }
+ break;
}
// All other cases are considered wrong results for the parser and are probably live events which cannot be
// parsed currently
- logger.debug("Message command not expected. Received command={}", receivedCommand);
+ logger.debug("Message command not expected. Received command={}", String.format("0x%08X", receivedCommand));
header = null;
payload = null;
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication;
+
+import org.openhab.binding.paradoxalarm.internal.communication.messages.IPPacket;
+
+/**
+ * @author Konstantin Polihronov - Initial contribution
+ */
+public class ZoneCommandRequest extends Request {
+
+ public ZoneCommandRequest(RequestType type, IPPacket packet, IResponseReceiver receiver) {
+ super(type, packet, receiver);
+ }
+}
private static final int PAYLOAD_RATE_LENGTH = 16;
private static final int ROUNDS = 14;
- private static final int[] lTable = new int[TABLE_SIZE];
- private static final int[] aTable = new int[TABLE_SIZE];
+ private static final int[] L_TABLE = new int[TABLE_SIZE];
+ private static final int[] A_TABLE = new int[TABLE_SIZE];
private static EncryptionHandler instance = new EncryptionHandler(new byte[] {});
static {
int a = 1;
int d;
for (int index = 0; index < 255; index++) {
- aTable[index] = a & 0xFF;
+ A_TABLE[index] = a & 0xFF;
/* Multiply by three */
d = (a & 0x80) & 0xFF;
a <<= 1;
a ^= 0x1b;
a &= 0xFF;
}
- a ^= aTable[index];
+ a ^= A_TABLE[index];
a &= 0xFF;
/* Set the log table value */
- lTable[aTable[index]] = index & 0xFF;
+ L_TABLE[A_TABLE[index]] = index & 0xFF;
}
- aTable[255] = aTable[0];
- lTable[0] = 0;
+ A_TABLE[255] = A_TABLE[0];
+ L_TABLE[0] = 0;
}
private final int[] expandedKey = new int[KEY_LENGTH];
if (i % 8 == 0) {
int tmp = temp[0];
- for (int j = 1; j < 4; j++) {
- temp[j - 1] = temp[j];
- }
+ System.arraycopy(temp, 1, temp, 0, temp.length - 1);
temp[3] = tmp;
temp[0] ^= EncryptionHandlerConstants.RCON[(i / 8 - 1)];
}
private int gmul(int c, int b) {
- int s = lTable[c] + lTable[b];
+ int s = L_TABLE[c] + L_TABLE[b];
s %= 255;
- s = aTable[s];
+ s = A_TABLE[s];
if (b == 0 || c == 0) {
s = 0;
}
int[] tmpArray = new int[] { 0, 0, 0, 0 };
for (int i = 1; i < 4; i++) {
for (int j = 0; j < 4; j++) {
- int[][][] shifts = EncryptionHandlerConstants.SHIFTS;
- int index = i * 4 + (j + shifts[0][i][d]) % 4;
+ int index = i * 4 + (j + EncryptionHandlerConstants.SHIFTS[0][i][d]) % 4;
tmpArray[j] = a[index];
}
for (int j = 0; j < 4; j++) {
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication.messages;
+
+import org.openhab.binding.paradoxalarm.internal.communication.IRequest;
+
+/**
+ * More generic interface for creating command requests
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+public interface Command {
+ IRequest getRequest(int id);
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication.messages;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * The {@link CommandPayload} Class that structures the payload for partition commands.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class CommandPayload implements IPayload {
-
- private static final int BYTES_LENGTH = 15;
-
- private final byte MESSAGE_START = 0x40;
- private final byte PAYLOAD_SIZE = 0x0f;
- private final byte[] EMPTY_FOUR_BYTES = { 0, 0, 0, 0 };
- private final byte CHECKSUM = 0;
-
- private final int partitionNumber;
- private final PartitionCommand command;
-
- public CommandPayload(int partitionNumber, PartitionCommand command) {
- this.partitionNumber = partitionNumber;
- this.command = command;
- }
-
- @Override
- public byte[] getBytes() {
- byte[] bufferArray = new byte[BYTES_LENGTH];
- ByteBuffer buf = ByteBuffer.wrap(bufferArray);
- buf.put(MESSAGE_START);
- buf.put(PAYLOAD_SIZE);
- buf.put(EMPTY_FOUR_BYTES);
- buf.put(calculateMessageBytes());
- buf.put(EMPTY_FOUR_BYTES);
- buf.put(CHECKSUM);
- return bufferArray;
- }
-
- /*
- * The message bytes contain nibbles of command information. First byte, first nibble is partition 1, first byte,
- * second nibble is partition 2, second byte, first nibble is partition 3, etc...
- *
- * For command values that are set in byte nibbles, see PartitionCommand enum
- */
- private byte[] calculateMessageBytes() {
- byte[] result = { 0, 0, 0, 0 };
- int index = (partitionNumber - 1) / 2;
- result[index] = (byte) (calculateNibbleToSet() & 0xff);
- return result;
- }
-
- private int calculateNibbleToSet() {
- if ((partitionNumber - 1) % 2 == 0) {
- return (command.getCommand() << 4) & 0xF0;
- } else {
- return command.getCommand() & 0x0F;
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication.messages;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link PartitionCommand} Enum representing the possible commands for a partition with the respective integer
- * values that are sent as nibbles in the packet.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public enum PartitionCommand {
- UNKNOWN(0),
- ARM(2),
- STAY_ARM(3),
- INSTANT_ARM(4),
- FORCE_ARM(5),
- DISARM(6),
- BEEP(8);
-
- private static final Logger logger = LoggerFactory.getLogger(PartitionCommand.class);
-
- private int command;
-
- PartitionCommand(int command) {
- this.command = command;
- }
-
- public int getCommand() {
- return command;
- }
-
- public static PartitionCommand parse(String command) {
- try {
- return PartitionCommand.valueOf(command);
- } catch (IllegalArgumentException e) {
- logger.debug("Unable to parse command={}. Fallback to UNKNOWN.", command);
- return PartitionCommand.UNKNOWN;
- }
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication.messages.partition;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.paradoxalarm.internal.communication.IRequest;
+import org.openhab.binding.paradoxalarm.internal.communication.PartitionCommandRequest;
+import org.openhab.binding.paradoxalarm.internal.communication.RequestType;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.Command;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link PartitionCommand} Enum representing the possible commands for a partition with the respective integer
+ * values that are sent as nibbles in the packet.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public enum PartitionCommand implements Command {
+ ARM(2),
+ STAY_ARM(3),
+ INSTANT_ARM(4),
+ FORCE_ARM(5),
+ DISARM(6),
+ BEEP(8);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PartitionCommand.class);
+
+ private int command;
+
+ PartitionCommand(int command) {
+ this.command = command;
+ }
+
+ public int getCommand() {
+ return command;
+ }
+
+ public static @Nullable PartitionCommand parse(String command) {
+ try {
+ return PartitionCommand.valueOf(command);
+ } catch (IllegalArgumentException e) {
+ LOGGER.debug("Unable to parse command={}. Fallback to UNKNOWN.", command);
+ return null;
+ }
+ }
+
+ @Override
+ public IRequest getRequest(int partitionId) {
+ PartitionCommandPayload payload = new PartitionCommandPayload(partitionId, this);
+ ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes())
+ .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
+ return new PartitionCommandRequest(RequestType.PARTITION_COMMAND, packet, null);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication.messages.partition;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload;
+
+/**
+ * The {@link PartitionCommandPayload} Class that structures the payload for partition commands.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class PartitionCommandPayload implements IPayload {
+
+ private static final int BYTES_LENGTH = 15;
+
+ private static final byte MESSAGE_START = 0x40;
+ private static final byte PAYLOAD_SIZE = 0x0f;
+ private static final byte[] EMPTY_FOUR_BYTES = { 0, 0, 0, 0 };
+ private static final byte CHECKSUM = 0;
+
+ private final int partitionNumber;
+ private final PartitionCommand command;
+
+ public PartitionCommandPayload(int partitionNumber, PartitionCommand command) {
+ this.partitionNumber = partitionNumber;
+ this.command = command;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ byte[] bufferArray = new byte[BYTES_LENGTH];
+ ByteBuffer buf = ByteBuffer.wrap(bufferArray);
+ buf.put(MESSAGE_START);
+ buf.put(PAYLOAD_SIZE);
+ buf.put(EMPTY_FOUR_BYTES);
+ buf.put(calculateMessageBytes());
+ buf.put(EMPTY_FOUR_BYTES);
+ buf.put(CHECKSUM);
+ return bufferArray;
+ }
+
+ /*
+ * The message bytes contain nibbles of command information. First byte, first nibble is partition 1, first byte,
+ * second nibble is partition 2, second byte, first nibble is partition 3, etc...
+ *
+ * For command values that are set in byte nibbles, see PartitionCommand enum
+ */
+ private byte[] calculateMessageBytes() {
+ byte[] result = { 0, 0, 0, 0 };
+ int index = (partitionNumber - 1) / 2;
+ result[index] = (byte) (calculateNibbleToSet() & 0xff);
+ return result;
+ }
+
+ private int calculateNibbleToSet() {
+ if ((partitionNumber - 1) % 2 == 0) {
+ return (command.getCommand() << 4) & 0xF0;
+ } else {
+ return command.getCommand() & 0x0F;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication.messages.zone;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.paradoxalarm.internal.communication.IRequest;
+import org.openhab.binding.paradoxalarm.internal.communication.RequestType;
+import org.openhab.binding.paradoxalarm.internal.communication.ZoneCommandRequest;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.Command;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public enum ZoneCommand implements Command {
+ CLEAR_BYPASS(0),
+ BYPASS(8);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ZoneCommand.class);
+
+ private byte command;
+
+ ZoneCommand(int command) {
+ this.command = (byte) command;
+ }
+
+ public byte getCommand() {
+ return command;
+ }
+
+ public static @Nullable ZoneCommand parse(@Nullable String command) {
+ if (command == null) {
+ return null;
+ }
+
+ try {
+ return ZoneCommand.valueOf(command);
+ } catch (IllegalArgumentException e) {
+ LOGGER.debug("Unable to parse command={}. Fallback to UNKNOWN.", command);
+ return null;
+ }
+ }
+
+ @Override
+ public IRequest getRequest(int zoneId) {
+ ZoneCommandPayload payload = new ZoneCommandPayload(zoneId, this);
+ ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes(), false)
+ .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
+ return new ZoneCommandRequest(RequestType.ZONE_COMMAND, packet, null);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.paradoxalarm.internal.communication.messages.zone;
+
+import java.nio.ByteBuffer;
+
+import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload;
+import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
+
+/**
+ * @author Konstantin Polihronov - Initial contribution
+ */
+public class ZoneCommandPayload implements IPayload {
+
+ private static final int BYTES_LENGTH = 31;
+
+ private static final byte MESSAGE_START = (byte) 0xD0;
+ private static final byte PAYLOAD_SIZE = 0x1f;
+ private static final byte ZONE_FLAG = 0x08; // "bypassed" flag (5th bit)
+ private static final byte[] EMPTY_TWO_BYTES = { 0, 0 };
+ private static final byte CHECKSUM = 0;
+
+ private int zoneNumber;
+ private ZoneCommand command;
+
+ public ZoneCommandPayload(int zoneNumber, ZoneCommand command) {
+ this.zoneNumber = zoneNumber;
+ this.command = command;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ byte[] bufferArray = new byte[BYTES_LENGTH];
+ ByteBuffer buf = ByteBuffer.wrap(bufferArray);
+ buf.put(MESSAGE_START);
+ buf.put(PAYLOAD_SIZE);
+ buf.put(ZONE_FLAG);
+ buf.put(command.getCommand());
+ buf.put(EMPTY_TWO_BYTES);
+ buf.put(calculateMessageBytes());
+ buf.put(ParadoxUtil.calculateChecksum(bufferArray));
+ return bufferArray;
+ }
+
+ /**
+ * The total zone message consists of 24 bytes (8 bits each) which results of 192 bits for each zone in case of
+ * Evo192 (i.e. 24x8).
+ * The low nible of each byte represents the first 4 zones of each group as a bit, the high nible is
+ * the second 4 zones. The zone groups are considered every 8 zones represented by a byte in this array (1-8,9-16,
+ * 17-24, etc)<br>
+ *
+ * Example: So if we address zone 1 for example the value of first byte will be 0x01, for zone 2 - 0x02, for zone 3
+ * - 0x04
+ * (third bit set to 1), for zone 4 - 0x08(fourth bit set to 1),<br>
+ * for zone 5 - 0x10, for zone 6 - 0x20, for zone 7 - 0x40, for zone 8 - 0x80.<br>
+ * For examples see TestGetBytes.java
+ *
+ * @return 24 bytes array with the needed zone to be set to bypass/clear bypass
+ */
+ private byte[] calculateMessageBytes() {
+ byte[] zoneMessage = new byte[24];
+ int byteIndex = (zoneNumber - 1) / 8;
+ byte zoneByteGroup = zoneMessage[byteIndex];
+ int bitNumber = calculateBitNumber();
+ zoneMessage[byteIndex] = ParadoxUtil.setBit(zoneByteGroup, bitNumber - 1, 1);
+ return zoneMessage;
+ }
+
+ private int calculateBitNumber() {
+ int residual = zoneNumber % 8;
+ return residual != 0 ? residual : 8;
+ }
+}
ThingUID thingUID = new ThingUID(PARTITION_THING_TYPE_UID, bridgeUid, thingId);
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUid)
.withLabel("Partition " + label).withProperty(PARTITION_THING_TYPE_ID, thingId)
- .withProperty("id", partition.getId()).build();
+ .withProperty("label", label).withProperty("id", partition.getId()).build();
logger.debug("Partition DiscoveryResult={}", result);
thingDiscovered(result);
ThingUID thingUID = new ThingUID(ZONE_THING_TYPE_UID, bridgeUid, thingId);
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUid)
.withLabel("Zone " + label).withProperty(ZONE_THING_TYPE_ID, thingId)
- .withProperty("id", zone.getId()).build();
+ .withProperty("id", zone.getId()).withProperty("label", label).build();
logger.debug("Zone DiscoveryResult={}", result);
thingDiscovered(result);
public static final String PANEL_BOARD_VOLTAGE = "boardVoltage";
public static final String PANEL_BATTERY_VOLTAGE = "batteryVoltage";
- public static final String PARTITION_LABEL_CHANNEL_UID = "partitionLabel";
+ public static final String PARTITION_LABEL_CHANNEL_UID = "label";
public static final String PARTITION_STATE_CHANNEL_UID = "state";
@Deprecated // After implementation of channels for every possible state, the summarized additional states is no
// longer needed. We'll keep it for backward compatibility
public static final String PARTITION_INHIBIT_READY_CHANNEL_UID = "inhibitReady";
public static final String PARTITION_ALL_ZONES_CLOSED_CHANNEL_UID = "allZonesClosed";
- public static final String ZONE_LABEL_CHANNEL_UID = "zoneLabel";
+ public static final String ZONE_LABEL_CHANNEL_UID = "label";
public static final String ZONE_OPENED_CHANNEL_UID = "opened";
public static final String ZONE_TAMPERED_CHANNEL_UID = "tampered";
public static final String ZONE_LOW_BATTERY_CHANNEL_UID = "lowBattery";
+ public static final String ZONE_SUPERVISION_TROUBLE_UID = "supervisionTrouble";
+ public static final String ZONE_IN_TX_DELAY_UID = "inTxDelay";
+ public static final String ZONE_SHUTDOWN_UID = "shutdown";
+ public static final String ZONE_BYPASSED_UID = "bypassed";
+ public static final String ZONE_HAS_ACTIVATED_INTELLIZONE_DELAY_UID = "hasActivatedIntellizoneDelay";
+ public static final String ZONE_HAS_ACTIVATED_ENTRY_DELAY_UID = "hasActivatedEntryDelay";
+ public static final String ZONE_PRESENTLY_IN_ALARM_UID = "presentlyInAlarm";
+ public static final String ZONE_GENERATED_ALARM_UID = "generatedAlarm";
+
// Misc constants
public static final StringType STATE_OFFLINE = new StringType("Offline");
public static final StringType STATE_ONLINE = new StringType("Online");
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Thing;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxPanelHandler} This is the handler that takes care of the panel related stuff.
@NonNullByDefault
public class ParadoxPanelHandler extends EntityBaseHandler {
- private final Logger logger = LoggerFactory.getLogger(ParadoxPanelHandler.class);
-
public ParadoxPanelHandler(Thing thing) {
super(thing);
}
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.openhab.binding.paradoxalarm.internal.model.Partition;
import org.openhab.core.library.types.OnOffType;
}
}
- protected Partition getPartition() {
- int index = calculateEntityIndex();
- ParadoxIP150BridgeHandler bridge = (ParadoxIP150BridgeHandler) getBridge().getHandler();
- ParadoxPanel panel = bridge.getPanel();
- List<Partition> partitions = panel.getPartitions();
- if (partitions == null) {
- logger.debug(
- "Partitions collection of Paradox Panel object is null. Probably not yet initialized. Skipping update.");
- return null;
- }
- if (partitions.size() <= index) {
- logger.debug("Attempted to access partition out of bounds of current partitions list. Index: {}, List: {}",
- index, partitions);
- return null;
- }
-
- Partition partition = partitions.get(index);
- return partition;
- }
-
private OpenClosedType booleanToContactState(boolean value) {
return value ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
}
super.handleCommand(channelUID, command);
}
}
+
+ protected Partition getPartition() {
+ int index = calculateEntityIndex();
+ ParadoxIP150BridgeHandler bridge = (ParadoxIP150BridgeHandler) getBridge().getHandler();
+ ParadoxPanel panel = bridge.getPanel();
+ List<Partition> partitions = panel.getPartitions();
+ if (partitions == null) {
+ logger.debug(
+ "Partitions collection of Paradox Panel object is null. Probably not yet initialized. Skipping update.");
+ return null;
+ }
+ if (partitions.size() <= index) {
+ logger.debug("Attempted to access partition out of bounds of current partitions list. Index: {}, List: {}",
+ index, partitions);
+ return null;
+ }
+
+ Partition partition = partitions.get(index);
+ return partition;
+ }
}
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.openhab.binding.paradoxalarm.internal.model.Zone;
+import org.openhab.binding.paradoxalarm.internal.model.ZoneState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Override
protected void updateEntity() {
- int index = calculateEntityIndex();
- ParadoxIP150BridgeHandler bridge = (ParadoxIP150BridgeHandler) getBridge().getHandler();
- ParadoxPanel panel = bridge.getPanel();
+ ParadoxIP150BridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler == null) {
+ logger.debug("Paradox bridge handler is null. Skipping update.");
+ return;
+ }
+
+ ParadoxPanel panel = bridgeHandler.getPanel();
List<Zone> zones = panel.getZones();
if (zones == null) {
logger.debug(
"Zones collection of Paradox Panel object is null. Probably not yet initialized. Skipping update.");
return;
}
+
+ int index = calculateEntityIndex();
if (zones.size() <= index) {
logger.debug("Attempted to access zone out of bounds of current zone list. Index: {}, List: {}", index,
zones);
Zone zone = zones.get(index);
if (zone != null) {
updateState(ZONE_LABEL_CHANNEL_UID, new StringType(zone.getLabel()));
- updateState(ZONE_OPENED_CHANNEL_UID, booleanToContactState(zone.getZoneState().isOpened()));
- updateState(ZONE_TAMPERED_CHANNEL_UID, booleanToSwitchState(zone.getZoneState().isTampered()));
- updateState(ZONE_LOW_BATTERY_CHANNEL_UID, booleanToSwitchState(zone.getZoneState().hasLowBattery()));
+ ZoneState zoneState = zone.getZoneState();
+ if (zoneState != null) {
+ updateState(ZONE_OPENED_CHANNEL_UID, booleanToContactState(zoneState.isOpened()));
+ updateState(ZONE_TAMPERED_CHANNEL_UID, booleanToSwitchState(zoneState.isTampered()));
+ updateState(ZONE_LOW_BATTERY_CHANNEL_UID, booleanToSwitchState(zoneState.hasLowBattery()));
+
+ updateState(ZONE_SUPERVISION_TROUBLE_UID, booleanToSwitchState(zoneState.isSupervisionTrouble()));
+ updateState(ZONE_IN_TX_DELAY_UID, booleanToSwitchState(zoneState.isInTxDelay()));
+ updateState(ZONE_SHUTDOWN_UID, booleanToSwitchState(zoneState.isShutdown()));
+ updateState(ZONE_BYPASSED_UID, booleanToSwitchState(zoneState.isBypassed()));
+ updateState(ZONE_HAS_ACTIVATED_INTELLIZONE_DELAY_UID,
+ booleanToSwitchState(zoneState.isHasActivatedIntellizoneDelay()));
+ updateState(ZONE_HAS_ACTIVATED_ENTRY_DELAY_UID,
+ booleanToSwitchState(zoneState.isHasActivatedEntryDelay()));
+ updateState(ZONE_PRESENTLY_IN_ALARM_UID, booleanToSwitchState(zoneState.isPresentlyInAlarm()));
+ updateState(ZONE_GENERATED_ALARM_UID, booleanToSwitchState(zoneState.isGeneratedAlarm()));
+ }
}
}
private OnOffType booleanToSwitchState(boolean value) {
return value ? OnOffType.ON : OnOffType.OFF;
}
+
+ @Override
+ public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) {
+ if (command instanceof StringType) {
+ Zone zone = getZone();
+ if (zone != null) {
+ zone.handleCommand(command.toString());
+ }
+ } else {
+ super.handleCommand(channelUID, command);
+ }
+ }
+
+ protected Zone getZone() {
+ ParadoxIP150BridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler == null) {
+ logger.debug("Paradox bridge handler is null. Skipping update.");
+ return null;
+ }
+
+ ParadoxPanel panel = bridgeHandler.getPanel();
+ List<Zone> zones = panel.getZones();
+ if (zones == null) {
+ logger.debug(
+ "Zones collection of Paradox Panel object is null. Probably not yet initialized. Skipping update.");
+ return null;
+ }
+
+ int index = calculateEntityIndex();
+ if (zones.size() <= index) {
+ logger.debug("Attempted to access a zone out of bounds of current zone list. Index: {}, List: {}", index,
+ zones);
+ return null;
+ }
+
+ Zone zone = zones.get(index);
+ return zone;
+ }
+
+ private ParadoxIP150BridgeHandler getBridgeHandler() {
+ Bridge bridge = getBridge();
+ if (bridge == null) {
+ logger.debug("Paradox bridge is null. Skipping update.");
+ return null;
+ }
+
+ return (ParadoxIP150BridgeHandler) bridge.getHandler();
+ }
}
*/
package org.openhab.binding.paradoxalarm.internal.model;
-import org.openhab.binding.paradoxalarm.internal.communication.PartitionCommandRequest;
-import org.openhab.binding.paradoxalarm.internal.communication.RequestType;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand;
+import org.openhab.binding.paradoxalarm.internal.communication.IRequest;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand;
import org.openhab.binding.paradoxalarm.internal.handlers.Commandable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Override
public void handleCommand(String command) {
PartitionCommand partitionCommand = PartitionCommand.parse(command);
- if (partitionCommand == PartitionCommand.UNKNOWN) {
- logger.debug("Command UNKNOWN will be ignored.");
+ if (partitionCommand == null) {
+ logger.debug("Command {} is parsed to null. Skipping it", command);
return;
}
logger.debug("Submitting command={} for partition=[{}]", partitionCommand, this);
- CommandPayload payload = new CommandPayload(getId(), partitionCommand);
- ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes())
- .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
- PartitionCommandRequest request = new PartitionCommandRequest(RequestType.PARTITION_COMMAND, packet, null);
+ IRequest request = partitionCommand.getRequest(getId());
getPanel().getCommunicator().submitRequest(request);
}
}
*/
package org.openhab.binding.paradoxalarm.internal.model;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.paradoxalarm.internal.communication.IRequest;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.zone.ZoneCommand;
+import org.openhab.binding.paradoxalarm.internal.handlers.Commandable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
*
* @author Konstantin Polihronov - Initial contribution
*/
-public class Zone extends Entity {
+@NonNullByDefault
+public class Zone extends Entity implements Commandable {
private final Logger logger = LoggerFactory.getLogger(Zone.class);
- private ZoneState zoneState;
+ private @Nullable ZoneState zoneState;
- public Zone(ParadoxPanel panel, int id, String label) {
+ public Zone(ParadoxPanel panel, int id, @Nullable String label) {
super(panel, id, label);
}
- public ZoneState getZoneState() {
+ public @Nullable ZoneState getZoneState() {
return zoneState;
}
public void setZoneState(ZoneState zoneState) {
this.zoneState = zoneState;
- logger.debug("Zone {} state updated to:\tOpened: {}, Tampered: {}, LowBattery: {}", getLabel(),
- zoneState.isOpened(), zoneState.isTampered(), zoneState.hasLowBattery());
+ logger.debug("Zone {} state updated to: {}", getLabel(), zoneState);
+ }
+
+ @Override
+ public void handleCommand(@Nullable String command) {
+ ZoneCommand zoneCommand = ZoneCommand.parse(command);
+ if (zoneCommand == null) {
+ logger.debug("Command {} is parsed to null. Skipping it", command);
+ return;
+ }
+
+ logger.debug("Submitting command={} for partition=[{}]", zoneCommand, this);
+ IRequest request = zoneCommand.getRequest(getId());
+ getPanel().getCommunicator().submitRequest(request);
}
}
* @author Konstantin Polihronov - Initial contribution
*/
public class ZoneState {
+ // Regular states
private boolean isOpened;
private boolean isTampered;
private boolean hasLowBattery;
+ // Special flag states
+ private boolean supervisionTrouble;
+ private boolean inTxDelay;
+ private boolean shuttedDown;
+ private boolean bypassed;
+ private boolean hasActivatedIntellizoneDelay;
+ private boolean hasActivatedEntryDelay;
+ private boolean presentlyInAlarm;
+ private boolean generatedAlarm;
+
public ZoneState(boolean isOpened, boolean isTampered, boolean hasLowBattery) {
this.isOpened = isOpened;
this.isTampered = isTampered;
public void setHasLowBattery(boolean hasLowBattery) {
this.hasLowBattery = hasLowBattery;
}
+
+ public void setSupervisionTrouble(boolean supervisionTrouble) {
+ this.supervisionTrouble = supervisionTrouble;
+ }
+
+ public boolean isSupervisionTrouble() {
+ return supervisionTrouble;
+ }
+
+ public boolean isInTxDelay() {
+ return inTxDelay;
+ }
+
+ public void setInTxDelay(boolean inTxDelay) {
+ this.inTxDelay = inTxDelay;
+ }
+
+ public boolean isShutdown() {
+ return shuttedDown;
+ }
+
+ public void setShuttedDown(boolean shuttedDown) {
+ this.shuttedDown = shuttedDown;
+ }
+
+ public boolean isBypassed() {
+ return bypassed;
+ }
+
+ public void setBypassed(boolean bypassed) {
+ this.bypassed = bypassed;
+ }
+
+ public boolean isHasActivatedIntellizoneDelay() {
+ return hasActivatedIntellizoneDelay;
+ }
+
+ public void setHasActivatedIntellizoneDelay(boolean hasActivatedIntellizoneDelay) {
+ this.hasActivatedIntellizoneDelay = hasActivatedIntellizoneDelay;
+ }
+
+ public boolean isHasActivatedEntryDelay() {
+ return hasActivatedEntryDelay;
+ }
+
+ public void setHasActivatedEntryDelay(boolean hasActivatedEntryDelay) {
+ this.hasActivatedEntryDelay = hasActivatedEntryDelay;
+ }
+
+ public boolean isPresentlyInAlarm() {
+ return presentlyInAlarm;
+ }
+
+ public void setPresentlyInAlarm(boolean presentlyInAlarm) {
+ this.presentlyInAlarm = presentlyInAlarm;
+ }
+
+ public boolean isGeneratedAlarm() {
+ return generatedAlarm;
+ }
+
+ public void setGeneratedAlarm(boolean generatedAlarm) {
+ this.generatedAlarm = generatedAlarm;
+ }
+
+ @Override
+ public String toString() {
+ return "ZoneState [isOpened=" + isOpened + ", isTampered=" + isTampered + ", hasLowBattery=" + hasLowBattery
+ + ", supervisionTrouble=" + supervisionTrouble + ", inTxDelay=" + inTxDelay + ", shuttedDown="
+ + shuttedDown + ", bypassed=" + bypassed + ", hasActivatedIntellizoneDelay="
+ + hasActivatedIntellizoneDelay + ", hasActivatedEntryDelay=" + hasActivatedEntryDelay
+ + ", presentlyInAlarm=" + presentlyInAlarm + ", generatedAlarm=" + generatedAlarm + "]";
+ }
}
private byte[] zonesOpened;
private byte[] zonesTampered;
private byte[] zonesLowBattery;
+ private byte[] zoneSpecialFlags;
public byte[] getZonesOpened() {
return zonesOpened;
public void setZonesLowBattery(byte[] zonesLowBattery) {
this.zonesLowBattery = zonesLowBattery;
}
+
+ public byte[] getZoneSpecialFlags() {
+ return zoneSpecialFlags;
+ }
+
+ public void setZoneSpecialFlags(byte[] zoneSpecialFlags) {
+ this.zoneSpecialFlags = zoneSpecialFlags;
+ }
}
@Override
public ZoneState calculateZoneState(int id, ZoneStateFlags zoneStateFlags) {
-
int index = (id - 1) / 8;
int bitNumber = id % 8 - 1;
+ // Every zone state is represented by a bit set/unset in the big byte array retrieved from the memory of the
+ // panel
byte[] zonesOpened = zoneStateFlags.getZonesOpened();
boolean isOpened = ParadoxUtil.isBitSet(zonesOpened[index], bitNumber);
byte[] zonesLowBattery = zoneStateFlags.getZonesLowBattery();
boolean hasLowBattery = ParadoxUtil.isBitSet(zonesLowBattery[index], bitNumber);
- return new ZoneState(isOpened, isTampered, hasLowBattery);
+ ZoneState zoneState = new ZoneState(isOpened, isTampered, hasLowBattery);
+
+ calculateSpecialFlags(zoneStateFlags, id, zoneState);
+
+ return zoneState;
+ }
+
+ private void calculateSpecialFlags(ZoneStateFlags zoneStateFlags, int index, ZoneState zoneState) {
+ // Each byte is filled with 8 special zone flags.
+ // Each bit of the byte represents a specific flag.
+ // Zone Flags:
+ // 0 = Zone supervision trouble
+ // 1 = Zone in TX delay
+ // 2 = Zone shutted down
+ // 3 = Zone bypassed
+ // 4 = Zone activated intellizone delay
+ // 5 = Zone activated entry delay
+ // 6 = Zone presently in alarm
+ // 7 = Zone generated an alarm
+
+ // The index of the actual zones and partitions enumerates from 1-N. In the arrays we need to index it from 0.
+ int specialFlagsIndex = index - 1;
+ byte[] zoneSpecialFlags = zoneStateFlags.getZoneSpecialFlags();
+ byte currentZoneFlags = zoneSpecialFlags[specialFlagsIndex];
+
+ zoneState.setSupervisionTrouble(ParadoxUtil.isBitSet(currentZoneFlags, 0));
+ zoneState.setInTxDelay(ParadoxUtil.isBitSet(currentZoneFlags, 1));
+ zoneState.setShuttedDown(ParadoxUtil.isBitSet(currentZoneFlags, 2));
+ zoneState.setBypassed(ParadoxUtil.isBitSet(currentZoneFlags, 3));
+ zoneState.setHasActivatedIntellizoneDelay(ParadoxUtil.isBitSet(currentZoneFlags, 4));
+ zoneState.setHasActivatedEntryDelay(ParadoxUtil.isBitSet(currentZoneFlags, 5));
+ zoneState.setPresentlyInAlarm(ParadoxUtil.isBitSet(currentZoneFlags, 6));
+ zoneState.setGeneratedAlarm(ParadoxUtil.isBitSet(currentZoneFlags, 7));
}
}
public class ParadoxUtil {
private static final String SPACE_DELIMITER = " ";
- private static final Logger logger = LoggerFactory.getLogger(ParadoxUtil.class);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ParadoxUtil.class);
public static byte calculateChecksum(byte[] payload) {
int result = 0;
}
public static void printPacket(String description, byte[] array) {
- if (logger.isTraceEnabled()) {
- logger.trace("Packet payload size: {}", array[1]);
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Packet payload size: {}", array[1]);
printByteArray(description, array, array[1] + 16);
}
}
public static void printByteArray(String description, byte[] array) {
if (array == null) {
- logger.trace("Array is null");
+ LOGGER.trace("Array is null");
return;
}
printByteArray(description, array, array.length);
}
public static void printByteArray(String description, byte[] array, int length) {
- if (!logger.isTraceEnabled()) {
+ if (!LOGGER.isTraceEnabled()) {
return;
}
String result = byteArrayToString(array, length);
if (!result.isEmpty()) {
- logger.trace("{}", description + SPACE_DELIMITER + result);
+ LOGGER.trace("{}", description + SPACE_DELIMITER + result);
}
}
byte[] byteArray = outputStream.toByteArray();
return byteArray;
} catch (IOException e) {
- logger.warn("Exception merging arrays:", e);
+ LOGGER.warn("Exception merging arrays:", e);
return new byte[0];
}
}
-# add-on
-
-addon.paradoxalarm.name = ParadoxAlarm Binding
-addon.paradoxalarm.description = This is the binding for ParadoxAlarm.
-
# thing types
thing-type.paradoxalarm.ip150.label = Paradox IP150 Module Connector
channel-type.paradoxalarm.bootloaderVersion.description = Boot loader version
channel-type.paradoxalarm.bypassReady.label = Partition Is Bypass Ready
channel-type.paradoxalarm.bypassReady.description = Partition is Bypass Ready
+channel-type.paradoxalarm.bypassed.label = Bypassed
+channel-type.paradoxalarm.command.label = Communicator Command
+channel-type.paradoxalarm.command.description = Send Command
channel-type.paradoxalarm.command.label = Partition Command
channel-type.paradoxalarm.command.description = Send command
channel-type.paradoxalarm.command.state.option.ARM = Arm
channel-type.paradoxalarm.command.state.option.FORCE_ARM = Force Arm
channel-type.paradoxalarm.command.state.option.DISARM = Disarm
channel-type.paradoxalarm.command.state.option.BEEP = Keyboard Beep
-channel-type.paradoxalarm.command.label = Communicator Command
-channel-type.paradoxalarm.command.description = Send Command
+channel-type.paradoxalarm.command.label = Zone Command
+channel-type.paradoxalarm.command.description = Send command for a zone
+channel-type.paradoxalarm.command.state.option.BYPASS = Bypass
+channel-type.paradoxalarm.command.state.option.CLEAR_BYPASS = Clear Bypass
channel-type.paradoxalarm.communicationState.label = Bridge Communication State
channel-type.paradoxalarm.communicationState.description = Status of connection to Paradox system
channel-type.paradoxalarm.forceReady.label = Partition Is Force Ready
channel-type.paradoxalarm.forceReady.description = Partition is Force Ready
+channel-type.paradoxalarm.generatedAlarm.label = Generated an Alarm
channel-type.paradoxalarm.hardwareVersion.label = Hardware Version
channel-type.paradoxalarm.hardwareVersion.description = Panel hardware version
+channel-type.paradoxalarm.hasActivatedEntryDelay.label = Has Activated Entry Delay
+channel-type.paradoxalarm.hasActivatedIntellizoneDelay.label = Has Activated Intellizone Delay
channel-type.paradoxalarm.inEntryDelay.label = Partition In Entry Delay
channel-type.paradoxalarm.inEntryDelay.description = Partition in Entry Delay
channel-type.paradoxalarm.inExitDelay.label = Partition In Exit Delay
channel-type.paradoxalarm.inExitDelay.description = Partition in Exit Delay
channel-type.paradoxalarm.inTrouble.label = Partition In Trouble
channel-type.paradoxalarm.inTrouble.description = Partition in Trouble
+channel-type.paradoxalarm.inTxDelay.label = In TX Delay
channel-type.paradoxalarm.inhibitReady.label = Partition Is Inhibit Ready
channel-type.paradoxalarm.inhibitReady.description = Partition is Inhibit Ready
channel-type.paradoxalarm.openedState.label = Zone State
channel-type.paradoxalarm.panelType.description = Panel type (Evo, SP, etc)
channel-type.paradoxalarm.partitionLabel.label = Partition Label
channel-type.paradoxalarm.partitionLabel.description = Label of partition
+channel-type.paradoxalarm.presentlyInAlarm.label = Currently in Alarm
channel-type.paradoxalarm.readyToArm.label = Partition Ready To Arm
channel-type.paradoxalarm.readyToArm.description = Partition ready to arm
channel-type.paradoxalarm.serialNumber.label = Serial Number
channel-type.paradoxalarm.serialNumber.description = Panel serial number
+channel-type.paradoxalarm.shutdown.label = Shutdown
channel-type.paradoxalarm.state.label = Partition State
channel-type.paradoxalarm.state.description = State of partition
channel-type.paradoxalarm.stayInstantReady.label = Partition Is Stay Instant Ready
channel-type.paradoxalarm.stayInstantReady.description = Partition is Stay Instant Ready
+channel-type.paradoxalarm.supervisionTrouble.label = Supervision Trouble
channel-type.paradoxalarm.tamperedState.label = Tampered
channel-type.paradoxalarm.tamperedState.description = State of zone
channel-type.paradoxalarm.voltage.label = Voltage
channel-type.paradoxalarm.zoneInTamperTrouble.description = Partition has in Tamper Trouble
channel-type.paradoxalarm.zoneLabel.label = Zone Label
channel-type.paradoxalarm.zoneLabel.description = Label of zone
+
+# add-on
+
+addon.paradoxalarm.name = ParadoxAlarm Binding
+addon.paradoxalarm.description = This is the binding for ParadoxAlarm.
<channel id="opened" typeId="openedState"/>
<channel id="tampered" typeId="tamperedState"/>
<channel id="lowBattery" typeId="system.low-battery"/>
+ <channel id="command" typeId="command"/>
+
+ <channel id="supervisionTrouble" typeId="supervisionTrouble"/>
+ <channel id="inTxDelay" typeId="inTxDelay"/>
+ <channel id="shutdown" typeId="shutdown"/>
+ <channel id="bypassed" typeId="bypassed"/>
+ <channel id="hasActivatedIntellizoneDelay" typeId="hasActivatedIntellizoneDelay"/>
+ <channel id="hasActivatedEntryDelay" typeId="hasActivatedEntryDelay"/>
+ <channel id="presentlyInAlarm" typeId="presentlyInAlarm"/>
+ <channel id="generatedAlarm" typeId="generatedAlarm"/>
</channels>
<config-description>
<description>State of zone</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
+ <channel-type id="command">
+ <item-type>String</item-type>
+ <label>Zone Command</label>
+ <description>Send command for a zone</description>
+ <state>
+ <options>
+ <option value="BYPASS">Bypass</option>
+ <option value="CLEAR_BYPASS">Clear Bypass</option>
+ </options>
+ </state>
+ </channel-type>
+ <channel-type id="supervisionTrouble">
+ <item-type>Switch</item-type>
+ <label>Supervision Trouble</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+ <channel-type id="inTxDelay">
+ <item-type>Switch</item-type>
+ <label>In TX Delay</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+ <channel-type id="shutdown">
+ <item-type>Switch</item-type>
+ <label>Shutdown</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+ <channel-type id="bypassed">
+ <item-type>Switch</item-type>
+ <label>Bypassed</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+ <channel-type id="hasActivatedIntellizoneDelay">
+ <item-type>Switch</item-type>
+ <label>Has Activated Intellizone Delay</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+ <channel-type id="hasActivatedEntryDelay">
+ <item-type>Switch</item-type>
+ <label>Has Activated Entry Delay</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+ <channel-type id="presentlyInAlarm">
+ <item-type>Switch</item-type>
+ <label>Currently in Alarm</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+ <channel-type id="generatedAlarm">
+ <item-type>Switch</item-type>
+ <label>Generated an Alarm</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
</thing:thing-descriptions>
*/
package org.openhab.binding.paradoxalarm.internal;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommandPayload;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
/**
*
* @author Konstantin Polihronov - Initial contribution
*/
+@NonNullByDefault
public class TestCreateCommandPayload {
@Test
public void testCreatePayload() {
for (PartitionCommand command : PartitionCommand.values()) {
for (int partitionNumber = 1; partitionNumber <= 8; partitionNumber++) {
- CommandPayload payload = new CommandPayload(partitionNumber, command);
+ PartitionCommandPayload payload = new PartitionCommandPayload(partitionNumber, command);
assertNibble(partitionNumber, command, payload);
}
}
}
- private void assertNibble(int partitionNumber, PartitionCommand command, CommandPayload payload) {
+ private void assertNibble(int partitionNumber, PartitionCommand command, PartitionCommandPayload payload) {
byte[] bytes = payload.getBytes();
int payloadIndexOfByteToCheck = 6 + (partitionNumber - 1) / 2;
byte byteValue = bytes[payloadIndexOfByteToCheck];
import java.util.Arrays;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler;
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand;
*
* @author Konstantin Polihronov - Initial contribution
*/
+@NonNullByDefault
public class TestEncryptionHandler {
private static final String INPUT_STRING = "My test string for encryption.";
*/
package org.openhab.binding.paradoxalarm.internal;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload;
import org.openhab.binding.paradoxalarm.internal.communication.messages.EpromRequestPayload;
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand;
import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload;
import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
-import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommandPayload;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.zone.ZoneCommand;
+import org.openhab.binding.paradoxalarm.internal.communication.messages.zone.ZoneCommandPayload;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
*
* @author Konstantin Polihronov - Initial contribution
*/
+@NonNullByDefault
public class TestGetBytes {
private static final int PARTITION_NUMBER = 1;
assertTrue(Arrays.equals(packetBytes, EXPECTED1));
}
- private static final byte[] EXPECTED_COMMAND_PAYLOAD = { 0x40, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00 };
+ private static final byte[] EXPECTED_PARTITION_COMMAND_PAYLOAD = { 0x40, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
@Test
- public void testCommandPayload() {
- CommandPayload payload = new CommandPayload(PARTITION_NUMBER, PartitionCommand.ARM);
+ public void testPartitionCommandPayload() {
+ PartitionCommandPayload payload = new PartitionCommandPayload(PARTITION_NUMBER, PartitionCommand.ARM);
final byte[] packetBytes = payload.getBytes();
- ParadoxUtil.printByteArray("Expected =", EXPECTED_COMMAND_PAYLOAD);
+ ParadoxUtil.printByteArray("Expected =", EXPECTED_PARTITION_COMMAND_PAYLOAD);
ParadoxUtil.printByteArray("Result =", packetBytes);
- assertTrue(Arrays.equals(packetBytes, EXPECTED_COMMAND_PAYLOAD));
+ assertTrue(Arrays.equals(packetBytes, EXPECTED_PARTITION_COMMAND_PAYLOAD));
}
private static final byte[] EXPECTED_MEMORY_PAYLOAD = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x08, (byte) 0xF0, 0x00,
ParadoxUtil.printByteArray("Expected =", EXPECTED_MEMORY_PAYLOAD);
ParadoxUtil.printByteArray("Result =", bytes);
}
+
+ private static final byte[] EXPECTED_ZONE_5_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x08, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f };
+ private static final byte[] EXPECTED_ZONE_5_CLEAR_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 };
+ private static final byte[] EXPECTED_ZONE_20_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 };
+ private static final byte[] EXPECTED_ZONE_23_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f };
+ private static final byte[] EXPECTED_ZONE_23_CLEAR_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37 };
+
+ @Test
+ public void testZoneCommandPayload() {
+ testZoneCommandPayload(5, ZoneCommand.BYPASS, EXPECTED_ZONE_5_BYPASS_COMMAND);
+ testZoneCommandPayload(5, ZoneCommand.CLEAR_BYPASS, EXPECTED_ZONE_5_CLEAR_BYPASS_COMMAND);
+ testZoneCommandPayload(20, ZoneCommand.BYPASS, EXPECTED_ZONE_20_BYPASS_COMMAND);
+ testZoneCommandPayload(23, ZoneCommand.BYPASS, EXPECTED_ZONE_23_BYPASS_COMMAND);
+ testZoneCommandPayload(23, ZoneCommand.CLEAR_BYPASS, EXPECTED_ZONE_23_CLEAR_BYPASS_COMMAND);
+ }
+
+ private void testZoneCommandPayload(int zoneNumber, ZoneCommand command, byte[] expected) {
+ ZoneCommandPayload payload = new ZoneCommandPayload(zoneNumber, command);
+ final byte[] packetBytes = payload.getBytes();
+
+ ParadoxUtil.printByteArray("Expected " + zoneNumber + " =", expected);
+ ParadoxUtil.printByteArray("Result " + zoneNumber + " =", packetBytes);
+
+ assertTrue(Arrays.equals(packetBytes, expected));
+ }
}
*/
package org.openhab.binding.paradoxalarm.internal;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
*
* @author Konstantin Polihronov - Initial contribution
*/
+@NonNullByDefault
public class TestParadoxUtil {
@Test
final int rate = 16;
byte[] extendedArray = ParadoxUtil.extendArray(arrayToExtend, rate);
- final byte[] EXPECTED_RESULT = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x59, (byte) 0xEE, (byte) 0xEE,
+ final byte[] expectedResult = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x59, (byte) 0xEE, (byte) 0xEE,
(byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE };
- assertArrayEquals(EXPECTED_RESULT, extendedArray); //
+ assertArrayEquals(expectedResult, extendedArray); //
}
@Test
public void testMergeArrays() {
- final byte[] ARR1 = { 0x01, 0x02, 0x03 };
- final byte[] ARR2 = { 0x04, 0x05, 0x06 };
- final byte[] ARR3 = { 0x07, 0x08, 0x09 };
- byte[] mergedArrays = ParadoxUtil.mergeByteArrays(ARR1, ARR2, ARR3);
+ final byte[] arr1 = { 0x01, 0x02, 0x03 };
+ final byte[] arr2 = { 0x04, 0x05, 0x06 };
+ final byte[] arr3 = { 0x07, 0x08, 0x09 };
+ byte[] mergedArrays = ParadoxUtil.mergeByteArrays(arr1, arr2, arr3);
- final byte[] EXPECTED_RESULT = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
- assertArrayEquals(EXPECTED_RESULT, mergedArrays);
+ final byte[] expectedResult = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
+ assertArrayEquals(expectedResult, mergedArrays);
}
}