added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) + "]";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.slf4j.simpleLogger.defaultLogLevel=trace
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user