added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.paradoxalarm-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-paradoxalarm" description="Paradox Alarm System Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.paradoxalarm/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,207 @@
/**
* Copyright (c) 2010-2020 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 java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.ScheduledExecutorService;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AbstractCommunicator} Abstract class with common low-level communication logic. Extended by the
* communicator classes.
*
* @author Konstantin Polihronov - Initial contribution
*/
public abstract class AbstractCommunicator implements IParadoxInitialLoginCommunicator {
protected static final int SOCKET_TIMEOUT = 4000;
private static final long PACKET_EXPIRATION_TRESHOLD_MILLISECONDS = 2000;
private final Logger logger = LoggerFactory.getLogger(AbstractCommunicator.class);
protected ScheduledExecutorService scheduler;
protected Collection<IDataUpdateListener> listeners;
protected Socket socket;
private ISocketTimeOutListener stoListener;
private final String ipAddress;
private final int tcpPort;
private DataOutputStream tx;
private DataInputStream rx;
private boolean isOnline;
public AbstractCommunicator(String ipAddress, int tcpPort, ScheduledExecutorService scheduler)
throws UnknownHostException, IOException {
this.ipAddress = ipAddress;
this.tcpPort = tcpPort;
logger.debug("IP Address={}, TCP Port={}", ipAddress, tcpPort);
this.scheduler = scheduler;
initializeSocket();
}
protected void initializeSocket() throws IOException, UnknownHostException {
if (socket != null && !socket.isClosed()) {
close();
}
socket = new Socket(ipAddress, tcpPort);
socket.setSoTimeout(SOCKET_TIMEOUT);
tx = new DataOutputStream(socket.getOutputStream());
rx = new DataInputStream(socket.getInputStream());
}
@Override
public synchronized void close() {
logger.info("Stopping communication to Paradox system");
setOnline(false);
try {
tx.close();
rx.close();
socket.close();
} catch (IOException e) {
logger.warn("IO exception during socket/stream close operation.", e);
}
}
@Override
public void submitRequest(IRequest request) {
if (isEncrypted()) {
request.getRequestPacket().encrypt();
}
SyncQueue syncQueue = SyncQueue.getInstance();
syncQueue.add(request);
communicateToParadox();
}
protected void communicateToParadox() {
SyncQueue syncQueue = SyncQueue.getInstance();
synchronized (syncQueue) {
if (syncQueue.hasPacketToReceive()) {
receivePacket();
} else if (syncQueue.hasPacketsToSend()) {
sendPacket();
}
// Recursively check if there are more packets to send in TX queue until it becomes empty
if (syncQueue.hasPacketsToSend() || syncQueue.hasPacketToReceive()) {
communicateToParadox();
}
}
}
protected void sendPacket() {
SyncQueue syncQueue = SyncQueue.getInstance();
IRequest request = syncQueue.peekSendQueue();
try {
logger.trace("Sending packet with request={}", request);
byte[] packetBytes = request.getRequestPacket().getBytes();
ParadoxUtil.printPacket("Tx Packet:", packetBytes);
tx.write(packetBytes);
syncQueue.moveRequest();
} catch (SocketException e) {
logger.debug("Socket time out occurred. Informing listener. Request={}. Exception=", request, e);
syncQueue.removeSendRequest();
stoListener.onSocketTimeOutOccurred(e);
} catch (IOException e) {
logger.debug("Error while sending packet with request={}. IOException=", request, e);
syncQueue.removeSendRequest();
}
}
protected void receivePacket() {
SyncQueue syncQueue = SyncQueue.getInstance();
try {
logger.trace("Found packet to receive in queue...");
byte[] result = new byte[256];
int readBytes = rx.read(result);
if (readBytes > 0 && result[1] > 0 && result[1] + 16 < 256) {
logger.trace("Successfully read valid packet from Rx");
IRequest request = syncQueue.poll();
byte[] bytesData = Arrays.copyOfRange(result, 0, readBytes);
IResponse response = new Response(request, bytesData, isEncrypted());
if (response.getPayload() == null || response.getHeader() == null) {
handleWrongPacket(result, request);
}
IResponseReceiver responseReceiver = request.getResponseReceiver();
if (responseReceiver != null) {
responseReceiver.receiveResponse(response, this);
}
} else if (SyncQueue.getInstance().peekReceiveQueue()
.isTimeStampExpired(PACKET_EXPIRATION_TRESHOLD_MILLISECONDS)) {
logger.trace("Unable to receive proper package for {} time. Rescheduling...",
PACKET_EXPIRATION_TRESHOLD_MILLISECONDS);
} else {
IRequest requestInQueue = syncQueue.poll();
logger.debug("Error receiving packet after reaching the set timeout of {}ms. Request: {}",
PACKET_EXPIRATION_TRESHOLD_MILLISECONDS, requestInQueue);
}
} catch (SocketException e) {
IRequest request = syncQueue.poll();
logger.debug("Socket time out occurred. Informing listener. Request={}, SocketException=", request, e);
stoListener.onSocketTimeOutOccurred(e);
} catch (IOException e) {
IRequest request = syncQueue.poll();
logger.debug("Unable to receive package due to IO Exception. Request {}, IOException=", request, e);
}
}
protected void handleWrongPacket(byte[] result, IRequest request) throws IOException {
logger.trace(
"Payload or header are null. Probably unexpected package has been read. Need to retry read the same request.");
rx.read(result);
ParadoxUtil.printPacket("Flushing packet:", result);
submitRequest(request);
}
@Override
public boolean isOnline() {
return isOnline;
}
@Override
public void setOnline(boolean flag) {
this.isOnline = flag;
}
public DataInputStream getRx() {
return rx;
}
protected abstract void receiveEpromResponse(IResponse response);
protected abstract void receiveRamResponse(IResponse response);
public ISocketTimeOutListener getStoListener() {
return stoListener;
}
@Override
public void setStoListener(ISocketTimeOutListener stoListener) {
this.stoListener = stoListener;
}
}

View File

@@ -0,0 +1,421 @@
/**
* Copyright (c) 2010-2020 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 java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler;
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand;
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType;
import org.openhab.binding.paradoxalarm.internal.communication.messages.IPPacket;
import org.openhab.binding.paradoxalarm.internal.communication.messages.IpMessagesConstants;
import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link CommunicationState}. This is enum based state machine used mostly for the logon process orchestration.
*
* @author Konstantin Polihronov - Initial contribution
*/
public enum CommunicationState implements IResponseReceiver {
START {
@Override
protected CommunicationState nextState() {
return STEP2;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
String password = communicator.getPassword();
logger.debug("Phase {}", this);
if (communicator.isEncrypted()) {
EncryptionHandler.getInstance().updateKey(ParadoxUtil.getBytesFromString(password));
}
ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxUtil.getBytesFromString(password), false)
.setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
sendLogonPhasePacket(communicator, packet);
}
@Override
protected boolean isPhaseSuccess(IResponse response) {
byte payloadResponseByte = response.getPayload()[0];
if (payloadResponseByte == 0x00) {
logger.info("Login - Login to IP150 - OK");
return true;
}
byte headerResponseByte = response.getHeader()[4];
switch (headerResponseByte) {
case 0x30:
logger.warn("Login - Login to IP150 failed - Incorrect password");
break;
case 0x78:
case 0x79:
logger.warn("Login - IP module is busy");
break;
}
switch (payloadResponseByte) {
case 0x01:
logger.warn("Login - Invalid password");
break;
case 0x02:
case 0x04:
logger.warn("Login - User already connected");
break;
default:
logger.warn("Login - Connection refused");
}
return false;
}
@Override
public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
if (isPhaseSuccess(response)) {
// Retrieve new encryption key from first packet response and update it in encryption handler
if (communicator.isEncrypted()) {
byte[] payload = response.getPayload();
byte[] receivedKey = Arrays.copyOfRange(payload, 1, 17);
EncryptionHandler.getInstance().updateKey(receivedKey);
}
logger.debug("Phase {} completed successfully.", this);
nextState().runPhase(communicator);
}
}
},
STEP2 {
@Override
protected CommunicationState nextState() {
return STEP3;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
logger.debug("Phase {}", this);
ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxIPPacket.EMPTY_PAYLOAD, false)
.setCommand(HeaderCommand.LOGIN_COMMAND1);
sendLogonPhasePacket(communicator, packet);
}
},
STEP3 {
@Override
protected CommunicationState nextState() {
return STEP4;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
logger.debug("Phase {}", this);
ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxIPPacket.EMPTY_PAYLOAD, false)
.setCommand(HeaderCommand.LOGIN_COMMAND2);
sendLogonPhasePacket(communicator, packet);
}
},
STEP4 {
@Override
protected CommunicationState nextState() {
return STEP5;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
logger.debug("Phase {}", this);
byte[] message4 = new byte[37];
message4[0] = 0x72;
ParadoxIPPacket packet = new ParadoxIPPacket(message4, true)
.setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
sendLogonPhasePacket(communicator, packet);
}
@Override
public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
byte[] payload = response.getPayload();
if (payload != null && payload.length >= 37) {
communicator.setPanelInfoBytes(payload);
logger.debug("Phase {} completed successfully.", this);
nextState().runPhase(communicator);
} else {
logger.warn("Received wrong response in phase {}. Response: {}", this, response);
LOGOUT.runPhase(communicator);
}
}
},
STEP5 {
@Override
protected CommunicationState nextState() {
return STEP6;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
logger.debug("Phase {}", this);
ParadoxIPPacket packet = new ParadoxIPPacket(IpMessagesConstants.UNKNOWN_IP150_REQUEST_MESSAGE01, false)
.setCommand(HeaderCommand.SERIAL_CONNECTION_INITIATED);
sendLogonPhasePacket(communicator, packet);
}
},
STEP6 {
@Override
protected CommunicationState nextState() {
return STEP7;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
logger.debug("Phase {}", this);
byte[] message6 = new byte[37];
message6[0] = 0x5F;
message6[1] = 0x20;
ParadoxIPPacket packet = new ParadoxIPPacket(message6, true)
.setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
sendLogonPhasePacket(communicator, packet);
}
@Override
public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
byte[] payload = response.getPayload();
ParadoxUtil.printPacket("Init communication sub array: ", payload);
logger.debug("Phase {} completed successfully.", this);
nextState().runPhase(communicator, payload);
}
},
STEP7 {
@Override
protected CommunicationState nextState() {
return INITIALIZE_DATA;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
if (args != null && args.length == 1) {
byte[] initializationMessage = (byte[]) args[0];
logger.debug("Phase {}", this);
byte[] message7 = generateInitializationRequest(initializationMessage,
communicator.getPcPasswordBytes());
ParadoxIPPacket packet = new ParadoxIPPacket(message7, true)
.setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
sendLogonPhasePacket(communicator, packet);
} else {
logger.error("Error in step {}. Missing argument {}", this, args);
throw new IllegalArgumentException(
"Initialization message not send in request for phase + " + this + ". Arguments= " + args);
}
}
private byte[] generateInitializationRequest(byte[] initializationMessage, byte[] pcPassword) {
byte[] message7 = new byte[] {
// Initialization command
0x00,
// Module address
initializationMessage[1],
// Not used
0x00, 0x00,
// Product ID
initializationMessage[4],
// Software version
initializationMessage[5],
// Software revision
initializationMessage[6],
// Software ID
initializationMessage[7],
// Module ID
initializationMessage[8], initializationMessage[9],
// PC Password
pcPassword[0], pcPassword[1],
// Modem speed
0x08,
// Winload type ID
0x30,
// User code (aligned with PAI)
0x02, 0x02, 0x00,
// Module serial number
initializationMessage[17], initializationMessage[18], initializationMessage[19],
initializationMessage[20],
// EVO section 3030-3038 data
initializationMessage[21], initializationMessage[22], initializationMessage[23],
initializationMessage[24], initializationMessage[25], initializationMessage[26],
initializationMessage[27], initializationMessage[28], initializationMessage[29],
// Not used
0x00, 0x00, 0x00, 0x00,
// Source ID
0x00,
// Carrier length
0x00,
// Checksum
0x00 };
return message7;
}
@Override
public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
// UGLY - this is the handling of ghost packet which appears after the logon sequence
// Read ghost packet affter 300ms then continue with normal flow
communicator.getScheduler().schedule(() -> {
if (communicator instanceof GenericCommunicator) {
try {
GenericCommunicator genCommunicator = (GenericCommunicator) communicator;
byte[] value = new byte[256];
int packetLength = genCommunicator.getRx().read(value);
logger.debug("Reading ghost packet with length={}", packetLength);
ParadoxUtil.printPacket("Reading ghost packet", value);
} catch (IOException e) {
logger.debug("Error reading ghost packet.", e);
}
super.receiveResponse(response, communicator);
}
}, 300, TimeUnit.MILLISECONDS);
}
},
INITIALIZE_DATA {
@Override
protected CommunicationState nextState() {
return ONLINE;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
if (communicator instanceof IParadoxCommunicator) {
IParadoxCommunicator comm = (IParadoxCommunicator) communicator;
comm.initializeData();
}
nextState().runPhase(communicator);
}
},
ONLINE {
@Override
protected CommunicationState nextState() {
return this;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
logger.debug("Phase {}. Setting communicator to status ONLINE.", this);
communicator.setOnline(true);
logger.info("Successfully established communication with the panel.");
}
},
LOGOUT {
@Override
protected CommunicationState nextState() {
return OFFLINE;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
// For some reason after sending logout packet the connection gets reset from the other end
// currently workaround is to run directly offline phase, i.e. close socket from our end
// logger.info("Logout packet sent to IP150.");
// ParadoxIPPacket logoutPacket = new ParadoxIPPacket(IpMessagesConstants.LOGOUT_MESAGE_BYTES, true)
// .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
// sendPacket(communicator, logoutPacket);
nextState().runPhase(communicator);
}
},
OFFLINE {
@Override
protected CommunicationState nextState() {
return this;
}
@Override
protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
if (communicator != null) {
communicator.close();
}
ParadoxPanel.getInstance().dispose();
}
};
protected final Logger logger = LoggerFactory.getLogger(CommunicationState.class);
private static CommunicationState currentState = CommunicationState.OFFLINE;
// This method is the entry of logon procedure.
public static void login(IParadoxInitialLoginCommunicator communicator) {
START.runPhase(communicator);
}
public static void logout(IParadoxInitialLoginCommunicator communicator) {
LOGOUT.runPhase(communicator);
}
protected abstract CommunicationState nextState();
protected abstract void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args);
protected void runPhase(IParadoxInitialLoginCommunicator communicator) {
setCurrentState(this);
runPhase(communicator, new Object[0]);
}
protected void sendLogonPhasePacket(IParadoxInitialLoginCommunicator communicator, IPPacket packet) {
IRequest request = new LogonRequest(this, packet);
communicator.submitRequest(request);
}
@Override
public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
if (isPhaseSuccess(response)) {
logger.debug("Phase {} completed successfully.", this);
nextState().runPhase(communicator);
}
}
protected boolean isPhaseSuccess(IResponse response) {
return true;
}
public static CommunicationState getCurrentState() {
return currentState;
}
public static void setCurrentState(CommunicationState currentState) {
CommunicationState.currentState = currentState;
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 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;
import org.openhab.binding.paradoxalarm.internal.model.EntityType;
/**
* The {@link EpromRequest}. Request for retrieving EPROM data from Paradox system.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class EpromRequest extends Request {
private EntityType entityType;
private int entityId;
public EpromRequest(int entityId, EntityType entityType, IPPacket payload, IResponseReceiver receiver) {
super(RequestType.EPROM, payload, receiver);
this.entityId = entityId;
this.entityType = entityType;
}
public EntityType getEntityType() {
return entityType;
}
public int getEntityId() {
return entityId;
}
@Override
public String toString() {
return "EpromRequest [getType()=" + getType() + ", entityType=" + entityType + ", entityId=" + entityId + "]";
}
}

View File

@@ -0,0 +1,420 @@
/**
* Copyright (c) 2010-2020 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 java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.paradoxalarm.internal.communication.messages.EpromRequestPayload;
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType;
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.RamRequestPayload;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.model.EntityType;
import org.openhab.binding.paradoxalarm.internal.model.PanelType;
import org.openhab.binding.paradoxalarm.internal.model.ZoneStateFlags;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EvoCommunicator} is responsible for handling communication to Evo192 alarm system via IP150 interface.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class EvoCommunicator extends GenericCommunicator implements IParadoxCommunicator {
private static final byte RAM_BLOCK_SIZE = (byte) 64;
private final Logger logger = LoggerFactory.getLogger(EvoCommunicator.class);
private MemoryMap memoryMap;
private Map<EntityType, Map<Integer, String>> entityLabelsMap = new HashMap<>();
private PanelType panelType = PanelType.UNKNOWN;
private Integer maxPartitions;
private Integer maxZones;
private EvoCommunicator(String ipAddress, int tcpPort, String ip150Password, String pcPassword,
ScheduledExecutorService scheduler, PanelType panelType, Integer maxPartitions, Integer maxZones,
boolean useEncryption) throws UnknownHostException, IOException {
super(ipAddress, tcpPort, ip150Password, pcPassword, scheduler, useEncryption);
this.panelType = panelType;
this.maxPartitions = maxPartitions;
this.maxZones = maxZones;
logger.debug("PanelType={}, Max Partitions={}, Max Zones={}", panelType, maxPartitions, maxZones);
initializeMemoryMap();
}
@Override
protected void receiveEpromResponse(IResponse response) {
byte[] payload = response.getPayload();
if (payload != null) {
EpromRequest request = (EpromRequest) response.getRequest();
int entityId = request.getEntityId();
EntityType entityType = request.getEntityType();
updateEntityLabel(entityType, entityId, payload);
} else {
logger.debug("Wrong parsed result. Probably wrong data received in response. Response={}", response);
return;
}
}
@Override
protected void receiveRamResponse(IResponse response) {
byte[] payload = response.getPayload();
if (payload != null && payload.length >= RAM_BLOCK_SIZE) {
RamRequest request = (RamRequest) response.getRequest();
int ramBlockNumber = request.getRamBlockNumber();
memoryMap.updateElement(ramBlockNumber, payload);
if (logger.isTraceEnabled()) {
logger.trace("Result for ramBlock={} is [{}]", ramBlockNumber, ParadoxUtil.byteArrayToString(payload));
}
// Trigger listeners update when last memory page update is received
if (ramBlockNumber == panelType.getRamPagesNumber()) {
updateListeners();
}
} else {
logger.debug("Wrong parsed result. Probably wrong data received in response");
return;
}
}
private void updateEntityLabel(EntityType entityType, int entityId, byte[] payload) {
String label = createString(payload);
logger.debug("{} label updated to: {}", entityType, label);
entityLabelsMap.get(entityType).put(entityId, label);
}
private void retrievePartitionLabel(int partitionNo) {
logger.debug("Submitting request for partition label: {}", partitionNo);
int address = 0x3A6B + (partitionNo) * 107;
byte labelLength = 16;
try {
IPayload payload = new EpromRequestPayload(address, labelLength);
ParadoxIPPacket readEpromIPPacket = createSerialPassthroughPacket(payload);
IRequest epromRequest = new EpromRequest(partitionNo, EntityType.PARTITION, readEpromIPPacket, this);
submitRequest(epromRequest);
} catch (ParadoxException e) {
logger.debug("Error creating request for with number={}, Exception={} ", partitionNo, e.getMessage());
}
}
private ParadoxIPPacket createSerialPassthroughPacket(IPayload payload) {
ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes());
packet.setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
return packet;
}
private void retrieveZoneLabel(int zoneNumber) {
logger.debug("Submitting request for zone label: {}", zoneNumber);
final byte labelLength = 16;
try {
int address;
if (zoneNumber < 96) {
address = 0x430 + (zoneNumber) * 16;
} else {
address = 0x62F7 + (zoneNumber - 96) * 16;
}
IPayload payload = new EpromRequestPayload(address, labelLength);
ParadoxIPPacket readEpromIPPacket = createSerialPassthroughPacket(payload);
IRequest epromRequest = new EpromRequest(zoneNumber, EntityType.ZONE, readEpromIPPacket, this);
submitRequest(epromRequest);
} catch (ParadoxException e) {
logger.debug("Error creating request with number={}, Exception={} ", zoneNumber, e.getMessage());
}
}
@Override
public List<byte[]> getPartitionFlags() {
List<byte[]> result = new ArrayList<>();
byte[] element = memoryMap.getElement(2);
byte[] firstBlock = Arrays.copyOfRange(element, 32, 64);
element = memoryMap.getElement(3);
byte[] secondBlock = Arrays.copyOfRange(element, 0, 16);
byte[] mergeByteArrays = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
for (int i = 0; i < mergeByteArrays.length; i += 6) {
result.add(Arrays.copyOfRange(mergeByteArrays, i, i + 6));
}
return result;
}
@Override
public ZoneStateFlags getZoneStateFlags() {
ZoneStateFlags result = new ZoneStateFlags();
byte[] firstPage = memoryMap.getElement(0);
byte[] secondPage = memoryMap.getElement(8);
int pageOffset = panelType == PanelType.EVO48 ? 34 : 40;
byte[] firstBlock = Arrays.copyOfRange(firstPage, 28, pageOffset);
if (panelType != PanelType.EVO192) {
result.setZonesOpened(firstBlock);
} else {
byte[] secondBlock = Arrays.copyOfRange(secondPage, 0, 12);
byte[] zonesOpened = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
result.setZonesOpened(zonesOpened);
}
pageOffset = panelType == PanelType.EVO48 ? 46 : 52;
firstBlock = Arrays.copyOfRange(firstPage, 40, pageOffset);
if (panelType != PanelType.EVO192) {
result.setZonesTampered(firstBlock);
} else {
byte[] secondBlock = Arrays.copyOfRange(secondPage, 12, 24);
byte[] zonesTampered = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
result.setZonesTampered(zonesTampered);
}
pageOffset = panelType == PanelType.EVO48 ? 58 : 64;
firstBlock = Arrays.copyOfRange(firstPage, 52, pageOffset);
if (panelType != PanelType.EVO192) {
result.setZonesTampered(firstBlock);
} else {
byte[] secondBlock = Arrays.copyOfRange(secondPage, 24, 36);
byte[] zonesLowBattery = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock);
result.setZonesLowBattery(zonesLowBattery);
}
return result;
}
public void initializeMemoryMap() {
for (EntityType type : EntityType.values()) {
entityLabelsMap.put(type, new HashMap<>());
}
List<byte[]> ramCache = new ArrayList<>(panelType.getRamPagesNumber() + 1);
for (int i = 0; i <= panelType.getRamPagesNumber(); i++) {
ramCache.add(new byte[0]);
}
memoryMap = new MemoryMap(ramCache);
}
@Override
public void refreshMemoryMap() {
if (!isOnline()) {
logger.debug("Attempt to refresh memory map was made, but communicator is not online. Skipping update.");
return;
}
SyncQueue queue = SyncQueue.getInstance();
synchronized (queue) {
for (int i = 1; i <= panelType.getRamPagesNumber(); i++) {
submitRamRequest(i);
}
}
}
private void submitRamRequest(int blockNo) {
try {
logger.trace("Creating RAM page {} read request", blockNo);
IPayload payload = new RamRequestPayload(blockNo, RAM_BLOCK_SIZE);
ParadoxIPPacket ipPacket = createSerialPassthroughPacket(payload);
IRequest ramRequest = new RamRequest(blockNo, ipPacket, this);
submitRequest(ramRequest);
} catch (ParadoxException e) {
logger.debug(
"Unable to create request payload from provided bytes to read. blockNo={}, bytes to read={}. Exception={}",
blockNo, RAM_BLOCK_SIZE, e.getMessage());
}
}
private String createString(byte[] payloadResult) {
return new String(payloadResult, StandardCharsets.US_ASCII);
}
@Override
public void executeCommand(String command) {
IP150Command ip150Command = IP150Command.valueOf(command);
switch (ip150Command) {
case LOGIN:
startLoginSequence();
return;
case LOGOUT:
CommunicationState.LOGOUT.runPhase(this);
return;
case RESET:
CommunicationState.LOGOUT.runPhase(this);
scheduler.schedule(this::startLoginSequence, 5, TimeUnit.SECONDS);
return;
default:
logger.debug("Command {} not implemented.", command);
}
}
@Override
public MemoryMap getMemoryMap() {
return memoryMap;
}
public Map<EntityType, Map<Integer, String>> getEntityLabelsMap() {
return entityLabelsMap;
}
@Override
public Map<Integer, String> getPartitionLabels() {
return entityLabelsMap.get(EntityType.PARTITION);
}
@Override
public Map<Integer, String> getZoneLabels() {
return entityLabelsMap.get(EntityType.ZONE);
}
@Override
public void initializeData() {
synchronized (SyncQueue.getInstance()) {
initializeEpromData();
refreshMemoryMap();
}
}
private void initializeEpromData() {
for (int i = 0; i < maxPartitions; i++) {
retrievePartitionLabel(i);
}
for (int i = 0; i < maxZones; i++) {
retrieveZoneLabel(i);
}
}
public static class EvoCommunicatorBuilder implements ICommunicatorBuilder {
private final Logger logger = LoggerFactory.getLogger(EvoCommunicatorBuilder.class);
// Mandatory parameters
private PanelType panelType;
private String ipAddress;
private String ip150Password;
private ScheduledExecutorService scheduler;
// Non mandatory or with predefined values
private Integer maxPartitions;
private Integer maxZones;
private int tcpPort = 10000;
private String pcPassword = "0000";
private boolean useEncryption;
EvoCommunicatorBuilder(PanelType panelType) {
this.panelType = panelType;
}
@Override
public IParadoxCommunicator build() {
if (ipAddress == null || ipAddress.isEmpty()) {
final String msg = "IP address cannot be empty !";
logger.debug(msg);
throw new ParadoxRuntimeException(msg);
}
if (ip150Password == null || ip150Password.isEmpty()) {
final String msg = "Password for IP150 cannot be empty !";
logger.debug(msg);
throw new ParadoxRuntimeException(msg);
}
if (scheduler == null) {
final String msg = "Scheduler is mandatory parameter !";
logger.debug(msg);
throw new ParadoxRuntimeException(msg);
}
if (maxPartitions == null || maxPartitions < 1) {
this.maxPartitions = panelType.getPartitions();
}
if (maxZones == null || maxZones < 1) {
this.maxZones = panelType.getZones();
}
try {
return new EvoCommunicator(ipAddress, tcpPort, ip150Password, pcPassword, scheduler, panelType,
maxPartitions, maxZones, useEncryption);
} catch (IOException e) {
logger.warn("Unable to create communicator for Panel={}. Message={}", panelType, e.getMessage());
throw new ParadoxRuntimeException(e);
}
}
@Override
public ICommunicatorBuilder withMaxZones(Integer maxZones) {
this.maxZones = maxZones;
return this;
}
@Override
public ICommunicatorBuilder withMaxPartitions(Integer maxPartitions) {
this.maxPartitions = maxPartitions;
return this;
}
@Override
public ICommunicatorBuilder withIp150Password(String ip150Password) {
this.ip150Password = ip150Password;
return this;
}
@Override
public ICommunicatorBuilder withPcPassword(String pcPassword) {
this.pcPassword = pcPassword;
return this;
}
@Override
public ICommunicatorBuilder withIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
return this;
}
@Override
public ICommunicatorBuilder withTcpPort(Integer tcpPort) {
this.tcpPort = tcpPort;
return this;
}
@Override
public ICommunicatorBuilder withScheduler(ScheduledExecutorService scheduler) {
this.scheduler = scheduler;
return this;
}
@Override
public ICommunicatorBuilder withEncryption(boolean useEncryption) {
this.useEncryption = useEncryption;
return this;
}
}
}

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2010-2020 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 java.io.IOException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.concurrent.ScheduledExecutorService;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link GenericCommunicator} Used for the common communication logic for all types of panels.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class GenericCommunicator extends AbstractCommunicator implements IResponseReceiver {
private final Logger logger = LoggerFactory.getLogger(GenericCommunicator.class);
private final byte[] pcPasswordBytes;
private byte[] panelInfoBytes;
private boolean isEncrypted;
private final String password;
public GenericCommunicator(String ipAddress, int tcpPort, String ip150Password, String pcPassword,
ScheduledExecutorService scheduler, boolean useEncryption) throws UnknownHostException, IOException {
super(ipAddress, tcpPort, scheduler);
this.isEncrypted = useEncryption;
logger.debug("Use encryption={}", isEncrypted);
this.password = ip150Password;
this.pcPasswordBytes = ParadoxUtil.stringToBCD(pcPassword);
}
@Override
public synchronized void startLoginSequence() {
logger.debug("Login sequence started");
if (isOnline()) {
logger.debug("Already logged on. No action needed. Returning.");
return;
}
if (socket.isClosed()) {
try {
initializeSocket();
} catch (IOException e) {
throw new ParadoxRuntimeException(e);
}
}
CommunicationState.login(this);
}
@Override
public byte[] getPanelInfoBytes() {
return panelInfoBytes;
}
@Override
public void setPanelInfoBytes(byte[] panelInfoBytes) {
this.panelInfoBytes = panelInfoBytes;
}
@Override
public byte[] getPcPasswordBytes() {
return pcPasswordBytes;
}
@Override
protected void receiveEpromResponse(IResponse response) {
// Nothing to do here. Override in particular implementation class.
}
@Override
protected void receiveRamResponse(IResponse response) {
// Nothing to do here. Override in particular implementation class.
}
public void refreshMemoryMap() {
// Nothing to do here. Override in particular implementation class.
}
@Override
public ScheduledExecutorService getScheduler() {
return scheduler;
}
@Override
public void setListeners(Collection<IDataUpdateListener> listeners) {
this.listeners = listeners;
}
@Override
public void updateListeners() {
if (listeners != null && !listeners.isEmpty()) {
listeners.forEach(IDataUpdateListener::update);
}
}
@Override
public boolean isEncrypted() {
return isEncrypted;
}
@Override
public String getPassword() {
return password;
}
@Override
public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
IRequest request = response.getRequest();
logger.trace("Handling response for request={}", request);
ParadoxUtil.printPacket("Full packet", response.getPacketBytes());
RequestType type = request.getType();
if (type == RequestType.RAM) {
receiveRamResponse(response);
} else if (type == RequestType.EPROM) {
receiveEpromResponse(response);
} else {
logger.debug("Probably wrong sender in the request. Request type is not one of the supported methods.");
}
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 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 java.util.concurrent.ScheduledExecutorService;
/**
* The {@link ICommunicatorBuilder} is representing the functionality of communicator builders.
* The idea is to ease initialization of communicators which can have lots of parameters.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface ICommunicatorBuilder {
ICommunicatorBuilder withMaxZones(Integer zones);
ICommunicatorBuilder withMaxPartitions(Integer partitions);
ICommunicatorBuilder withIp150Password(String ip150Password);
ICommunicatorBuilder withPcPassword(String pcPassword);
ICommunicatorBuilder withIpAddress(String ipAddress);
ICommunicatorBuilder withTcpPort(Integer tcpPort);
ICommunicatorBuilder withScheduler(ScheduledExecutorService scheduler);
ICommunicatorBuilder withEncryption(boolean useEncryption);
IParadoxCommunicator build();
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link IConnectionHandler} is base communication interface which defines only the basic communication level.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IConnectionHandler {
void close();
boolean isOnline();
void setOnline(boolean flag);
void submitRequest(IRequest request);
boolean isEncrypted();
/**
* @param stoListener This method sets a listener which is called in case of socket timeout occurrence.
*/
void setStoListener(ISocketTimeOutListener stoListener);
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link IDataUpdateListener} - Listener interface used to provide updates when data is retrieved.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IDataUpdateListener {
void update();
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link IParadoxCommunicator} is representing the functionality of communication implementation.
* If another Paradox alarm system is used this interface must be implemented.
*
* @author Konstantin Polihronov - Initial contribution
*/
public enum IP150Command {
LOGOUT,
LOGIN,
RESET,
UNIMPLEMENTED
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 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 java.util.List;
import java.util.Map;
import org.openhab.binding.paradoxalarm.internal.model.ZoneStateFlags;
/**
* The {@link IParadoxCommunicator} is representing the functionality of communication implementation.
* If another Paradox alarm system is used this interface must be implemented.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IParadoxCommunicator extends IParadoxInitialLoginCommunicator {
void refreshMemoryMap();
List<byte[]> getPartitionFlags();
ZoneStateFlags getZoneStateFlags();
void executeCommand(String commandAsString);
Map<Integer, String> getPartitionLabels();
Map<Integer, String> getZoneLabels();
void initializeData();
MemoryMap getMemoryMap();
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 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 java.util.Collection;
import java.util.concurrent.ScheduledExecutorService;
/**
* The {@link IParadoxInitialLoginCommunicator} is representing the functionality of generic communication. Only
* login/logout
* sequence which is used to determine the Panel type.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IParadoxInitialLoginCommunicator extends IConnectionHandler {
void startLoginSequence();
byte[] getPanelInfoBytes();
void setPanelInfoBytes(byte[] panelInfoBytes);
/**
* @return IP150 connection password
*/
String getPassword();
byte[] getPcPasswordBytes();
ScheduledExecutorService getScheduler();
void setListeners(Collection<IDataUpdateListener> listeners);
void updateListeners();
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link IRequest} - interface definition for the request used in the communication.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IRequest {
IResponseReceiver getResponseReceiver();
IPPacket getRequestPacket();
void setTimeStamp();
boolean isTimeStampExpired(long expirationTreshold);
RequestType getType();
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link IResponse} - interface for the response used in communication on the way back.
*
* @author Konstantin Polihronov - Initial contribution
*/
interface IResponse {
RequestType getType();
IRequest getRequest();
byte[] getPacketBytes();
byte[] getHeader();
byte[] getPayload();
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 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.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link IResponseReceiver} Used to pass parsed responses from Paradox to original senders of the requests for
* further processing.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public interface IResponseReceiver {
void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator);
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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 java.io.IOException;
/**
* The {@link ISocketTimeOutListener} Listener interface for informing listeners who may be interested and act in case
* of socket time out occurs.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface ISocketTimeOutListener {
void onSocketTimeOutOccurred(IOException exception);
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link LogonRequest}. Request for initial logon sequence.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class LogonRequest extends Request {
private CommunicationState logonSequenceSender;
public LogonRequest(CommunicationState logonSequenceSender, IPPacket payload) {
super(RequestType.LOGON_SEQUENCE, payload, logonSequenceSender);
this.logonSequenceSender = logonSequenceSender;
}
public CommunicationState getLogonSequenceSender() {
return logonSequenceSender;
}
@Override
public String toString() {
return "LogonRequest [getType()=" + getType() + ", getLogonSequenceSender()=" + getLogonSequenceSender() + "]";
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 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 java.util.ArrayList;
import java.util.List;
/**
* The {@link MemoryMap} this keeps Paradox RAM map as cached object inside the communicator.
* Every record in the list is byte array which contains 64 byte RAM page.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class MemoryMap {
private List<byte[]> ramCache = new ArrayList<>();
public MemoryMap(List<byte[]> ramCache) {
this.ramCache = ramCache;
}
public List<byte[]> getRamCache() {
return ramCache;
}
public void setRamCache(List<byte[]> ramCache) {
this.ramCache = ramCache;
}
public synchronized byte[] getElement(int index) {
return ramCache.get(index);
}
public synchronized void updateElement(int index, byte[] elementValue) {
ramCache.set(index - 1, elementValue);
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 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.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.model.PanelType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxBuilderFactory} used to create the proper communicator builder objects for different panel
* types.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxBuilderFactory {
private final Logger logger = LoggerFactory.getLogger(ParadoxBuilderFactory.class);
public ICommunicatorBuilder createBuilder(PanelType panelType) {
switch (panelType) {
case EVO48:
case EVO96:
case EVO192:
case EVOHD:
logger.debug("Creating new builder for Paradox {} system", panelType);
return new EvoCommunicator.EvoCommunicatorBuilder(panelType);
default:
logger.debug("Unsupported panel type: {}", panelType);
throw new ParadoxRuntimeException("Unsupported panel type: " + panelType);
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link PartitionCommandRequest} Request object for wrapping partition command.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class PartitionCommandRequest extends Request {
public PartitionCommandRequest(RequestType type, IPPacket packet, IResponseReceiver receiver) {
super(type, packet, receiver);
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link RamRequest}. Request for retrieving RAM pages from Paradox system.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class RamRequest extends Request {
private int ramBlockNumber;
public RamRequest(int ramBlockNumber, IPPacket payload, IResponseReceiver receiver) {
super(RequestType.RAM, payload, receiver);
this.ramBlockNumber = ramBlockNumber;
}
public int getRamBlockNumber() {
return ramBlockNumber;
}
@Override
public String toString() {
return "RamRequest [getType()=" + getType() + ", getRamBlockNumber()=" + getRamBlockNumber() + "]";
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link Request}. Abstract request class. Used to be derived for the particular types of requests to Paradox.
*
* @author Konstantin Polihronov - Initial contribution
*/
public abstract class Request implements IRequest {
private IPPacket packet;
private long timestamp;
private RequestType type;
private IResponseReceiver receiver;
public Request(RequestType type, IPPacket packet, IResponseReceiver receiver) {
this.packet = packet;
this.type = type;
this.receiver = receiver;
}
@Override
public IPPacket getRequestPacket() {
return packet;
}
@Override
public void setTimeStamp() {
timestamp = System.currentTimeMillis();
}
@Override
public boolean isTimeStampExpired(long tresholdInMillis) {
return System.currentTimeMillis() - timestamp >= tresholdInMillis;
}
@Override
public RequestType getType() {
return type;
}
@Override
public String toString() {
return "Request [packet=" + packet + ", timestamp=" + timestamp + ", type=" + type + "]";
}
@Override
public IResponseReceiver getResponseReceiver() {
return receiver;
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link RequestType}. Enum with possible request types to Paradox system.
*
* @author Konstantin Polihronov - Initial contribution
*/
public enum RequestType {
LOGON_SEQUENCE,
RAM,
EPROM,
PARTITION_COMMAND
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2020 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 java.util.Arrays;
import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Response}. The response which is returned after receiving data from socket.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class Response implements IResponse {
private final Logger logger = LoggerFactory.getLogger(Response.class);
private IRequest request;
private byte[] packetBytes;
private byte[] header;
private byte[] payload;
public Response(IRequest request, byte[] content, boolean useEncryption) {
this.request = request;
this.packetBytes = content;
ParadoxUtil.printPacket("Rx packet", packetBytes);
if (useEncryption) {
decrypt();
}
parsePacket();
}
@Override
public RequestType getType() {
return request.getType();
}
@Override
public byte[] getPacketBytes() {
return packetBytes;
}
@Override
public byte[] getPayload() {
return payload;
}
@Override
public byte[] getHeader() {
return header;
}
@Override
public IRequest getRequest() {
return request;
}
public void updatePayload(byte[] payload) {
this.packetBytes = payload;
}
private void decrypt() {
byte[] payloadBytes = Arrays.copyOfRange(packetBytes, 16, packetBytes.length);
logger.trace("DECRYPTING. Full packet length={}", packetBytes.length);
EncryptionHandler handler = EncryptionHandler.getInstance();
byte[] decrypted = handler.decrypt(payloadBytes);
header = Arrays.copyOfRange(packetBytes, 0, 16);
payload = Arrays.copyOfRange(decrypted, 0, header[1]);
packetBytes = ParadoxUtil.mergeByteArrays(header, payload);
ParadoxUtil.printByteArray("Decrypted package=", packetBytes, packetBytes.length);
}
/**
* This method parses data from the IP150 module.
* A panel command, e.g. 0x5
* A logon sequence part is starting with 0x0, 0x1 or 0x7
* We ignore invalid packets which do not match our pattern.
*
*/
private void parsePacket() {
// Message too short
if (packetBytes.length < 17) {
logger.debug("Message length is too short. Length={}", packetBytes.length);
return;
}
byte receivedCommand = packetBytes[16];
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;
}
// 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:
header = Arrays.copyOfRange(packetBytes, 0, 16);
payload = Arrays.copyOfRange(packetBytes, 16, packetBytes.length);
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;
}
}
// 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);
header = null;
payload = null;
}
@Override
public String toString() {
return "Response [request=" + request + ", packetBytes=" + ParadoxUtil.byteArrayToString(packetBytes) + "]";
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2020 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 java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SyncQueue} is used to synchronize communication to/from Paradox system. All requests go into sendQueue and
* upon send are popped from send queue and are pushed into receiveQueue.
* Due to nature of Paradox communication receive queue is with priority, i.e. if there is anything in receive queue we
* attempt to read the socket first and only after receive queue is empty then we attempt to send. We never send any
* packet if we have something to read.
* For more details about usage see method {@link AbstractCommunicator.submitRequest()}
*
* @author Konstantin Polihronov - Initial contribution
*/
public class SyncQueue {
private final Logger logger = LoggerFactory.getLogger(SyncQueue.class);
private BlockingQueue<IRequest> sendQueue = new ArrayBlockingQueue<>(1000, true);
private BlockingQueue<IRequest> receiveQueue = new ArrayBlockingQueue<>(10, true);
private static SyncQueue syncQueue;
private SyncQueue() {
}
public static SyncQueue getInstance() {
SyncQueue temp = syncQueue;
if (temp == null) {
synchronized (SyncQueue.class) {
temp = syncQueue;
if (temp == null) {
syncQueue = new SyncQueue();
}
}
}
return syncQueue;
}
public synchronized void add(IRequest request) {
logger.trace("Adding to queue request={}", request);
sendQueue.add(request);
}
public synchronized void moveRequest() {
IRequest request = sendQueue.poll();
request.setTimeStamp();
logger.trace("Moving from Tx to RX queue request={}", request);
receiveQueue.add(request);
}
public synchronized IRequest poll() {
IRequest request = receiveQueue.poll();
logger.trace("Removing from queue request={}", request);
return request;
}
public synchronized IRequest removeSendRequest() {
IRequest request = sendQueue.poll();
logger.trace("Removing from queue request={}", request);
return request;
}
public synchronized IRequest peekSendQueue() {
return sendQueue.peek();
}
public IRequest peekReceiveQueue() {
return receiveQueue.peek();
}
public synchronized boolean hasPacketToReceive() {
return receiveQueue.peek() != null;
}
public synchronized boolean hasPacketsToSend() {
return sendQueue.peek() != null;
}
public synchronized boolean canSend() {
return receiveQueue.isEmpty();
}
}

View File

@@ -0,0 +1,285 @@
/**
* Copyright (c) 2010-2020 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.crypto;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used to encrypt and decrypt communication from/to Paradox system. Singleton pattern.
*
* Paradox encryption is using Rijndael 256-key expansion alghoritm.
* When key is changed the updateKey(byte[]) method needs to be called in front.
* Encrypt and Decrypt methods use the expandedKey field to do their job.
*
* The first packet sent to Paradox is the IP150 password as bytes, extended to 32 bytes with 0xEE.
* The first response contains the key that will be used for the rest of communication.
*
* Most of the coding is copy from Python / rewrite in Java from second link of jpbaracca's PAI repository. Probably
* some of the variables can be named better but I don't understand this code in it's full scope so I preferred to keep
* it as it is.
*
* @author Konstantin Polihronov - Initial contribution
*
* @see <a href=https://www.samiam.org/key-schedule.html>Sam Trendholme's page about AES</a>
* @see <a href=https://github.com/ParadoxAlarmInterface/pai>Github of jpbaracca's work - ParadoxAlarmInterface in
* python</a>
*/
@NonNullByDefault
public class EncryptionHandler {
private final Logger logger = LoggerFactory.getLogger(EncryptionHandler.class);
private static final int KEY_ARRAY_LENGTH = 32;
private static final int TABLE_SIZE = 256;
private static final int KEY_LENGTH = 240;
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 EncryptionHandler instance = new EncryptionHandler(new byte[] {});
static {
generateTables();
}
private static void generateTables() {
int a = 1;
int d;
for (int index = 0; index < 255; index++) {
aTable[index] = a & 0xFF;
/* Multiply by three */
d = (a & 0x80) & 0xFF;
a <<= 1;
if (d == 0x80) {
a ^= 0x1b;
a &= 0xFF;
}
a ^= aTable[index];
a &= 0xFF;
/* Set the log table value */
lTable[aTable[index]] = index & 0xFF;
}
aTable[255] = aTable[0];
lTable[0] = 0;
}
private final int[] expandedKey = new int[KEY_LENGTH];
private EncryptionHandler(byte[] newKey) {
if (newKey.length > 0) {
expandKey(newKey);
}
}
public static EncryptionHandler getInstance() {
return instance;
}
public synchronized EncryptionHandler updateKey(byte[] newKey) {
instance = new EncryptionHandler(newKey);
return instance;
}
public byte[] encrypt(byte[] payload) {
if (payload.length % 16 != 0) {
payload = ParadoxUtil.extendArray(payload, PAYLOAD_RATE_LENGTH);
printArray("Array had to be extended:", payload);
logger.trace("New payload length={}", payload.length);
}
int[] payloadAsIntArray = ParadoxUtil.toIntArray(payload);
final int[] s = EncryptionHandlerConstants.S;
byte[] result = new byte[0];
for (int i = 0; i < payloadAsIntArray.length / 16; i++) {
int[] tempArray = Arrays.copyOfRange(payloadAsIntArray, i * 16, (i + 1) * 16);
keyAddition(tempArray, 0);
for (int r = 1; r <= ROUNDS; r++) {
sBox(tempArray, s);
shiftRow(tempArray, 0);
if (r != ROUNDS) {
mixColumn(tempArray);
}
keyAddition(tempArray, r * 16);
}
result = ParadoxUtil.mergeByteArrays(result, ParadoxUtil.toByteArray(tempArray));
}
printArray("Encrypted array", result);
return result;
}
public byte[] decrypt(byte[] payload) {
int[] payloadAsIntArray = ParadoxUtil.toIntArray(payload);
final int[] si = EncryptionHandlerConstants.Si;
byte[] result = new byte[0];
for (int i = 0; i < payloadAsIntArray.length / 16; i++) {
int[] tempArray = Arrays.copyOfRange(payloadAsIntArray, i * 16, (i + 1) * 16);
for (int r = ROUNDS; r > 0; r--) {
keyAddition(tempArray, r * 16);
if (r != ROUNDS) {
invMixColumn(tempArray);
}
sBox(tempArray, si);
shiftRow(tempArray, 1);
}
keyAddition(tempArray, 0);
result = ParadoxUtil.mergeByteArrays(result, ParadoxUtil.toByteArray(tempArray));
}
printArray("Decrypted array", result);
return result;
}
private void printArray(String description, byte[] array) {
ParadoxUtil.printByteArray(description, array, array.length);
}
private byte[] fillArray(byte[] keyBytes) {
byte[] byteArray = new byte[keyBytes.length];
for (int i = 0; i < keyBytes.length; i++) {
byteArray[i] = (byte) (keyBytes[i] & 0xFF);
}
byte[] expandedArray = ParadoxUtil.extendArray(byteArray, KEY_ARRAY_LENGTH);
return expandedArray;
}
private void expandKey(byte[] input) {
// fill array to 32th byte with 0xEE
byte[] filledArray = fillArray(input);
int[] temp = { 0, 0, 0, 0 };
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
expandedKey[j * 4 + i] = filledArray[i * 4 + j] & 0xFF;
}
for (int j = 0; j < 4; j++) {
expandedKey[j * 4 + i + 16] = filledArray[i * 4 + j + 16] & 0xFF;
}
}
final int[] s = EncryptionHandlerConstants.S;
for (int i = 8; i < 60; i++) {
for (int j = 0; j < 4; j++) {
temp[j] = expandedKey[(((i - 1) & 0xfc) << 2) + ((i - 1) & 0x03) + j * 4];
}
if (i % 4 == 0) {
for (int j = 0; j < 4; j++) {
temp[j] = s[temp[j]];
}
}
if (i % 8 == 0) {
int tmp = temp[0];
for (int j = 1; j < 4; j++) {
temp[j - 1] = temp[j];
}
temp[3] = tmp;
temp[0] ^= EncryptionHandlerConstants.RCON[(i / 8 - 1)];
}
for (int j = 0; j < 4; j++) {
expandedKey[((i & 0xfc) << 2) + (i & 0x03)
+ j * 4] = expandedKey[(((i - 8) & 0xfc) << 2) + ((i - 8) & 0x03) + j * 4] ^ temp[j];
}
}
}
private int gmul(int c, int b) {
int s = lTable[c] + lTable[b];
s %= 255;
s = aTable[s];
if (b == 0 || c == 0) {
s = 0;
}
return s;
}
private void sBox(int[] a, int[] box) {
for (int i = 0; i < 16; i++) {
a[i] = box[a[i]];
}
}
private void mixColumn(int[] a) {
final int[] xtimetbl = EncryptionHandlerConstants.XTIMETABLE;
int[] b = new int[] { 0, 0, 0, 0 };
for (int j = 0; j < 4; j++) {
int tmp = a[j] ^ a[j + 4] ^ a[j + 8] ^ a[j + 12];
for (int i = 0; i < 4; i++) {
b[i] = a[i * 4 + j];
}
b[0] ^= xtimetbl[a[j] ^ a[j + 4]] ^ tmp;
b[1] ^= xtimetbl[a[j + 4] ^ a[j + 8]] ^ tmp;
b[2] ^= xtimetbl[a[j + 8] ^ a[j + 12]] ^ tmp;
b[3] ^= xtimetbl[a[j + 12] ^ a[j]] ^ tmp;
for (int i = 0; i < 4; i++) {
a[i * 4 + j] = b[i];
}
}
}
private void invMixColumn(int[] a) {
int[][] b = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } };
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 4; i++) {
b[i][j] = gmul(0xe, a[i * 4 + j]) ^ gmul(0xb, a[((i + 1) % 4) * 4 + j])
^ gmul(0xd, a[((i + 2) % 4) * 4 + j]) ^ gmul(0x9, a[((i + 3) % 4) * 4 + j]);
}
}
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 4; i++) {
a[i * 4 + j] = b[i][j];
}
}
}
private void shiftRow(int[] a, int d) {
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;
tmpArray[j] = a[index];
}
for (int j = 0; j < 4; j++) {
a[i * 4 + j] = tmpArray[j];
}
}
}
private void keyAddition(int[] result, int startIndex) {
for (int i = 0; i < 16; i++) {
result[i] ^= expandedKey[i + startIndex];
}
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 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.crypto;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
*
* Constant
*
* @author Konstantin Polihronov - Initial contribution
*
* @see <a href=https://www.samiam.org/key-schedule.html>Sam Trendholme's page about AES</a>
*/
@NonNullByDefault
public class EncryptionHandlerConstants {
protected static final int[] S = { 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202,
130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247,
204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178,
117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177,
91, 106, 203, 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159,
168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151,
68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213,
78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75,
189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17,
105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45,
15, 176, 84, 187, 22 };
public static final int[] Si = { 82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, 124, 227,
57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, 84, 123, 148, 50, 166, 194, 35, 61, 238,
76, 149, 11, 66, 250, 195, 78, 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, 114,
248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, 108, 112, 72, 80, 253, 237, 185,
218, 94, 21, 70, 87, 167, 141, 157, 132, 144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69,
6, 208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, 58, 145, 17, 65, 79, 103, 220, 234,
151, 242, 207, 206, 240, 180, 230, 115, 150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117,
223, 110, 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, 252, 86, 62, 75, 198, 210,
121, 32, 154, 219, 192, 254, 120, 205, 90, 244, 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128,
236, 95, 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, 160, 224, 59, 77, 174,
42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85,
33, 12, 125 };
protected static final int[][][] SHIFTS = { { { 0, 0 }, { 1, 3 }, { 2, 2 }, { 3, 1 } },
{ { 0, 0 }, { 1, 5 }, { 2, 4 }, { 3, 3 } }, { { 0, 0 }, { 1, 7 }, { 3, 5 }, { 4, 4 } } };
public static final int[] XTIMETABLE = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16,
0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a,
0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82,
0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2, 0xa4, 0xa6,
0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca,
0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee,
0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09,
0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d,
0x23, 0x21, 0x27, 0x25, 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41,
0x47, 0x45, 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, 0xbb, 0xb9,
0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 0xdb, 0xd9, 0xdf, 0xdd,
0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1,
0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5 };
public static final int[] RCON = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab,
0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 };
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 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;
}
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 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.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EpromRequestPayload} Object representing payload of IP packet which retrieves data from Paradox EPROM
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class EpromRequestPayload extends MemoryRequestPayload implements IPayload {
private final Logger logger = LoggerFactory.getLogger(EpromRequestPayload.class);
public EpromRequestPayload(int address, byte bytesToRead) throws ParadoxException {
super(address, bytesToRead);
}
@Override
protected byte calculateControlByte() {
int address = getAddress();
logTraceHexFormatted("Address: {}", address);
byte controlByte = 0x00;
byte[] shortToByteArray = ParadoxUtil.intToByteArray(address);
if (shortToByteArray.length > 2) {
byte bit16 = ParadoxUtil.getBit(address, 16);
controlByte |= bit16 << 0;
byte bit17 = ParadoxUtil.getBit(address, 17);
controlByte |= bit17 << 1;
}
logger.trace("ControlByte value: {}", controlByte);
return controlByte;
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 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;
/**
* {@link HeaderCommand}
* From Jean's(Jean_Henning from community) excel sheet:
* 0x00: Serial/pass through command any other: IP module command
* 0xF0: Connect to IP module
* 0xF2: (unknown, part of login sequence)
* 0xF3: (unknown, part of login sequence)
* 0xF4: (unknown)
* 0xF8: (unknown, occurs after serial connection is initiated with the panel)
* 0xFB: Multicommand
* 0xFF: Disconnect from IP module (byte 00 in the response payload MUST be 01 to indicate a successful disconnect)
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public enum HeaderCommand {
SERIAL((byte) 0x00),
CONNECT_TO_IP_MODULE((byte) 0xF0),
LOGIN_COMMAND1((byte) 0xF2),
LOGIN_COMMAND2((byte) 0xF3),
UNKNOWN1((byte) 0xF4),
SERIAL_CONNECTION_INITIATED((byte) 0xF8),
MULTI_COMMAND((byte) 0xFB),
DISCONNECT((byte) 0xFF);
private byte value;
HeaderCommand(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 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;
/**
* From Jean's excel:
* 0x03: IP Request
* 0x01: IP Response
* 0x04: Serial/pass through command request
* 0x02: Serial/pass through command response
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public enum HeaderMessageType {
IP_REQUEST((byte) 0x03),
IP_RESPONSE((byte) 0x01),
SERIAL_PASSTHRU_REQUEST((byte) 0x04),
SERIAL_PASSTHRU_RESPONSE((byte) 0x02);
private byte value;
HeaderMessageType(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2020 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.messages.ParadoxIPPacket.PacketHeader;
/**
* Interface representing the functionality of IP packet.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IPPacket extends IPayload {
PacketHeader getHeader();
byte[] getPayload();
void encrypt();
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 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;
/**
* Interface representing what we need to add IPPacketPayload.
* Not sure if we need it as it needs only getBytes() method so far.
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IPayload {
byte[] getBytes();
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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;
/**
* Constants representing packet headers / messages which are easier written as static final byte arrays
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class IpMessagesConstants {
public static final byte[] UNKNOWN_IP150_REQUEST_MESSAGE01 = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00,
0x59 };
public static final byte[] EPROM_REQUEST_HEADER = { (byte) 0xAA, 0x08, 0x00, 0x04, 0x08, 0x00, 0x00, 0x14,
(byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE };
public static final byte[] LOGOUT_MESAGE_BYTES = new byte[] { 0x00, 0x07, 0x05, 0x00, 0x00, 0x00, 0x00 };
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2010-2020 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 java.nio.ByteOrder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MemoryRequestPayload} Abstract class which contains common logic used in RAM and EPROM payload generation
* classes.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public abstract class MemoryRequestPayload implements IPayload {
private static final int BUFFER_LENGTH = 8;
private static final short MESSAGE_START = (short) ((0x50 << 8) | 0x08);
private final Logger logger = LoggerFactory.getLogger(MemoryRequestPayload.class);
private final int address;
private final byte bytesToRead;
public MemoryRequestPayload(int address, byte bytesToRead) throws ParadoxException {
if (bytesToRead < 1 || bytesToRead > 64) {
throw new ParadoxException("Invalid bytes to read. Valid values are 1 to 64.");
}
this.address = address;
this.bytesToRead = bytesToRead;
logTraceHexFormatted("MessageStart: {}", MESSAGE_START);
}
protected abstract byte calculateControlByte();
@Override
public byte[] getBytes() {
byte[] bufferArray = new byte[BUFFER_LENGTH];
ByteBuffer buffer = ByteBuffer.wrap(bufferArray);
buffer.order(ByteOrder.BIG_ENDIAN).putShort(MESSAGE_START);
buffer.put(calculateControlByte());
buffer.put((byte) 0x00);
buffer.order(ByteOrder.BIG_ENDIAN).putShort((short) address);
buffer.put(bytesToRead);
buffer.put((byte) 0x00);
return bufferArray;
}
protected int getAddress() {
return address;
}
protected void logTraceHexFormatted(String text, int address) {
logTraceOptional(text, "0x%02X,\t", address);
}
private void logTraceOptional(String text, String format, int address) {
if (logger.isTraceEnabled()) {
logger.trace("Address: {}", String.format(format, address));
}
}
}

View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) 2010-2020 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 java.nio.ByteOrder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
/**
* The {@link ParadoxIPPacket} This class is object representing a full IP request packet. Header and payload together.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class ParadoxIPPacket implements IPPacket {
public static final byte[] EMPTY_PAYLOAD = new byte[0];
private PacketHeader header;
private byte[] payload;
public ParadoxIPPacket(byte[] bytes) {
this(bytes, true);
}
@SuppressWarnings("null")
public ParadoxIPPacket(byte[] payload, boolean isChecksumRequired) {
this.payload = payload != null ? payload : new byte[0];
if (isChecksumRequired) {
payload[payload.length - 1] = ParadoxUtil.calculateChecksum(payload);
}
short payloadLength = (short) (payload != null ? payload.length : 0);
header = new PacketHeader(payloadLength);
}
@Override
public byte[] getBytes() {
final byte[] headerBytes = header.getBytes();
int bufferLength = headerBytes.length + payload.length;
byte[] bufferArray = new byte[bufferLength];
ByteBuffer buf = ByteBuffer.wrap(bufferArray);
buf.put(headerBytes);
buf.put(payload);
return bufferArray;
}
public ParadoxIPPacket setCommand(HeaderCommand command) {
header.command = command.getValue();
return this;
}
public ParadoxIPPacket setMessageType(HeaderMessageType messageType) {
header.messageType = messageType.getValue();
return this;
}
public ParadoxIPPacket setUnknown0(byte unknownByteValue) {
header.unknown0 = unknownByteValue;
return this;
}
@Override
public PacketHeader getHeader() {
return header;
}
@Override
public byte[] getPayload() {
return payload;
}
@Override
public void encrypt() {
EncryptionHandler encryptionHandler = EncryptionHandler.getInstance();
payload = encryptionHandler.encrypt(payload);
header.encryption = 0x09;
}
@Override
public String toString() {
return "ParadoxIPPacket [" + ParadoxUtil.byteArrayToString(getBytes()) + "]";
}
public class PacketHeader {
public PacketHeader(short payloadLength) {
this.payloadLength = payloadLength;
}
private static final int BYTES_LENGTH = 9;
/**
* Start of header - always 0xAA
*/
private byte startOfHeader = (byte) 0xAA;
/**
* Payload length - 2 bytes (LL HH)
*/
private short payloadLength = 0;
/**
* "Message Type: 0x01: IP responses 0x02: Serial/pass through cmd response
* 0x03: IP requests 0x04: Serial/pass through cmd requests"
*/
private byte messageType = 0x03;
/**
* "IP Encryption Disabled=0x08, Enabled=0x09"
*/
private byte encryption = 0x08;
private byte command = 0;
private byte subCommand = 0;
private byte unknown0 = 0x00;
private byte unknown1 = 0x01;
public byte[] getBytes() {
byte[] bufferArray = new byte[BYTES_LENGTH];
ByteBuffer buf = ByteBuffer.wrap(bufferArray);
buf.put(startOfHeader);
buf.order(ByteOrder.LITTLE_ENDIAN).putShort(payloadLength);
buf.put(messageType);
buf.put(encryption);
buf.put(command);
buf.put(subCommand);
buf.put(unknown0);
buf.put(unknown1);
return ParadoxUtil.extendArray(bufferArray, 16);
}
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 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 final static 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;
}
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 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.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
/**
* The {@link RamRequestPayload} Object representing payload of IP packet which retrieves data from Paradox RAM
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class RamRequestPayload extends MemoryRequestPayload implements IPayload {
private static final byte CONTROL_BYTE = ParadoxUtil.setBit((byte) 0, 7, 1);
public RamRequestPayload(int address, byte bytesToRead) throws ParadoxException {
super(address, bytesToRead);
}
@Override
protected byte calculateControlByte() {
return CONTROL_BYTE;
}
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2020 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.discovery;
import static org.openhab.binding.paradoxalarm.internal.handlers.ParadoxAlarmBindingConstants.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openhab.binding.paradoxalarm.internal.communication.IParadoxCommunicator;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.handlers.ParadoxIP150BridgeHandler;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxInformation;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.openhab.binding.paradoxalarm.internal.model.Partition;
import org.openhab.binding.paradoxalarm.internal.model.Zone;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxDiscoveryService} is responsible for discovery of partitions, zones and the panel once bridge is
* created.
*
* @author Konstnatin Polihronov - Initial Contribution
*/
public class ParadoxDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(ParadoxDiscoveryService.class);
private ParadoxIP150BridgeHandler ip150BridgeHandler;
public ParadoxDiscoveryService(ParadoxIP150BridgeHandler ip150BridgeHandler) {
super(SUPPORTED_THING_TYPES_UIDS, 15, false);
this.ip150BridgeHandler = ip150BridgeHandler;
}
@Override
protected void startScan() {
IParadoxCommunicator communicator = ip150BridgeHandler.getCommunicator();
if (communicator != null && communicator.isOnline()) {
ParadoxPanel panel = ParadoxPanel.getInstance();
discoverPanel(panel.getPanelInformation());
discoverPartitions(panel.getPartitions());
discoverZones(panel.getZones());
} else {
logger.debug("Communicator null or not online. Trace:", new ParadoxRuntimeException());
}
}
private void discoverPanel(ParadoxInformation panelInformation) {
if (panelInformation != null) {
Map<String, Object> properties = new HashMap<>();
properties.put(PANEL_TYPE_PROPERTY_NAME, panelInformation.getPanelType().name());
properties.put(PANEL_SERIAL_NUMBER_PROPERTY_NAME, panelInformation.getSerialNumber());
properties.put(PANEL_APPLICATION_VERSION_PROPERTY_NAME, panelInformation.getApplicationVersion());
properties.put(PANEL_BOOTLOADER_VERSION_PROPERTY_NAME, panelInformation.getBootLoaderVersion());
properties.put(PANEL_HARDWARE_VERSION_PROPERTY_NAME, panelInformation.getHardwareVersion());
ThingUID bridgeUid = ip150BridgeHandler.getThing().getUID();
ThingUID thingUID = new ThingUID(PANEL_THING_TYPE_UID, bridgeUid, PARADOX_PANEL_THING_TYPE_ID);
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withBridge(bridgeUid).withLabel("Paradox panel - " + panelInformation.getPanelType()).build();
logger.debug("Panel DiscoveryResult={}", result);
thingDiscovered(result);
}
}
private void discoverPartitions(List<Partition> partitions) {
partitions.stream().forEach(partition -> {
String thingId = PARTITION_THING_TYPE_ID + partition.getId();
String label = partition.getLabel();
ThingUID bridgeUid = ip150BridgeHandler.getThing().getUID();
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();
logger.debug("Partition DiscoveryResult={}", result);
thingDiscovered(result);
});
}
private void discoverZones(List<Zone> zones) {
zones.stream().forEach(zone -> {
String thingId = zone.getLabel().replaceAll(" ", "_");
String label = zone.getLabel();
ThingUID bridgeUid = ip150BridgeHandler.getThing().getUID();
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();
logger.debug("Zone DiscoveryResult={}", result);
thingDiscovered(result);
});
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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.exceptions;
/**
* The {@link ParadoxException} Wrapper of Exception class.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxException extends Exception {
private static final long serialVersionUID = -5771699322577106346L;
public ParadoxException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ParadoxException(String message, Throwable cause) {
super(message, cause);
}
public ParadoxException(String message) {
super(message);
}
public ParadoxException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 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.exceptions;
/**
* The {@link ParadoxRuntimeException} Used for Paradox binding specific runtime exceptions.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxRuntimeException extends RuntimeException {
private static final long serialVersionUID = -4656474289606169766L;
public ParadoxRuntimeException(String message) {
super(message);
}
public ParadoxRuntimeException() {
super();
}
public ParadoxRuntimeException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ParadoxRuntimeException(String message, Throwable cause) {
super(message, cause);
}
public ParadoxRuntimeException(Throwable cause) {
this("This is a Paradox Binding wrapper of RuntimeException. For detailed error message, see the original exception. Short message: "
+ cause.getMessage(), cause);
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 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.handlers;
/**
* The {@link Commandable} Interface for entities that can handle string commands
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface Commandable {
void handleCommand(String command);
}

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EntityBaseHandler} abstract handler class that contains common logic for entities.
*
* @author Konstantin Polihronov - Initial contribution
*/
public abstract class EntityBaseHandler extends BaseThingHandler {
private static final long INITIAL_DELAY_SECONDS = 15;
private static final int MAX_WAIT_TIME_MILLIS = 60000;
private long timeStamp;
private final Logger logger = LoggerFactory.getLogger(EntityBaseHandler.class);
protected EntityConfiguration config;
private ScheduledFuture<?> delayedSchedule;
public EntityBaseHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.trace("Start initializing. {}", thing.getUID());
updateStatus(ThingStatus.UNKNOWN);
config = getConfigAs(EntityConfiguration.class);
timeStamp = System.currentTimeMillis();
delayedSchedule = scheduler.schedule(this::initializeDelayed, INITIAL_DELAY_SECONDS, TimeUnit.SECONDS);
}
private void initializeDelayed() {
logger.debug("Start initializeDelayed() in {}", getThing().getUID());
ParadoxPanel panel = ParadoxPanel.getInstance();
// Asynchronous update not yet done
if (panel.getPanelInformation() == null) {
// Retry until reach MAX_WAIT_TIME
if (System.currentTimeMillis() - timeStamp <= MAX_WAIT_TIME_MILLIS) {
logger.debug("Panel information is null. Scheduling initializeDelayed() to be executed again in {} sec",
INITIAL_DELAY_SECONDS);
delayedSchedule = scheduler.schedule(this::initializeDelayed, INITIAL_DELAY_SECONDS, TimeUnit.SECONDS);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"Panel is not updating the information in " + MAX_WAIT_TIME_MILLIS
+ " ms. Giving up. Cannot update entity=" + this + ".");
}
// Asynchronous update done but panel is not supported
} else if (!panel.isPanelSupported()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Panel is not supported. Cannot update entity=" + this + ".");
// All OK
} else {
updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (ThingStatus.OFFLINE == getThing().getStatus()) {
logger.debug("Received {} command but {} is OFFLINE with the following detailed status {}", command,
getThing().getUID(), getThing().getStatusInfo());
return;
}
if (command instanceof RefreshType) {
updateEntity();
}
}
@Override
public void dispose() {
if (delayedSchedule != null) {
boolean cancelingResult = delayedSchedule.cancel(true);
String cancelingSuccessful = cancelingResult ? "successful" : "failed";
logger.debug("Canceling schedule of {} is {}", delayedSchedule, cancelingSuccessful);
}
}
protected abstract void updateEntity();
protected int calculateEntityIndex() {
return Math.max(0, config.getId() - 1);
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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.handlers;
/**
* The {@link EntityConfiguration} Common configuration class used by all entities at the moment.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class EntityConfiguration {
private int id;
private boolean disarmEnabled;
public int getId() {
return id;
}
public boolean isDisarmEnabled() {
return disarmEnabled;
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import java.time.LocalDateTime;
/**
* The {@link PanelConfiguration} Paradox Panel handler configuration.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class PanelConfiguration extends EntityConfiguration {
private double vdcVoltage;
private double dcVoltage;
private double batteryVoltage;
private LocalDateTime panelTime;
public double getVdcVoltage() {
return vdcVoltage;
}
public void setAcVoltage(double vdcVoltage) {
this.vdcVoltage = vdcVoltage;
}
public double getDcVoltage() {
return dcVoltage;
}
public void setDcVoltage(double dcVoltage) {
this.dcVoltage = dcVoltage;
}
public double getBatteryVoltage() {
return batteryVoltage;
}
public void setBatteryVoltage(double batteryVoltage) {
this.batteryVoltage = batteryVoltage;
}
public LocalDateTime getPanelTime() {
return panelTime;
}
public void setPanelTime(LocalDateTime panelTime) {
this.panelTime = panelTime;
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ParadoxAlarmBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class ParadoxAlarmBindingConstants {
public static final String BINDING_ID = "paradoxalarm";
public static final String PARADOX_COMMUNICATOR_THING_TYPE_ID = "ip150";
public static final String PARADOX_PANEL_THING_TYPE_ID = "panel";
public static final String PARTITION_THING_TYPE_ID = "partition";
public static final String ZONE_THING_TYPE_ID = "zone";
// List of all Thing Type UIDs
public static final ThingTypeUID COMMUNICATOR_THING_TYPE_UID = new ThingTypeUID(BINDING_ID,
PARADOX_COMMUNICATOR_THING_TYPE_ID);
public static final ThingTypeUID PANEL_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, PARADOX_PANEL_THING_TYPE_ID);
public static final ThingTypeUID PARTITION_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, PARTITION_THING_TYPE_ID);
public static final ThingTypeUID ZONE_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, ZONE_THING_TYPE_ID);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(COMMUNICATOR_THING_TYPE_UID, PANEL_THING_TYPE_UID, PARTITION_THING_TYPE_UID, ZONE_THING_TYPE_UID)
.collect(Collectors.toSet()));
// List of all Channel UIDs
public static final String IP150_COMMUNICATION_COMMAND_CHANNEL_UID = "communicationCommand";
public static final String IP150_COMMUNICATION_STATE_CHANNEL_UID = "communicationState";
public static final String PANEL_STATE_CHANNEL_UID = "state";
public static final String PANEL_SERIAL_NUMBER_PROPERTY_NAME = "serialNumber";
public static final String PANEL_TYPE_PROPERTY_NAME = "panelType";
public static final String PANEL_HARDWARE_VERSION_PROPERTY_NAME = "hardwareVersion";
public static final String PANEL_APPLICATION_VERSION_PROPERTY_NAME = "applicationVersion";
public static final String PANEL_BOOTLOADER_VERSION_PROPERTY_NAME = "bootloaderVersion";
public static final String PANEL_TIME = "panelTime";
public static final String PANEL_INPUT_VOLTAGE = "inputVoltage";
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_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_ADDITIONAL_STATES_CHANNEL_UID = "additionalStates";
public static final String PARTITION_READY_TO_ARM_CHANNEL_UID = "readyToArm";
public static final String PARTITION_IN_EXIT_DELAY_CHANNEL_UID = "inExitDelay";
public static final String PARTITION_IN_ENTRY_DELAY_CHANNEL_UID = "inEntryDelay";
public static final String PARTITION_IN_TROUBLE_CHANNEL_UID = "inTrouble";
public static final String PARTITION_ALARM_IN_MEMORY_CHANNEL_UID = "alarmInMemory";
public static final String PARTITION_ZONE_BYPASS_CHANNEL_UID = "zoneBypass";
public static final String PARTITION_ZONE_IN_TAMPER_CHANNEL_UID = "zoneInTamperTrouble";
public static final String PARTITION_ZONE_IN_LOW_BATTERY_CHANNEL_UID = "zoneInLowBatteryTrouble";
public static final String PARTITION_ZONE_IN_FIRE_LOOP_CHANNEL_UID = "zoneInFireLoopTrouble";
public static final String PARTITION_ZONE_IN_SUPERVISION_TROUBLE_CHANNEL_UID = "zoneInSupervisionTrouble";
public static final String PARTITION_STAY_INSTANT_READY_CHANNEL_UID = "stayInstantReady";
public static final String PARTITION_FORCE_READY_CHANNEL_UID = "forceReady";
public static final String PARTITION_BYPASS_READY_CHANNEL_UID = "bypassReady";
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_OPENED_CHANNEL_UID = "opened";
public static final String ZONE_TAMPERED_CHANNEL_UID = "tampered";
public static final String ZONE_LOW_BATTERY_CHANNEL_UID = "lowBattery";
// Misc constants
public static final StringType STATE_OFFLINE = new StringType("Offline");
public static final StringType STATE_ONLINE = new StringType("Online");
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import static org.openhab.binding.paradoxalarm.internal.handlers.ParadoxAlarmBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.paradoxalarm.internal.discovery.ParadoxDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxAlarmHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.paradoxalarm", service = ThingHandlerFactory.class)
public class ParadoxAlarmHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(ParadoxAlarmHandlerFactory.class);
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
ThingUID thingUID = thing.getUID();
if (COMMUNICATOR_THING_TYPE_UID.equals(thingTypeUID)) {
logger.debug("createHandler(): ThingHandler created for {}", thingUID);
ParadoxIP150BridgeHandler paradoxIP150BridgeHandler = new ParadoxIP150BridgeHandler((Bridge) thing);
registerDiscoveryService(paradoxIP150BridgeHandler);
return paradoxIP150BridgeHandler;
} else if (PANEL_THING_TYPE_UID.equals(thingTypeUID)) {
logger.debug("createHandler(): ThingHandler created for {}", thingUID);
return new ParadoxPanelHandler(thing);
} else if (PARTITION_THING_TYPE_UID.equals(thingTypeUID)) {
logger.debug("createHandler(): ThingHandler created for {}", thingUID);
return new ParadoxPartitionHandler(thing);
} else if (ZONE_THING_TYPE_UID.equals(thingTypeUID)) {
logger.debug("createHandler(): ThingHandler created for {}", thingUID);
return new ParadoxZoneHandler(thing);
} else {
logger.warn("Handler implementation not found for Thing: {}", thingUID);
}
return null;
}
private void registerDiscoveryService(ParadoxIP150BridgeHandler paradoxIP150BridgeHandler) {
ParadoxDiscoveryService discoveryService = new ParadoxDiscoveryService(paradoxIP150BridgeHandler);
ServiceRegistration<?> serviceRegistration = bundleContext.registerService(DiscoveryService.class.getName(),
discoveryService, new Hashtable<>());
this.discoveryServiceRegs.put(paradoxIP150BridgeHandler.getThing().getUID(), serviceRegistration);
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof ParadoxIP150BridgeHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
serviceReg.unregister();
}
}
}
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2010-2020 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.handlers;
/**
* The {@link ParadoxIP150BridgeConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxIP150BridgeConfiguration {
private int refresh;
private String ip150Password;
private String pcPassword;
private String ipAddress;
private int port;
private String panelType;
private int reconnectWaitTime;
private Integer maxZones;
private Integer maxPartitions;
private boolean encrypt;
public int getRefresh() {
return refresh;
}
public void setRefresh(int refresh) {
this.refresh = refresh;
}
public String getIp150Password() {
return ip150Password;
}
public void setIp150Password(String ip150Password) {
this.ip150Password = ip150Password;
}
public String getPcPassword() {
return pcPassword;
}
public void setPcPassword(String pcPassword) {
this.pcPassword = pcPassword;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPanelType() {
return panelType;
}
public void setPanelType(String panelType) {
this.panelType = panelType;
}
public int getReconnectWaitTime() {
return reconnectWaitTime;
}
public void setReconnectWaitTime(int reconnectWaitTime) {
this.reconnectWaitTime = reconnectWaitTime;
}
public Integer getMaxZones() {
return maxZones;
}
public void setMaxZones(Integer maxZones) {
this.maxZones = maxZones;
}
public Integer getMaxPartitions() {
return maxPartitions;
}
public void setMaxPartitions(Integer maxPartitions) {
this.maxPartitions = maxPartitions;
}
public boolean isEncrypt() {
return encrypt;
}
public void setEncrypt(boolean encrypt) {
this.encrypt = encrypt;
}
}

View File

@@ -0,0 +1,343 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.paradoxalarm.internal.communication.CommunicationState;
import org.openhab.binding.paradoxalarm.internal.communication.GenericCommunicator;
import org.openhab.binding.paradoxalarm.internal.communication.ICommunicatorBuilder;
import org.openhab.binding.paradoxalarm.internal.communication.IDataUpdateListener;
import org.openhab.binding.paradoxalarm.internal.communication.IP150Command;
import org.openhab.binding.paradoxalarm.internal.communication.IParadoxCommunicator;
import org.openhab.binding.paradoxalarm.internal.communication.IParadoxInitialLoginCommunicator;
import org.openhab.binding.paradoxalarm.internal.communication.ISocketTimeOutListener;
import org.openhab.binding.paradoxalarm.internal.communication.ParadoxBuilderFactory;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.model.PanelType;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxInformationConstants;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxIP150BridgeHandler} This is the handler that takes care of communication to/from Paradox alarm
* system.
*
* @author Konstantin Polihronov - Initial contribution
*/
@SuppressWarnings("null")
@NonNullByDefault({})
public class ParadoxIP150BridgeHandler extends BaseBridgeHandler
implements IDataUpdateListener, ISocketTimeOutListener {
private static final String RESET_COMMAND = "RESET";
private static final int ONLINE_WAIT_TRESHOLD_MILLIS = 10000;
private static final int INITIAL_SCHEDULE_DELAY_SECONDS = 5;
private final Logger logger = LoggerFactory.getLogger(ParadoxIP150BridgeHandler.class);
private IParadoxCommunicator communicator;
private static ParadoxIP150BridgeConfiguration config;
private @Nullable ScheduledFuture<?> refreshCacheUpdateSchedule;
private long timeStamp = 0;
private ScheduledFuture<?> resetScheduleFuture;
public ParadoxIP150BridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
logger.debug("Start initialize()...");
updateStatus(ThingStatus.UNKNOWN);
logger.debug("Starting creation of communicator.");
config = getConfigAs(ParadoxIP150BridgeConfiguration.class);
scheduler.execute(this::initializeCommunicator);
logger.debug("Finished initialize().");
}
private synchronized void initializeCommunicator() {
try {
String ipAddress = config.getIpAddress();
int tcpPort = config.getPort();
String ip150Password = config.getIp150Password();
String pcPassword = config.getPcPassword();
boolean useEncryption = config.isEncrypt();
// Early exit. If panel type is configured and known to the binding skip auto-detection. Saves one full
// initial login process to detect the panel type.
PanelType configuredPanelType = PanelType.from(config.getPanelType());
if (configuredPanelType != PanelType.UNKNOWN) {
logger.debug("Configuration file has pannelType={}. Skipping Phase1 (Autodiscovery)",
configuredPanelType);
scheduler.schedule(() -> createDiscoveredCommunicatorJob(configuredPanelType), 3, TimeUnit.SECONDS);
return;
}
logger.debug("Phase1 - Auto discover communicator");
IParadoxInitialLoginCommunicator initialCommunicator = new GenericCommunicator(ipAddress, tcpPort,
ip150Password, pcPassword, scheduler, useEncryption);
initialCommunicator.startLoginSequence();
timeStamp = System.currentTimeMillis();
scheduler.schedule(() -> doPostOnlineTask(initialCommunicator), 500, TimeUnit.MILLISECONDS);
} catch (UnknownHostException e) {
logger.warn("Error while starting socket communication. {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Unknown host. Probably misconfiguration or DNS issue.");
throw new ParadoxRuntimeException(e);
} catch (IOException e) {
logger.warn("Error while starting socket communication. {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error while starting socket communication.");
throw new ParadoxRuntimeException(e);
}
}
private synchronized void doPostOnlineTask(IParadoxInitialLoginCommunicator initialCommunicator) {
if (!initialCommunicator.isOnline()) {
if (System.currentTimeMillis() - timeStamp <= ONLINE_WAIT_TRESHOLD_MILLIS) {
scheduler.schedule(() -> doPostOnlineTask(initialCommunicator), 500, TimeUnit.MILLISECONDS);
logger.debug("Communicator not yet online. Rescheduling...");
} else {
logger.warn(
"Initial communicator not coming up online for {} seconds. Probably there is something wrong with communication.",
ONLINE_WAIT_TRESHOLD_MILLIS);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error while starting socket communication.");
}
return;
}
byte[] panelInfoBytes = initialCommunicator.getPanelInfoBytes();
PanelType panelType = ParadoxInformationConstants.parsePanelType(panelInfoBytes);
logger.info("Found {} panel type.", panelType);
CommunicationState.logout(initialCommunicator);
// Wait 3 seconds before we create the discovered communicator so we ensure that the socket is closed
// successfully from both ends
scheduler.schedule(() -> createDiscoveredCommunicatorJob(panelType), 3, TimeUnit.SECONDS);
}
protected void createDiscoveredCommunicatorJob(PanelType panelType) {
// If not detected properly, use the value from config
if (panelType == PanelType.UNKNOWN) {
panelType = PanelType.from(config.getPanelType());
}
logger.debug("Phase2 - Creating communicator for panel {}", panelType);
ICommunicatorBuilder builder = new ParadoxBuilderFactory().createBuilder(panelType);
communicator = builder.withIp150Password(config.getIp150Password()).withPcPassword(config.getPcPassword())
.withIpAddress(config.getIpAddress()).withTcpPort(config.getPort())
.withMaxPartitions(config.getMaxPartitions()).withMaxZones(config.getMaxZones())
.withScheduler(scheduler).withEncryption(config.isEncrypt()).build();
ParadoxPanel panel = ParadoxPanel.getInstance();
panel.setCommunicator(communicator);
Collection<IDataUpdateListener> listeners = Arrays.asList(panel, this);
communicator.setListeners(listeners);
communicator.setStoListener(this);
logger.debug("Listeners set to: {}", listeners);
communicator.startLoginSequence();
timeStamp = System.currentTimeMillis();
doPostOnlineFinalCommunicatorJob();
}
private void doPostOnlineFinalCommunicatorJob() {
if (!communicator.isOnline()) {
if (System.currentTimeMillis() - timeStamp <= ONLINE_WAIT_TRESHOLD_MILLIS) {
scheduler.schedule(this::doPostOnlineFinalCommunicatorJob, 1, TimeUnit.SECONDS);
logger.debug("Communicator not yet online. Rescheduling...");
return;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error while starting socket communication.");
ParadoxRuntimeException exception = new ParadoxRuntimeException(
"Communicator didn't go online in defined treshold time. " + ONLINE_WAIT_TRESHOLD_MILLIS
+ "sec. You can still try to reset the bridge with command RESET.");
logger.debug("Problem with communication", exception);
throw exception;
}
}
logger.debug("Communicator created successfully.");
updateStatus(ThingStatus.ONLINE);
}
@Override
public void dispose() {
cancelSchedule(refreshCacheUpdateSchedule);
CommunicationState.logout(communicator);
super.dispose();
}
private void scheduleRefresh() {
logger.debug("Scheduling cache update. Refresh interval: {}s. Starts after: {}s.", config.getRefresh(),
INITIAL_SCHEDULE_DELAY_SECONDS);
refreshCacheUpdateSchedule = scheduler.scheduleWithFixedDelay(communicator::refreshMemoryMap,
INITIAL_SCHEDULE_DELAY_SECONDS, config.getRefresh(), TimeUnit.SECONDS);
}
private void cancelSchedule(@Nullable ScheduledFuture<?> schedule) {
if (schedule != null && !schedule.isCancelled()) {
boolean cancelingResult = schedule.cancel(true);
String cancelingSuccessful = cancelingResult ? "successful" : "failed";
logger.debug("Canceling schedule of {} is {}", schedule, cancelingSuccessful);
}
}
@Override
public void update() {
updateStatusChannel();
Bridge bridge = getThing();
if (bridge.getStatus() == ThingStatus.OFFLINE) {
if (communicator.isOnline()) {
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Bridge {} triggered update but is OFFLINE", bridge.getUID());
return;
}
}
List<Thing> things = bridge.getThings();
for (Thing thing : things) {
ThingHandler handler = thing.getHandler();
Channel bridgeChannel = bridge
.getChannel(ParadoxAlarmBindingConstants.IP150_COMMUNICATION_COMMAND_CHANNEL_UID);
if (handler != null && bridgeChannel != null) {
handler.handleCommand(bridgeChannel.getUID(), RefreshType.REFRESH);
}
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Received command {}", command);
if (ThingStatus.OFFLINE == getThing().getStatus() && command instanceof RefreshType) {
logger.debug("Bridge {} is OFFLINE. Cannot handle refresh commands.", getThing().getUID());
return;
}
if (ParadoxAlarmBindingConstants.IP150_COMMUNICATION_COMMAND_CHANNEL_UID.equals(channelUID.getId())) {
logger.debug("Command is instance of {}", command.getClass());
if (command instanceof StringType) {
String commandAsString = command.toFullString();
if (commandAsString.equals(RESET_COMMAND)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"Bringing bridge offline due to reinitialization of communicator.");
resetCommunicator();
} else {
communicator.executeCommand(commandAsString);
}
}
}
if (communicator != null && communicator.isOnline()) {
logger.debug("Communicator is online");
communicator.refreshMemoryMap();
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Communicator is null or not online");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Device is offline");
}
}
private void resetCommunicator() {
updateStatus(ThingStatus.OFFLINE);
synchronized (communicator) {
if (communicator != null) {
CommunicationState.logout(communicator);
}
initializeCommunicator();
}
resetScheduleFuture = null;
}
@Override
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
updateStatusChannel();
super.updateStatus(status, statusDetail, description);
if (status.equals(ThingStatus.ONLINE)) {
if (refreshCacheUpdateSchedule == null || refreshCacheUpdateSchedule.isDone()) {
scheduleRefresh();
}
} else {
cancelSchedule(refreshCacheUpdateSchedule);
}
}
private void updateStatusChannel() {
StringType communicatorStatus = communicator != null && communicator.isOnline()
? ParadoxAlarmBindingConstants.STATE_ONLINE
: ParadoxAlarmBindingConstants.STATE_OFFLINE;
updateState(ParadoxAlarmBindingConstants.IP150_COMMUNICATION_STATE_CHANNEL_UID, communicatorStatus);
}
public IParadoxCommunicator getCommunicator() {
return communicator;
}
@Override
public void onSocketTimeOutOccurred(IOException exception) {
logger.warn("TIMEOUT! {} received message for socket timeout. ", this, exception);
if (resetScheduleFuture == null) {
communicator.executeCommand(IP150Command.LOGOUT.name());
logger.warn("Reset task is null. Will schedule new task to reset communicator in {} seconds.",
config.getReconnectWaitTime());
resetScheduleFuture = scheduler.schedule(this::resetCommunicator, config.getReconnectWaitTime(),
TimeUnit.SECONDS);
}
}
@Override
public String toString() {
return "ParadoxIP150BridgeHandler [" + getThing().getUID().getAsString() + "]";
}
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import static org.openhab.binding.paradoxalarm.internal.handlers.ParadoxAlarmBindingConstants.*;
import javax.measure.quantity.ElectricPotential;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxInformation;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SmartHomeUnits;
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.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class ParadoxPanelHandler extends EntityBaseHandler {
private final Logger logger = LoggerFactory.getLogger(ParadoxPanelHandler.class);
public ParadoxPanelHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
config = getConfigAs(PanelConfiguration.class);
}
@Override
protected void updateEntity() {
ParadoxPanel panel = ParadoxPanel.getInstance();
StringType panelState = panel.isOnline() ? STATE_ONLINE : STATE_OFFLINE;
updateState(PANEL_STATE_CHANNEL_UID, panelState);
ParadoxInformation panelInformation = panel.getPanelInformation();
if (panelInformation != null) {
updateProperty(PANEL_SERIAL_NUMBER_PROPERTY_NAME, panelInformation.getSerialNumber());
updateProperty(PANEL_TYPE_PROPERTY_NAME, panelInformation.getPanelType().name());
updateProperty(PANEL_HARDWARE_VERSION_PROPERTY_NAME, panelInformation.getHardwareVersion().toString());
updateProperty(PANEL_APPLICATION_VERSION_PROPERTY_NAME,
panelInformation.getApplicationVersion().toString());
updateProperty(PANEL_BOOTLOADER_VERSION_PROPERTY_NAME, panelInformation.getBootLoaderVersion().toString());
updateState(PANEL_TIME, new DateTimeType(panel.getPanelTime()));
updateState(PANEL_INPUT_VOLTAGE,
new QuantityType<ElectricPotential>(panel.getVdcLevel(), SmartHomeUnits.VOLT));
updateState(PANEL_BOARD_VOLTAGE,
new QuantityType<ElectricPotential>(panel.getDcLevel(), SmartHomeUnits.VOLT));
updateState(PANEL_BATTERY_VOLTAGE,
new QuantityType<ElectricPotential>(panel.getBatteryLevel(), SmartHomeUnits.VOLT));
}
}
}

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import static org.openhab.binding.paradoxalarm.internal.handlers.ParadoxAlarmBindingConstants.*;
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.model.ParadoxPanel;
import org.openhab.binding.paradoxalarm.internal.model.Partition;
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.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxPartitionHandler} Handler that updates states of paradox partitions from the cache.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxPartitionHandler extends EntityBaseHandler {
private final Logger logger = LoggerFactory.getLogger(ParadoxPartitionHandler.class);
public ParadoxPartitionHandler(@NonNull Thing thing) {
super(thing);
}
@Override
protected void updateEntity() {
Partition partition = getPartition();
if (partition != null) {
updateState(PARTITION_LABEL_CHANNEL_UID, new StringType(partition.getLabel()));
updateState(PARTITION_STATE_CHANNEL_UID, new StringType(partition.getState().getMainState()));
updateState(PARTITION_ADDITIONAL_STATES_CHANNEL_UID,
new StringType("Deprecated field. Use direct channels instead"));
updateState(PARTITION_READY_TO_ARM_CHANNEL_UID, booleanToSwitchState(partition.getState().isReadyToArm()));
updateState(PARTITION_IN_EXIT_DELAY_CHANNEL_UID,
booleanToSwitchState(partition.getState().isInExitDelay()));
updateState(PARTITION_IN_ENTRY_DELAY_CHANNEL_UID,
booleanToSwitchState(partition.getState().isInEntryDelay()));
updateState(PARTITION_IN_TROUBLE_CHANNEL_UID, booleanToSwitchState(partition.getState().isInTrouble()));
updateState(PARTITION_ALARM_IN_MEMORY_CHANNEL_UID,
booleanToSwitchState(partition.getState().isHasAlarmInMemory()));
updateState(PARTITION_ZONE_BYPASS_CHANNEL_UID, booleanToSwitchState(partition.getState().isInZoneBypass()));
updateState(PARTITION_ZONE_IN_TAMPER_CHANNEL_UID,
booleanToSwitchState(partition.getState().isHasZoneInTamperTrouble()));
updateState(PARTITION_ZONE_IN_LOW_BATTERY_CHANNEL_UID,
booleanToSwitchState(partition.getState().isHasZoneInLowBatteryTrouble()));
updateState(PARTITION_ZONE_IN_FIRE_LOOP_CHANNEL_UID,
booleanToSwitchState(partition.getState().isHasZoneInFireLoopTrouble()));
updateState(PARTITION_ZONE_IN_SUPERVISION_TROUBLE_CHANNEL_UID,
booleanToSwitchState(partition.getState().isHasZoneInSupervisionTrouble()));
updateState(PARTITION_STAY_INSTANT_READY_CHANNEL_UID,
booleanToSwitchState(partition.getState().isStayInstantReady()));
updateState(PARTITION_FORCE_READY_CHANNEL_UID, booleanToSwitchState(partition.getState().isForceReady()));
updateState(PARTITION_BYPASS_READY_CHANNEL_UID, booleanToSwitchState(partition.getState().isBypassReady()));
updateState(PARTITION_INHIBIT_READY_CHANNEL_UID,
booleanToSwitchState(partition.getState().isInhibitReady()));
updateState(PARTITION_ALL_ZONES_CLOSED_CHANNEL_UID,
booleanToContactState(partition.getState().isAreAllZoneclosed()));
}
}
protected Partition getPartition() {
int index = calculateEntityIndex();
List<Partition> partitions = ParadoxPanel.getInstance().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;
}
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) {
boolean isDisarmCommand = PartitionCommand.DISARM.name().equals(command.toString());
if (isDisarmCommand && !config.isDisarmEnabled()) {
logger.warn(
"Received DISARM command but Disarm is not enabled. This is security relevant feature. Check documentation!");
return;
}
Partition partition = getPartition();
if (partition != null) {
partition.handleCommand(command.toString());
}
} else {
super.handleCommand(channelUID, command);
}
}
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 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.handlers;
import static org.openhab.binding.paradoxalarm.internal.handlers.ParadoxAlarmBindingConstants.*;
import java.util.List;
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.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Thing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxZoneHandler} Handler that updates states of paradox zones from the cache.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxZoneHandler extends EntityBaseHandler {
private final Logger logger = LoggerFactory.getLogger(ParadoxZoneHandler.class);
public ParadoxZoneHandler(@NonNull Thing thing) {
super(thing);
}
@Override
protected void updateEntity() {
int index = calculateEntityIndex();
List<Zone> zones = ParadoxPanel.getInstance().getZones();
if (zones == null) {
logger.debug(
"Zones collection of Paradox Panel object is null. Probably not yet initialized. Skipping update.");
return;
}
if (zones.size() <= index) {
logger.debug("Attempted to access zone out of bounds of current zone list. Index: {}, List: {}", index,
zones);
return;
}
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()));
}
}
private OpenClosedType booleanToContactState(boolean value) {
return value ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
}
private OnOffType booleanToSwitchState(boolean value) {
return value ? OnOffType.ON : OnOffType.OFF;
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 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.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Entity} Entity - base abstract class for Paradox entities (Partitions, zones, etc).
* Extend this class and add entity specific data (states, troubles, etc).
*
* @author Konstantin Polihronov - Initial contribution
*/
public abstract class Entity {
private final Logger logger = LoggerFactory.getLogger(Entity.class);
private int id;
private String label;
public Entity(int id, String label) {
this.id = id;
this.label = label.trim();
logger.debug("Creating entity with label: {} and ID: {}", label, id);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public String toString() {
return "Entity [id=" + id + ", label=" + label + "]";
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link EntityType} Enum of paradox entity types - zones, partitions, maybe more in the future...
*
* @author Konstantin Polihronov - Initial contribution
*/
public enum EntityType {
ZONE,
PARTITION;
@Override
public String toString() {
String upperCase = super.toString();
String lowerCase = upperCase.toLowerCase();
return lowerCase.replace(lowerCase.charAt(0), upperCase.charAt(0));
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link PanelType} Enum of all panel types
*
* @author Konstantin Polihronov - Initial contribution
*/
public enum PanelType {
EVO48(4, 48, 2, 16),
EVO96(4, 96, 3, 16),
EVO192(8, 192, 5, 16),
EVOHD(8, 192, 5, 16),
SP5500,
SP6000,
SP7000,
MG5000,
MG5050,
SP4000,
SP65,
UNKNOWN;
private int partitions;
private int zones;
private int pgms; // Programmable outputs
private int ramPagesNumber; // Ram pages 64 bytes each
private PanelType() {
this(0, 0, 0, 0);
}
private PanelType(int numberPartitions, int numberZones, int pgms, int ramPages) {
this.partitions = numberPartitions;
this.zones = numberZones;
this.pgms = pgms;
this.ramPagesNumber = ramPages;
}
public static PanelType from(String panelTypeStr) {
if (panelTypeStr == null) {
return PanelType.UNKNOWN;
}
try {
return PanelType.valueOf(panelTypeStr);
} catch (IllegalArgumentException e) {
return PanelType.UNKNOWN;
}
}
public int getPartitions() {
return partitions;
}
public int getZones() {
return zones;
}
@Override
public String toString() {
return this.name();
}
public int getPgms() {
return pgms;
}
public int getRamPagesNumber() {
return ramPagesNumber;
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.Arrays;
import org.openhab.binding.paradoxalarm.internal.parsers.IParadoxParser;
import org.openhab.core.util.HexUtils;
/**
* The {@link ParadoxInformation} Class that provides the basic panel
* information (serial number, panel type, application, hardware and bootloader
* versions. It's the object representation of 37 bytes 0x72 serial response.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxInformation {
private PanelType panelType;
private String serialNumber;
private Version applicationVersion;
private Version hardwareVersion;
private Version bootloaderVersion;
public ParadoxInformation(byte[] panelInfoBytes, IParadoxParser parser) {
panelType = ParadoxInformationConstants.parsePanelType(panelInfoBytes);
applicationVersion = parser.parseApplicationVersion(panelInfoBytes);
hardwareVersion = parser.parseHardwareVersion(panelInfoBytes);
bootloaderVersion = parser.parseBootloaderVersion(panelInfoBytes);
byte[] serialNumberBytes = Arrays.copyOfRange(panelInfoBytes, 12, 16);
serialNumber = HexUtils.bytesToHex(serialNumberBytes);
}
public PanelType getPanelType() {
return panelType;
}
public Version getApplicationVersion() {
return applicationVersion;
}
public Version getHardwareVersion() {
return hardwareVersion;
}
public Version getBootLoaderVersion() {
return bootloaderVersion;
}
public String getSerialNumber() {
return serialNumber;
}
@Override
public String toString() {
return "ParadoxInformation [panelType=" + panelType + ", serialNumber=" + serialNumber + ", applicationVersion="
+ applicationVersion + ", hardwareVersion=" + hardwareVersion + ", bootloaderVersion="
+ bootloaderVersion + "]";
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.openhab.core.util.HexUtils;
/**
* The {@link ParadoxInformation} Class that provides the basic panel
* information (serial number, panel type, application, hardware and bootloader
* versions. It's the object representation of 37 bytes 0x72 serial response.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxInformationConstants {
public static Map<String, PanelType> panelTypes = new HashMap<>();
static {
panelTypes.put("0xA122", PanelType.EVO48);
panelTypes.put("0xA133", PanelType.EVO48);
panelTypes.put("0xA159", PanelType.EVO48);
panelTypes.put("0xA123", PanelType.EVO192);
panelTypes.put("0xA15A", PanelType.EVO192);
panelTypes.put("0xA16D", PanelType.EVOHD);
panelTypes.put("0xA41E", PanelType.SP5500);
panelTypes.put("0xA450", PanelType.SP5500);
panelTypes.put("0xA41F", PanelType.SP6000);
panelTypes.put("0xA451", PanelType.SP6000);
panelTypes.put("0xA420", PanelType.SP7000);
panelTypes.put("0xA452", PanelType.SP7000);
panelTypes.put("0xA524", PanelType.MG5000);
panelTypes.put("0xA526", PanelType.MG5050);
panelTypes.put("0xAE53", PanelType.SP4000);
panelTypes.put("0xAE54", PanelType.SP65);
}
public static PanelType parsePanelType(byte[] infoPacket) {
if (infoPacket == null || infoPacket.length != 37) {
return PanelType.UNKNOWN;
}
byte[] panelTypeBytes = Arrays.copyOfRange(infoPacket, 6, 8);
String key = "0x" + HexUtils.bytesToHex(panelTypeBytes);
return ParadoxInformationConstants.panelTypes.getOrDefault(key, PanelType.UNKNOWN);
}
}

View File

@@ -0,0 +1,209 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.paradoxalarm.internal.communication.IDataUpdateListener;
import org.openhab.binding.paradoxalarm.internal.communication.IParadoxCommunicator;
import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxRuntimeException;
import org.openhab.binding.paradoxalarm.internal.parsers.EvoParser;
import org.openhab.binding.paradoxalarm.internal.parsers.IParadoxParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxPanel} Composition class which contains all Paradox entities.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxPanel implements IDataUpdateListener {
private final Logger logger = LoggerFactory.getLogger(ParadoxPanel.class);
@NonNull
private static ParadoxPanel paradoxPanel = new ParadoxPanel();
private ParadoxInformation panelInformation;
private List<Partition> partitions;
private List<Zone> zones;
private IParadoxParser parser;
private IParadoxCommunicator communicator;
private double vdcLevel;
private double batteryLevel;
private double dcLevel;
private ZonedDateTime panelTime;
private ParadoxPanel() {
this.parser = new EvoParser();
}
public void createModelEntities() {
byte[] panelInfoBytes = communicator.getPanelInfoBytes();
panelInformation = new ParadoxInformation(panelInfoBytes, parser);
if (isPanelSupported()) {
logger.info("Paradox system is supported. Panel data retrieved={} ", panelInformation);
createPartitions();
createZones();
} else {
throw new ParadoxRuntimeException(
"Unsupported panel type. Type: " + panelInformation.getPanelType().name());
}
}
public static ParadoxPanel getInstance() {
return paradoxPanel;
}
public boolean isPanelSupported() {
PanelType panelType = panelInformation.getPanelType();
return panelType == PanelType.EVO48 || panelType == PanelType.EVO192 || panelType == PanelType.EVOHD;
}
public void updateEntitiesStates() {
if (!isOnline()) {
logger.debug("Not online. Unable to update entities states. ");
return;
}
List<byte[]> currentPartitionFlags = communicator.getPartitionFlags();
for (int i = 0; i < partitions.size(); i++) {
Partition partition = partitions.get(i);
if (i < currentPartitionFlags.size()) {
partition.setState(parser.calculatePartitionState(currentPartitionFlags.get(i)));
} else {
logger.debug("Partition flags collection is smaller than the number of partitions.");
}
}
ZoneStateFlags zoneStateFlags = communicator.getZoneStateFlags();
for (int i = 0; i < zones.size(); i++) {
Zone zone = zones.get(i);
zone.setZoneState(parser.calculateZoneState(zone.getId(), zoneStateFlags));
}
byte[] firstRamPage = communicator.getMemoryMap().getElement(0);
panelTime = constructPanelTime(firstRamPage);
vdcLevel = Math.max(0, (firstRamPage[25] & 0xFF) * (20.3 - 1.4) / 255.0 + 1.4);
dcLevel = Math.max(0, (firstRamPage[27] & 0xFF) * 22.8 / 255);
batteryLevel = Math.max(0, (firstRamPage[26] & 0xFF) * 22.8 / 255);
}
protected ZonedDateTime constructPanelTime(byte[] firstPage) {
try {
int year = firstPage[18] * 100 + firstPage[19];
return ZonedDateTime.of(
LocalDateTime.of(year, firstPage[20], firstPage[21], firstPage[22], firstPage[23], firstPage[24]),
TimeZone.getDefault().toZoneId());
} catch (DateTimeException e) {
logger.debug("Received exception during parsing panel time. Falling back to old time.", e);
return panelTime;
}
}
private List<Zone> createZones() {
zones = new ArrayList<>();
Map<Integer, String> zoneLabels = communicator.getZoneLabels();
for (int i = 0; i < zoneLabels.size(); i++) {
Zone zone = new Zone(i + 1, zoneLabels.get(i));
zones.add(zone);
}
return zones;
}
private List<Partition> createPartitions() {
partitions = new ArrayList<>();
Map<Integer, String> partitionLabels = communicator.getPartitionLabels();
for (int i = 0; i < partitionLabels.size(); i++) {
Partition partition = new Partition(i + 1, partitionLabels.get(i));
partitions.add(partition);
logger.debug("Partition {}:\t{}", i + 1, partition.getState().getMainState());
}
return partitions;
}
@Override
public void update() {
if (panelInformation == null || partitions == null || zones == null) {
createModelEntities();
}
updateEntitiesStates();
}
public void dispose() {
this.panelInformation = null;
this.partitions = null;
this.zones = null;
}
public ParadoxInformation getPanelInformation() {
return panelInformation;
}
public List<Partition> getPartitions() {
return partitions;
}
public void setPartitions(List<Partition> partitions) {
this.partitions = partitions;
}
public List<Zone> getZones() {
return zones;
}
public void setZones(List<Zone> zones) {
this.zones = zones;
}
public boolean isOnline() {
return communicator != null && communicator.isOnline();
}
public double getVdcLevel() {
return vdcLevel;
}
public double getBatteryLevel() {
return batteryLevel;
}
public double getDcLevel() {
return dcLevel;
}
public ZonedDateTime getPanelTime() {
return panelTime;
}
public void setCommunicator(IParadoxCommunicator communicator) {
this.communicator = communicator;
}
public IParadoxCommunicator getCommunicator() {
return communicator;
}
@Override
public String toString() {
return "ParadoxPanel [panelInformation=" + panelInformation + "]";
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 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.model;
import org.openhab.binding.paradoxalarm.internal.communication.IParadoxCommunicator;
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.handlers.Commandable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Partition} Paradox partition.
* ID is always numeric (1-8 for Evo192)
*
* @author Konstantin Polihronov - Initial contribution
*/
public class Partition extends Entity implements Commandable {
private final Logger logger = LoggerFactory.getLogger(Partition.class);
private PartitionState state = new PartitionState();
public Partition(int id, String label) {
super(id, label);
}
public PartitionState getState() {
return state;
}
public Partition setState(PartitionState state) {
this.state = state;
logger.debug("Partition {}:\t{}", getLabel(), getState().getMainState());
return this;
}
@Override
public void handleCommand(String command) {
PartitionCommand partitionCommand = PartitionCommand.parse(command);
if (partitionCommand == PartitionCommand.UNKNOWN) {
logger.debug("Command UNKNOWN will be ignored.");
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);
IParadoxCommunicator communicator = ParadoxPanel.getInstance().getCommunicator();
communicator.submitRequest(request);
}
}

View File

@@ -0,0 +1,263 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link Partition} Paradox partition states. Retrieved and parsed from RAM memory responses.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class PartitionState {
private boolean isArmed;
private boolean isArmedInAway;
private boolean isArmedInStay;
private boolean isArmedInNoEntry;
private boolean isInAlarm;
private boolean isInSilentAlarm;
private boolean isInAudibleAlarm;
private boolean isInFireAlarm;
private boolean isReadyToArm;
private boolean isInExitDelay;
private boolean isInEntryDelay;
private boolean isInTrouble;
private boolean hasAlarmInMemory;
private boolean isInZoneBypass;
private boolean hasZoneInTamperTrouble;
private boolean hasZoneInLowBatteryTrouble;
private boolean hasZoneInFireLoopTrouble;
private boolean hasZoneInSupervisionTrouble;
private boolean isStayInstantReady;
private boolean isForceReady;
private boolean isBypassReady;
private boolean isInhibitReady;
private boolean areAllZoneclosed;
public String getMainState() {
if (isInAlarm) {
return "InAlarm";
} else {
return isArmed || isArmedInAway || isArmedInStay || isArmedInNoEntry ? "Armed" : "Disarmed";
}
}
@Override
public String toString() {
return "PartitionState [isArmed=" + isArmed + ", isArmedInAway=" + isArmedInAway + ", isArmedInStay="
+ isArmedInStay + ", isArmedInNoEntry=" + isArmedInNoEntry + ", isInAlarm=" + isInAlarm
+ ", isInSilentAlarm=" + isInSilentAlarm + ", isInAudibleAlarm=" + isInAudibleAlarm + ", isInFireAlarm="
+ isInFireAlarm + ", isReadyToArm=" + isReadyToArm + ", isInExitDelay=" + isInExitDelay
+ ", isInEntryDelay=" + isInEntryDelay + ", isInTrouble=" + isInTrouble + ", hasAlarmInMemory="
+ hasAlarmInMemory + ", isInZoneBypass=" + isInZoneBypass + ", hasZoneInTamperTrouble="
+ hasZoneInTamperTrouble + ", hasZoneInLowBatteryTrouble=" + hasZoneInLowBatteryTrouble
+ ", hasZoneInFireLoopTrouble=" + hasZoneInFireLoopTrouble + ", hasZoneInSupervisionTrouble="
+ hasZoneInSupervisionTrouble + ", isStayInstantReady=" + isStayInstantReady + ", isForceReady="
+ isForceReady + ", isBypassReady=" + isBypassReady + ", isInhibitReady=" + isInhibitReady
+ ", areAllZoneclosed=" + areAllZoneclosed + "]";
}
public boolean isArmed() {
return isArmed;
}
public void setArmed(boolean isArmed) {
this.isArmed = isArmed;
}
public boolean isArmedInAway() {
return isArmedInAway;
}
public void setArmedInAway(boolean isArmedInAway) {
this.isArmedInAway = isArmedInAway;
}
public boolean isArmedInStay() {
return isArmedInStay;
}
public void setArmedInStay(boolean isArmedInStay) {
this.isArmedInStay = isArmedInStay;
}
public boolean isArmedInNoEntry() {
return isArmedInNoEntry;
}
public void setArmedInNoEntry(boolean isArmedInNoEntry) {
this.isArmedInNoEntry = isArmedInNoEntry;
}
public boolean isInAlarm() {
return isInAlarm;
}
public void setInAlarm(boolean isInAlarm) {
this.isInAlarm = isInAlarm;
}
public boolean isInSilentAlarm() {
return isInSilentAlarm;
}
public void setInSilentAlarm(boolean isInSilentAlarm) {
this.isInSilentAlarm = isInSilentAlarm;
}
public boolean isInAudibleAlarm() {
return isInAudibleAlarm;
}
public void setInAudibleAlarm(boolean isInAudibleAlarm) {
this.isInAudibleAlarm = isInAudibleAlarm;
}
public boolean isInFireAlarm() {
return isInFireAlarm;
}
public void setInFireAlarm(boolean isInFireAlarm) {
this.isInFireAlarm = isInFireAlarm;
}
public boolean isReadyToArm() {
return isReadyToArm;
}
public void setReadyToArm(boolean isReadyToArm) {
this.isReadyToArm = isReadyToArm;
}
public boolean isInExitDelay() {
return isInExitDelay;
}
public void setInExitDelay(boolean isInExitDelay) {
this.isInExitDelay = isInExitDelay;
}
public boolean isInEntryDelay() {
return isInEntryDelay;
}
public void setInEntryDelay(boolean isInEntryDelay) {
this.isInEntryDelay = isInEntryDelay;
}
public boolean isInTrouble() {
return isInTrouble;
}
public void setInTrouble(boolean isInTrouble) {
this.isInTrouble = isInTrouble;
}
public boolean isHasAarmInMemory() {
return hasAlarmInMemory;
}
public void setHasAarmInMemory(boolean hasAarmInMemory) {
this.hasAlarmInMemory = hasAarmInMemory;
}
public boolean isInZoneBypass() {
return isInZoneBypass;
}
public void setInZoneBypass(boolean isInZoneBypass) {
this.isInZoneBypass = isInZoneBypass;
}
public boolean isHasZoneInTamperTrouble() {
return hasZoneInTamperTrouble;
}
public void setHasZoneInTamperTrouble(boolean hasZoneInTamperTrouble) {
this.hasZoneInTamperTrouble = hasZoneInTamperTrouble;
}
public boolean isHasZoneInLowBatteryTrouble() {
return hasZoneInLowBatteryTrouble;
}
public void setHasZoneInLowBatteryTrouble(boolean hasZoneInLowBatteryTrouble) {
this.hasZoneInLowBatteryTrouble = hasZoneInLowBatteryTrouble;
}
public boolean isHasZoneInFireLoopTrouble() {
return hasZoneInFireLoopTrouble;
}
public void setHasZoneInFireLoopTrouble(boolean hasZoneInFireLoopTrouble) {
this.hasZoneInFireLoopTrouble = hasZoneInFireLoopTrouble;
}
public boolean isHasZoneInSupervisionTrouble() {
return hasZoneInSupervisionTrouble;
}
public void setHasZoneInSupervisionTrouble(boolean hasZoneInSupervisionTrouble) {
this.hasZoneInSupervisionTrouble = hasZoneInSupervisionTrouble;
}
public boolean isStayInstantReady() {
return isStayInstantReady;
}
public void setStayInstantReady(boolean isStayInstantReady) {
this.isStayInstantReady = isStayInstantReady;
}
public boolean isForceReady() {
return isForceReady;
}
public void setForceReady(boolean isForceReady) {
this.isForceReady = isForceReady;
}
public boolean isBypassReady() {
return isBypassReady;
}
public void setBypassReady(boolean isBypassReady) {
this.isBypassReady = isBypassReady;
}
public boolean isInhibitReady() {
return isInhibitReady;
}
public void setInhibitReady(boolean isInhibitReady) {
this.isInhibitReady = isInhibitReady;
}
public boolean isAreAllZoneclosed() {
return areAllZoneclosed;
}
public void setAllZoneClosed(boolean areAllZoneclosed) {
this.areAllZoneclosed = areAllZoneclosed;
}
public boolean isHasAlarmInMemory() {
return hasAlarmInMemory;
}
public void setHasAlarmInMemory(boolean hasAlarmInMemory) {
this.hasAlarmInMemory = hasAlarmInMemory;
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Version} This class holds version information
*
* @author Konstantin Polihronov - Initial contribution
*/
public class Version {
private final Logger logger = LoggerFactory.getLogger(Version.class);
private Byte version;
private Byte revision;
private Byte build;
private Date buildTime;
public Version(Byte version, Byte revision) {
this(version, revision, null);
}
public Version(Byte version, Byte revision, Byte build) {
this(version, revision, build, null);
}
public Version(Byte version, Byte revision, Byte build, Date buildTime) {
this.version = version;
this.revision = revision;
this.build = build;
this.buildTime = buildTime;
logger.debug("version={}", this);
}
public Byte getVersion() {
return version;
}
public void setVersion(Byte version) {
this.version = version;
}
public Byte getRevision() {
return revision;
}
public void setRevision(Byte revision) {
this.revision = revision;
}
public Byte getBuild() {
return build;
}
public void setBuild(Byte build) {
this.build = build;
}
public Date getBuildTime() {
return buildTime;
}
public void setBuildTime(Date buildTime) {
this.buildTime = buildTime;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Version: ");
sb.append(version);
sb.append(".");
sb.append(revision);
if (build != null) {
sb.append(".");
sb.append(build);
}
if (buildTime != null) {
sb.append("/");
sb.append(buildTime);
}
return sb.toString();
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 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.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Zone} Paradox zone.
* ID is always numeric (1-192 for Evo192)
* States are taken from cached RAM memory map and parsed.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class Zone extends Entity {
private final Logger logger = LoggerFactory.getLogger(Zone.class);
private ZoneState zoneState;
public Zone(int id, String label) {
super(id, label);
}
public 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());
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link ZoneStateFlags} Paradox zone state flags. Retrieved and parsed from RAM memory responses.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ZoneState {
private boolean isOpened;
private boolean isTampered;
private boolean hasLowBattery;
public ZoneState(boolean isOpened, boolean isTampered, boolean hasLowBattery) {
this.isOpened = isOpened;
this.isTampered = isTampered;
this.hasLowBattery = hasLowBattery;
}
public boolean isOpened() {
return isOpened;
}
public void setOpened(boolean isOpened) {
this.isOpened = isOpened;
}
public boolean isTampered() {
return isTampered;
}
public void setTampered(boolean isTampered) {
this.isTampered = isTampered;
}
public boolean hasLowBattery() {
return hasLowBattery;
}
public void setHasLowBattery(boolean hasLowBattery) {
this.hasLowBattery = hasLowBattery;
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link ZoneStateFlags} Paradox zone state flags. Retrieved and parsed from RAM memory responses.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ZoneStateFlags {
private byte[] zonesOpened;
private byte[] zonesTampered;
private byte[] zonesLowBattery;
public byte[] getZonesOpened() {
return zonesOpened;
}
public void setZonesOpened(byte[] zonesOpened) {
this.zonesOpened = zonesOpened;
}
public byte[] getZonesTampered() {
return zonesTampered;
}
public void setZonesTampered(byte[] zonesTampered) {
this.zonesTampered = zonesTampered;
}
public byte[] getZonesLowBattery() {
return zonesLowBattery;
}
public void setZonesLowBattery(byte[] zonesLowBattery) {
this.zonesLowBattery = zonesLowBattery;
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 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.parsers;
import org.openhab.binding.paradoxalarm.internal.model.Version;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AbstractParser} Contains parsing methods irelevant from panel type
*
* @author Konstantin Polihronov - Initial contribution
*/
public abstract class AbstractParser implements IParadoxParser {
private final Logger logger = LoggerFactory.getLogger(AbstractParser.class);
@Override
public Version parseApplicationVersion(byte[] panelInfoBytes) {
return new Version(translateAsNibbles(panelInfoBytes[9]), translateAsNibbles(panelInfoBytes[10]),
translateAsNibbles(panelInfoBytes[11]));
}
@Override
public Version parseHardwareVersion(byte[] panelInfoBytes) {
return new Version(translateAsNibbles(panelInfoBytes[16]), translateAsNibbles(panelInfoBytes[17]));
}
@Override
public Version parseBootloaderVersion(byte[] panelInfoBytes) {
return new Version(translateAsNibbles(panelInfoBytes[18]), translateAsNibbles(panelInfoBytes[19]),
translateAsNibbles(panelInfoBytes[20]));
}
private byte translateAsNibbles(byte byteValue) {
return (byte) ((ParadoxUtil.getHighNibble(byteValue) * 10 + ParadoxUtil.getLowNibble(byteValue)) & 0xFF);
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2020 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.parsers;
import org.openhab.binding.paradoxalarm.internal.model.PartitionState;
import org.openhab.binding.paradoxalarm.internal.model.ZoneState;
import org.openhab.binding.paradoxalarm.internal.model.ZoneStateFlags;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
/**
* The {@link EvoParser} Implementation of parser interface for EVO type panels
*
* @author Konstantin Polihronov - Initial contribution
*/
public class EvoParser extends AbstractParser {
@Override
public PartitionState calculatePartitionState(byte[] partitionFlags) {
byte firstByte = partitionFlags[0];
PartitionState state = new PartitionState();
state.setArmed(ParadoxUtil.isBitSet(firstByte, 0));
state.setArmedInAway(ParadoxUtil.isBitSet(firstByte, 1));
state.setArmedInStay(ParadoxUtil.isBitSet(firstByte, 2));
state.setArmedInNoEntry(ParadoxUtil.isBitSet(firstByte, 3));
state.setInAlarm(ParadoxUtil.isBitSet(firstByte, 4));
state.setInSilentAlarm(ParadoxUtil.isBitSet(firstByte, 5));
state.setInAudibleAlarm(ParadoxUtil.isBitSet(firstByte, 6));
state.setInFireAlarm(ParadoxUtil.isBitSet(firstByte, 7));
byte secondByte = partitionFlags[1];
state.setReadyToArm(ParadoxUtil.isBitSet(secondByte, 0));
state.setInExitDelay(ParadoxUtil.isBitSet(secondByte, 1));
state.setInEntryDelay(ParadoxUtil.isBitSet(secondByte, 2));
state.setInTrouble(ParadoxUtil.isBitSet(secondByte, 3));
state.setHasAlarmInMemory(ParadoxUtil.isBitSet(secondByte, 4));
state.setInZoneBypass(ParadoxUtil.isBitSet(secondByte, 5));
byte thirdByte = partitionFlags[2];
state.setHasZoneInTamperTrouble(ParadoxUtil.isBitSet(thirdByte, 4));
state.setHasZoneInLowBatteryTrouble(ParadoxUtil.isBitSet(thirdByte, 5));
state.setHasZoneInFireLoopTrouble(ParadoxUtil.isBitSet(thirdByte, 6));
state.setHasZoneInSupervisionTrouble(ParadoxUtil.isBitSet(thirdByte, 7));
byte sixthByte = partitionFlags[5];
state.setStayInstantReady(ParadoxUtil.isBitSet(sixthByte, 0));
state.setForceReady(ParadoxUtil.isBitSet(sixthByte, 1));
state.setBypassReady(ParadoxUtil.isBitSet(sixthByte, 2));
state.setInhibitReady(ParadoxUtil.isBitSet(sixthByte, 3));
state.setAllZoneClosed(ParadoxUtil.isBitSet(sixthByte, 4));
return state;
}
@Override
public ZoneState calculateZoneState(int id, ZoneStateFlags zoneStateFlags) {
int index = (id - 1) / 8;
int bitNumber = id % 8 - 1;
byte[] zonesOpened = zoneStateFlags.getZonesOpened();
boolean isOpened = ParadoxUtil.isBitSet(zonesOpened[index], bitNumber);
byte[] zonesTampered = zoneStateFlags.getZonesTampered();
boolean isTampered = ParadoxUtil.isBitSet(zonesTampered[index], bitNumber);
byte[] zonesLowBattery = zoneStateFlags.getZonesLowBattery();
boolean hasLowBattery = ParadoxUtil.isBitSet(zonesLowBattery[index], bitNumber);
return new ZoneState(isOpened, isTampered, hasLowBattery);
}
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 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.parsers;
import org.openhab.binding.paradoxalarm.internal.model.PartitionState;
import org.openhab.binding.paradoxalarm.internal.model.Version;
import org.openhab.binding.paradoxalarm.internal.model.ZoneState;
import org.openhab.binding.paradoxalarm.internal.model.ZoneStateFlags;
/**
* The {@link IParadoxParser} Interface for Paradox Parsers implementations
*
* @author Konstantin Polihronov - Initial contribution
*/
public interface IParadoxParser {
PartitionState calculatePartitionState(byte[] partitionFlags);
ZoneState calculateZoneState(int id, ZoneStateFlags zoneStateFlags);
Version parseApplicationVersion(byte[] panelInfoBytes);
Version parseHardwareVersion(byte[] panelInfoBytes);
Version parseBootloaderVersion(byte[] panelInfoBytes);
}

View File

@@ -0,0 +1,229 @@
/**
* Copyright (c) 2010-2020 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.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ParadoxUtil} Utility class for different calculations / manipulations of data in the model and
* communicators.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class ParadoxUtil {
private static final String SPACE_DELIMITER = " ";
private final static Logger logger = LoggerFactory.getLogger(ParadoxUtil.class);
public static byte calculateChecksum(byte[] payload) {
int result = 0;
for (byte everyByte : payload) {
result += everyByte;
}
return (byte) (result % 256);
}
public static byte getBit(int value, int bitNumber) {
return (byte) ((value >> bitNumber) & 1);
}
public static boolean isBitSet(int value, int bitNumber) {
return ((value >> bitNumber) & 1) == 1;
}
public static void printPacket(String description, byte[] array) {
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");
return;
}
printByteArray(description, array, array.length);
}
public static void printByteArray(String description, byte[] array, int length) {
if (!logger.isTraceEnabled()) {
return;
}
String result = byteArrayToString(array, length);
if (!result.isEmpty()) {
logger.trace("{}", description + SPACE_DELIMITER + result);
}
}
public static String byteArrayToString(byte[] array) {
return byteArrayToString(array, array.length);
}
/**
*
* Returns passed array as HEX string. On every 8 bytes we put space for better readability. Example 16
* bytes array output: AA47000263000000 03EE00EEEEEEB727
*
* @param array
* @param length
* @return String
*/
public static String byteArrayToString(byte[] array, int length) {
if (array == null) {
throw new IllegalArgumentException("Array must not be null.");
}
if (length > array.length) {
throw new IllegalArgumentException("Length should be lower than or equal to array length. Length=" + length
+ ". Array length=" + array.length);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i != 0 && i % 8 == 0) {
sb.append(SPACE_DELIMITER);
}
sb.append(String.format("%02X", array[i]));
}
return sb.toString();
}
public static byte setBit(byte byteValue, int i, int j) {
if (j == 1) {
return (byte) (byteValue | (1 << i));
} else {
return (byte) (byteValue & ~(1 << i));
}
}
public static byte getHighNibble(byte value) {
return (byte) ((value & 0xF0) >> 4);
}
public static byte getLowNibble(byte value) {
return (byte) (value & 0x0F);
}
public static byte[] mergeByteArrays(byte[]... arrays) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (byte[] array : arrays) {
outputStream.write(array);
}
byte[] byteArray = outputStream.toByteArray();
return byteArray;
} catch (IOException e) {
logger.warn("Exception merging arrays:", e);
return new byte[0];
}
}
public static byte[] intToByteArray(int value) {
return ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).order(ByteOrder.BIG_ENDIAN).putInt(value).array();
}
public static byte[] shortToByteArray(short value) {
return ByteBuffer.allocate(Short.SIZE / Byte.SIZE).order(ByteOrder.BIG_ENDIAN).putShort(value).array();
}
public static byte[] stringToBCD(String pcPassword) {
return stringToBCD(pcPassword, 4);
}
public static byte[] stringToBCD(String pcPassword, int numberOfDigits) {
byte[] result = new byte[numberOfDigits / 2];
for (int i = 0, j = 0; i < 2; i++, j += 2) {
String substring = pcPassword.substring(j, j + 1);
int parseInt = Integer.parseInt(substring);
result[i] = (byte) ((parseInt & 0x0F) << 4);
substring = pcPassword.substring(j + 1, j + 2);
parseInt = Integer.parseInt(substring);
result[i] |= (byte) (parseInt & 0x0F);
}
return result;
}
/**
* This method fills array with 0xEE based on rate.
* Example: If input array length is 5 and rate is 8 the array will be extended with 3 more bytes filled with 0xEE
*
* @param inputArray
* @param rate
* @return byte[]
*/
public static byte[] extendArray(byte[] inputArray, int rate) {
if (inputArray == null || inputArray.length % rate == 0) {
return inputArray;
}
final int newLength = inputArray.length + (rate - inputArray.length % rate);
byte[] result = new byte[newLength];
for (int i = 0; i < result.length; i++) {
if (i < inputArray.length) {
result[i] = inputArray[i];
} else {
result[i] = (byte) 0xEE;
}
}
return result;
}
/**
* Returns bytes from string with standard US_ASCII standard charset to ensure everywhere in the binding we use same
* charset.
*
* @param str
* @return byte[]
*
*/
public static byte[] getBytesFromString(String str) {
if (str == null) {
throw new IllegalArgumentException("String must not be null !");
}
return str.getBytes(StandardCharsets.US_ASCII);
}
public static int[] toIntArray(byte[] input) {
if (input == null) {
throw new IllegalArgumentException("Input array must not be null");
}
int[] result = new int[input.length];
for (int i = 0; i < input.length; i++) {
result[i] = input[i] & 0xFF;
}
return result;
}
public static byte[] toByteArray(int[] input) {
if (input == null) {
throw new IllegalArgumentException("Input array must not be null");
}
byte[] result = new byte[input.length];
for (int i = 0; i < input.length; i++) {
result[i] = (byte) (input[i]);
}
return result;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="paradoxalarm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>ParadoxAlarm Binding</name>
<description>This is the binding for ParadoxAlarm.</description>
<author>Konstantin Polihronov</author>
</binding:binding>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="paradoxalarm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="ip150">
<label>Paradox IP150 Module Connector</label>
<description>Paradox IP150 module connector</description>
<channels>
<channel id="communicationCommand" typeId="command"/>
<channel id="communicationState" typeId="communicationState"/>
</channels>
<config-description>
<parameter name="refresh" type="integer" min="1" max="600">
<label>Refresh Interval</label>
<description>Specifies the refresh interval in seconds.</description>
<default>5</default>
</parameter>
<parameter name="panelType" type="text" required="false">
<label>Panel Type</label>
<description>PanelType</description>
</parameter>
<parameter name="ip150Password" type="text" required="true">
<label>IP150 Password</label>
<description>Password for accessing IP150</description>
<context>password</context>
<required>true</required>
</parameter>
<parameter name="pcPassword" type="text" required="true">
<label>PC Password</label>
<description>PC Password (section 3012 of configuration)</description>
<default>0000</default>
<context>password</context>
</parameter>
<parameter name="ipAddress" type="text" required="true">
<label>Network Address</label>
<description>IP address or host name of IP150</description>
<context>network-address</context>
<required>true</required>
</parameter>
<parameter name="port" type="integer" required="true">
<label>Port</label>
<description>Port to connect to IP150</description>
<default>10000</default>
</parameter>
<parameter name="reconnectWaitTime" type="integer" required="true" unit="s">
<label>Reconnect Wait Time</label>
<description>The time to wait before a reconnect occurs after socket timeout. Value is in seconds</description>
<default>30</default>
</parameter>
<parameter name="maxZones" type="integer" unit="s">
<label>Max Zones</label>
<description>Maximum number of configured zones to check (from zone 1 to maxZones)</description>
<default>0</default>
</parameter>
<parameter name="maxPartitions" type="integer" unit="s">
<label>Max Partitions</label>
<description>Maximum number of configured partitions to check (from partition 1 to maxPartitions)</description>
<default>0</default>
</parameter>
<parameter name="encrypt" type="boolean">
<label>Use Encryption</label>
<description>If set to true will use encrypted communication. Default=false for backward compatibility</description>
<default>false</default>
</parameter>
</config-description>
</bridge-type>
<channel-type id="command">
<item-type>String</item-type>
<label>Communicator Command</label>
<description>Send Command</description>
</channel-type>
<channel-type id="communicationState">
<item-type>String</item-type>
<label>Bridge Communication State</label>
<description>Status of connection to Paradox system</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="paradoxalarm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="panel">
<supported-bridge-type-refs>
<bridge-type-ref id="ip150"/>
</supported-bridge-type-refs>
<label>Paradox Panel</label>
<description>Paradox panel various information</description>
<channels>
<channel id="state" typeId="panelState"/>
<channel id="inputVoltage" typeId="voltage"/>
<channel id="boardVoltage" typeId="voltage"/>
<channel id="batteryVoltage" typeId="voltage"/>
<channel id="panelTime" typeId="panelTime"/>
</channels>
<properties>
<property name="serialNumber"/>
<property name="panelType"/>
<property name="hardwareVersion"/>
<property name="applicationVersion"/>
<property name="bootloaderVersion"/>
</properties>
</thing-type>
<channel-type id="panelState">
<item-type>String</item-type>
<label>Panel State</label>
<description>Shows the state of the communication to panel (online/offline)</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="serialNumber">
<item-type>String</item-type>
<label>Serial Number</label>
<description>Panel serial number</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="panelType">
<item-type>String</item-type>
<label>Panel Type</label>
<description>Panel type (Evo, SP, etc)</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="hardwareVersion">
<item-type>String</item-type>
<label>Hardware Version</label>
<description>Panel hardware version</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="applicationVersion">
<item-type>String</item-type>
<label>Application Version</label>
<description>Panel application version</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="bootloaderVersion">
<item-type>String</item-type>
<label>Boot Loader Version</label>
<description>Boot loader version</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="voltage">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>Voltage</description>
<state readOnly="true" pattern="%f %unit%"/>
</channel-type>
<channel-type id="panelTime">
<item-type>DateTime</item-type>
<label>Date</label>
<description>The current date and time on the panel</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="paradoxalarm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="partition">
<supported-bridge-type-refs>
<bridge-type-ref id="ip150"/>
</supported-bridge-type-refs>
<label>Paradox Partition</label>
<description>Paradox Partition</description>
<channels>
<channel id="label" typeId="partitionLabel"/>
<channel id="state" typeId="state"/>
<channel id="additionalStates" typeId="additionalState"/>
<channel id="readyToArm" typeId="readyToArm"/>
<channel id="inExitDelay" typeId="inExitDelay"/>
<channel id="inEntryDelay" typeId="inEntryDelay"/>
<channel id="inTrouble" typeId="inTrouble"/>
<channel id="alarmInMemory" typeId="alarmInMemory"/>
<channel id="zoneBypass" typeId="zoneBypass"/>
<channel id="zoneInTamperTrouble" typeId="zoneInTamperTrouble"/>
<channel id="zoneInLowBatteryTrouble" typeId="zoneInLowBatteryTrouble"/>
<channel id="zoneInFireLoopTrouble" typeId="zoneInFireLoopTrouble"/>
<channel id="zoneInSupervisionTrouble" typeId="zoneInSupervisionTrouble"/>
<channel id="stayInstantReady" typeId="stayInstantReady"/>
<channel id="forceReady" typeId="forceReady"/>
<channel id="bypassReady" typeId="bypassReady"/>
<channel id="inhibitReady" typeId="inhibitReady"/>
<channel id="allZonesClosed" typeId="allZonesClosed"/>
<channel id="command" typeId="command"/>
</channels>
<config-description>
<parameter name="id" type="integer" min="1" max="8" required="true">
<label>Partition Id</label>
<description>This is the partition ID of the Paradox partition (1-8 for Evo)</description>
</parameter>
<parameter name="disarmEnabled" type="boolean">
<label>Disarm Command Enabled</label>
<description>Parameter that enables option to send DISARM command to the partition. (DANGEROUS !)</description>
<default>false</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="partitionLabel">
<item-type>String</item-type>
<label>Partition Label</label>
<description>Label of partition</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="state">
<item-type>String</item-type>
<label>Partition State</label>
<description>State of partition</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="additionalState">
<item-type>String</item-type>
<label>Partition Additional States</label>
<description>Additional states provided by panel (deprecated channel)</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="readyToArm">
<item-type>switch</item-type>
<label>Partition Ready To Arm</label>
<description>Partition ready to arm</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="inExitDelay">
<item-type>switch</item-type>
<label>Partition In Exit Delay</label>
<description>Partition in Exit Delay</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="inEntryDelay">
<item-type>switch</item-type>
<label>Partition In Entry Delay</label>
<description>Partition in Entry Delay</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="inTrouble">
<item-type>switch</item-type>
<label>Partition In Trouble</label>
<description>Partition in Trouble</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="alarmInMemory">
<item-type>switch</item-type>
<label>Partition Has Alarm In Memory</label>
<description>Partition has Alarm in Memory</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="zoneBypass">
<item-type>switch</item-type>
<label>Partition Has Zone Bypass</label>
<description>Partition has Zone Bypass</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="zoneInTamperTrouble">
<item-type>switch</item-type>
<label>Partition Has Zone In Tamper Trouble</label>
<description>Partition has in Tamper Trouble</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="zoneInLowBatteryTrouble">
<item-type>switch</item-type>
<label>Partition Has Zone In Low Battery Trouble</label>
<description>Partition has in Low Battery Trouble</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="zoneInFireLoopTrouble">
<item-type>switch</item-type>
<label>Partition Has Zone In Fire Loop Trouble</label>
<description>Partition has in Fire Loop Trouble</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="zoneInSupervisionTrouble">
<item-type>switch</item-type>
<label>Partition Has Zone In Supervision Trouble</label>
<description>Partition has in Supervision Trouble</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="stayInstantReady">
<item-type>switch</item-type>
<label>Partition Is Stay Instant Ready</label>
<description>Partition is Stay Instant Ready</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="forceReady">
<item-type>switch</item-type>
<label>Partition Is Force Ready</label>
<description>Partition is Force Ready</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="bypassReady">
<item-type>switch</item-type>
<label>Partition Is Bypass Ready</label>
<description>Partition is Bypass Ready</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="inhibitReady">
<item-type>switch</item-type>
<label>Partition Is Inhibit Ready</label>
<description>Partition is Inhibit Ready</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="allZonesClosed">
<item-type>contact</item-type>
<label>Partition Has All Zones Closed</label>
<description>Partition has All Zones closed</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="command">
<item-type>String</item-type>
<label>Partition Command</label>
<description>Send command</description>
<state>
<options>
<option value="ARM">Arm</option>
<option value="STAY_ARM">Stay Arm</option>
<option value="INSTANT_ARM">Instant Arm</option>
<option value="FORCE_ARM">Force Arm</option>
<option value="DISARM">Disarm</option>
<option value="BEEP">Keyboard Beep</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="paradoxalarm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="zone">
<supported-bridge-type-refs>
<bridge-type-ref id="ip150"/>
</supported-bridge-type-refs>
<label>Paradox Zone</label>
<description>Paradox zone</description>
<channels>
<channel id="label" typeId="zoneLabel"/>
<channel id="opened" typeId="openedState"/>
<channel id="tampered" typeId="tamperedState"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<config-description>
<parameter name="id" type="integer" min="1" max="192" required="true">
<label>Zone Id</label>
<description>This is the zone ID in the Paradox configuration</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="zoneLabel">
<item-type>String</item-type>
<label>Zone Label</label>
<description>Label of zone</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="openedState">
<item-type>contact</item-type>
<label>Zone State</label>
<description>State of zone</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="tamperedState">
<item-type>switch</item-type>
<label>Tampered</label>
<description>State of zone</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2020 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 main;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.paradoxalarm.internal.communication.ICommunicatorBuilder;
import org.openhab.binding.paradoxalarm.internal.communication.IParadoxCommunicator;
import org.openhab.binding.paradoxalarm.internal.communication.ParadoxBuilderFactory;
import org.openhab.binding.paradoxalarm.internal.model.PanelType;
import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Main} - used for testing purposes of low-level stuff.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class Main {
private static Logger logger = LoggerFactory.getLogger(Main.class);
private static String ipAddress;
private static int port;
// PASSWORD is your IP150 password
private static String ip150Password;
// PC Password is the value of section 3012, i.e. if value is 0987, PC Password is two bytes 0x09, 0x87
private static String pcPassword;
private static ScheduledExecutorService scheduler;
private static IParadoxCommunicator communicator;
public static void main(String[] args) {
readArguments(args);
try {
scheduler = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
logger.info("System properties={}",
System.getProperties().get(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY));
ParadoxBuilderFactory factory = new ParadoxBuilderFactory();
ICommunicatorBuilder builder = factory.createBuilder(PanelType.EVO192);
communicator = builder.withIp150Password(ip150Password).withPcPassword(pcPassword).withIpAddress(ipAddress)
.withTcpPort(port).withMaxPartitions(4).withMaxZones(20).withScheduler(scheduler)
.withEncryption(true).build();
ParadoxPanel panel = ParadoxPanel.getInstance();
panel.setCommunicator(communicator);
communicator.setListeners(Arrays.asList(panel));
communicator.startLoginSequence();
scheduler.scheduleWithFixedDelay(() -> {
refreshMemoryMap(communicator, false);
}, 7, 5, TimeUnit.SECONDS);
} catch (Exception e) {
logger.error("Exception: ", e);
System.exit(0);
}
}
private static void refreshMemoryMap(IParadoxCommunicator communicator, boolean withEpromValues) {
logger.debug("Refreshing memory map");
communicator.refreshMemoryMap();
ParadoxPanel panel = ParadoxPanel.getInstance();
panel.getPartitions().stream().forEach(partition -> logger.debug("Partition={}", partition));
panel.getZones().stream().filter(zone -> zone.getId() == 19).forEach(zone -> logger.debug("Zone={}", zone));
logger.debug("PanelTime={}, ACLevel={}, DCLevel={}, BatteryLevel={}", panel.getPanelTime(), panel.getVdcLevel(),
panel.getDcLevel(), panel.getBatteryLevel());
}
private static void readArguments(String[] args) {
MainMethodArgParser parser = new MainMethodArgParser(args);
logger.info("Arguments retrieved successfully from CLI.");
ip150Password = parser.getPassword();
logger.info("IP150 Password: {}", ip150Password);
pcPassword = parser.getPcPassword();
logger.info("PC Password: {}", pcPassword);
ipAddress = parser.getIpAddress();
logger.info("IP150 IP Address: {}", ipAddress);
port = new Integer(parser.getPort());
logger.info("IP150 port: {}", port);
}
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2020 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 main;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MainMethodArgParser} Helper class to parse the arguments of main method, which is used for direct
* communication testing.
*
* @author Konstantin Polihronov - Initial contribution
*/
public class MainMethodArgParser {
private final Logger logger = LoggerFactory.getLogger(MainMethodArgParser.class);
private static final int NUMBER_OF_ARGUMENTS = 8;
private static final String USAGE_TEXT = "Usage: application --password <YOUR_PASSWORD_FOR_IP150> --pc_password <your PC_password> --ip_address <address of IP150> --port <port of Paradox>\n (pc password default is 0000, can be obtained by checking section 3012), default port is 10000";
private static final String KEY_PREFIX = "--";
private static final String PASSWORD_KEY = "--password";
private static final String PC_PASSWORD_KEY = "--pc_password";
private static final String IP_ADDRESS_KEY = "--ip_address";
private static final String PORT_KEY = "--port";
private static final List<String> ARGUMENT_KEYS = Arrays.asList(PASSWORD_KEY, PC_PASSWORD_KEY, IP_ADDRESS_KEY,
PORT_KEY);
private Map<String, String> argumentsMap;
public MainMethodArgParser(String[] args) {
this.argumentsMap = parseArguments(args);
validateArguments();
}
private Map<String, String> parseArguments(String[] args) {
if (args == null || args.length < NUMBER_OF_ARGUMENTS) {
logger.error(USAGE_TEXT);
throw new IllegalArgumentException("Arguments= " + Arrays.asList(args));
}
Map<String, String> result = new HashMap<>();
for (int i = 0; i < args.length;) {
if (!args[i].startsWith(KEY_PREFIX)) {
throw new IllegalArgumentException("Argument " + args[i] + " does not start with --");
}
String key = args[i];
String value;
if (args[i + 1] != null && args[i + 1].startsWith(KEY_PREFIX)) {
value = null;
i++;
} else {
value = args[i + 1];
i += 2;
}
result.put(key, value);
}
return result;
}
private void validateArguments() {
for (String argKey : ARGUMENT_KEYS) {
String value = argumentsMap.get(argKey);
if (value == null) {
logger.error(USAGE_TEXT);
throw new IllegalArgumentException("Argument " + argKey + "is mandatory");
}
}
}
public String getPassword() {
return argumentsMap.get(PASSWORD_KEY);
}
public String getPcPassword() {
return argumentsMap.get(PC_PASSWORD_KEY);
}
public String getIpAddress() {
return argumentsMap.get(IP_ADDRESS_KEY);
}
public String getPort() {
return argumentsMap.get(PORT_KEY);
}
}

View File

@@ -0,0 +1 @@
org.slf4j.simpleLogger.defaultLogLevel=trace

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 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 tests;
import org.junit.Assert;
import org.junit.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.util.ParadoxUtil;
/**
* The {@link TestCreateCommandPayload} This test tests the proper build of command payload object for partition entity
* with all types of commands.
*
* @author Konstantin Polihronov - Initial contribution
*/
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);
assertNibble(partitionNumber, command, payload);
}
}
}
private void assertNibble(int partitionNumber, PartitionCommand command, CommandPayload payload) {
byte[] bytes = payload.getBytes();
int payloadIndexOfByteToCheck = 6 + (partitionNumber - 1) / 2;
byte byteValue = bytes[payloadIndexOfByteToCheck];
if ((partitionNumber - 1) % 2 == 0) {
Assert.assertTrue(ParadoxUtil.getHighNibble(byteValue) == command.getCommand());
} else {
Assert.assertTrue(ParadoxUtil.getLowNibble(byteValue) == command.getCommand());
}
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 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 tests;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler;
import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand;
import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
/**
* The {@link TestEncryptionHandler} This test tests various functions from ParadoxUtils class
*
* @author Konstantin Polihronov - Initial contribution
*/
public class TestEncryptionHandler {
private static final String INPUT_STRING = "My test string for encryption.";
private static final String KEY = "MyKeyToEncrypt";
@Test
public void testEncryptDecryptString() {
EncryptionHandler.getInstance().updateKey(ParadoxUtil.getBytesFromString(KEY));
byte[] originalBytes = ParadoxUtil.getBytesFromString(INPUT_STRING);
ParadoxUtil.printByteArray("Original=", originalBytes);
byte[] encrypted = EncryptionHandler.getInstance().encrypt(originalBytes);
assertNotEquals(originalBytes, encrypted);
byte[] decrypted = EncryptionHandler.getInstance().decrypt(encrypted);
byte[] result = decrypted.length != originalBytes.length ? Arrays.copyOf(decrypted, originalBytes.length)
: decrypted;
ParadoxUtil.printByteArray("Result=", result);
assertEquals(originalBytes.length, result.length);
assertEquals(INPUT_STRING, new String(result));
}
private static final byte[] ENCRYPTION_KEY_BYTES = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
private static final byte[] ENCRYPTED_EXPECTED2 = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x09, (byte) 0xF0, 0x00, 0x00,
0x01, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE,
(byte) 0xF9, 0x11, 0x5A, (byte) 0xD7, 0x7C, (byte) 0xCB, (byte) 0xF4, 0x75, (byte) 0xB0, 0x49, (byte) 0xC3,
0x11, 0x1A, 0x41, (byte) 0x94, (byte) 0xE0 };
@Test
public void testCreateAndEncryptStartingPacket() {
ParadoxIPPacket paradoxIPPacket = new ParadoxIPPacket(ENCRYPTION_KEY_BYTES, false)
.setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
EncryptionHandler.getInstance().updateKey(ENCRYPTION_KEY_BYTES);
paradoxIPPacket.encrypt();
final byte[] packetBytes = paradoxIPPacket.getBytes();
ParadoxUtil.printByteArray("Expected=", ENCRYPTED_EXPECTED2);
ParadoxUtil.printByteArray("Packet= ", packetBytes);
Assert.assertTrue(Arrays.equals(packetBytes, ENCRYPTED_EXPECTED2));
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 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 tests;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.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.exceptions.ParadoxException;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TestGetBytes} This test tests creation of IP packet and it's getBytes() method
*
* @author Konstantin Polihronov - Initial contribution
*/
public class TestGetBytes {
private static final int PARTITION_NUMBER = 1;
static {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "TRACE");
}
private final static Logger logger = LoggerFactory.getLogger(ParadoxUtil.class);
private static final byte[] EXPECTED1 = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x08, (byte) 0xF0, 0x00, 0x00, 0x01,
(byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
private static final byte[] PAYLOAD = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
@Test
public void testParadoxIPPacket() {
ParadoxIPPacket paradoxIPPacket = new ParadoxIPPacket(PAYLOAD, false)
.setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
final byte[] packetBytes = paradoxIPPacket.getBytes();
ParadoxUtil.printByteArray("Expected =", EXPECTED1);
ParadoxUtil.printByteArray("Packet =", packetBytes);
Assert.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 };
@Test
public void testCommandPayload() {
CommandPayload payload = new CommandPayload(PARTITION_NUMBER, PartitionCommand.ARM);
final byte[] packetBytes = payload.getBytes();
ParadoxUtil.printByteArray("Expected =", EXPECTED_COMMAND_PAYLOAD);
ParadoxUtil.printByteArray("Result =", packetBytes);
Assert.assertTrue(Arrays.equals(packetBytes, EXPECTED_COMMAND_PAYLOAD));
}
private static final byte[] EXPECTED_MEMORY_PAYLOAD = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x08, (byte) 0xF0, 0x00,
0x00, 0x01, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10 };
@Test
public void testMemoryRequestPayload() throws ParadoxException {
int address = 0x3A6B + (PARTITION_NUMBER) * 107;
byte labelLength = 16;
IPayload payload = new EpromRequestPayload(address, labelLength);
byte[] bytes = payload.getBytes();
ParadoxUtil.printByteArray("Expected =", EXPECTED_MEMORY_PAYLOAD);
ParadoxUtil.printByteArray("Result =", bytes);
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 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 tests;
import org.junit.Assert;
import org.junit.Test;
import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
/**
* The {@link TestParadoxUtil} This test tests various functions from ParadoxUtils class
*
* @author Konstantin Polihronov - Initial contribution
*/
public class TestParadoxUtil {
@Test
public void testExtendArray() {
byte[] arrayToExtend = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x59 };
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,
(byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE };
Assert.assertArrayEquals(EXPECTED_RESULT, 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[] EXPECTED_RESULT = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
Assert.assertArrayEquals(EXPECTED_RESULT, mergedArrays);
}
}