2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.paradoxalarm.internal.communication;
15 import java.io.IOException;
16 import java.util.Arrays;
17 import java.util.concurrent.TimeUnit;
19 import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler;
20 import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand;
21 import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType;
22 import org.openhab.binding.paradoxalarm.internal.communication.messages.IPPacket;
23 import org.openhab.binding.paradoxalarm.internal.communication.messages.IpMessagesConstants;
24 import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket;
25 import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
30 * The {@link CommunicationState}. This is enum based state machine used mostly for the logon process orchestration.
32 * @author Konstantin Polihronov - Initial contribution
34 public enum CommunicationState implements IResponseReceiver {
38 protected CommunicationState nextState() {
43 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
44 String password = communicator.getPassword();
45 logger.debug("Phase {}", this);
46 if (communicator.isEncrypted()) {
47 EncryptionHandler.getInstance().updateKey(ParadoxUtil.getBytesFromString(password));
49 ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxUtil.getBytesFromString(password), false)
50 .setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
51 sendLogonPhasePacket(communicator, packet);
55 protected boolean isPhaseSuccess(IResponse response) {
56 byte payloadResponseByte = response.getPayload()[0];
57 if (payloadResponseByte == 0x00) {
58 logger.info("Login - Login to IP150 - OK");
62 byte headerResponseByte = response.getHeader()[4];
63 switch (headerResponseByte) {
65 logger.warn("Login - Login to IP150 failed - Incorrect password");
69 logger.warn("Login - IP module is busy");
73 switch (payloadResponseByte) {
75 logger.warn("Login - Invalid password");
79 logger.warn("Login - User already connected");
82 logger.warn("Login - Connection refused");
88 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
89 if (isPhaseSuccess(response)) {
90 // Retrieve new encryption key from first packet response and update it in encryption handler
91 if (communicator.isEncrypted()) {
92 byte[] payload = response.getPayload();
93 byte[] receivedKey = Arrays.copyOfRange(payload, 1, 17);
94 EncryptionHandler.getInstance().updateKey(receivedKey);
97 logger.debug("Phase {} completed successfully.", this);
98 nextState().runPhase(communicator);
105 protected CommunicationState nextState() {
110 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
111 logger.debug("Phase {}", this);
112 ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxIPPacket.EMPTY_PAYLOAD, false)
113 .setCommand(HeaderCommand.LOGIN_COMMAND1);
114 sendLogonPhasePacket(communicator, packet);
120 protected CommunicationState nextState() {
125 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
126 logger.debug("Phase {}", this);
127 ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxIPPacket.EMPTY_PAYLOAD, false)
128 .setCommand(HeaderCommand.LOGIN_COMMAND2);
129 sendLogonPhasePacket(communicator, packet);
135 protected CommunicationState nextState() {
140 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
141 logger.debug("Phase {}", this);
142 byte[] message4 = new byte[37];
144 ParadoxIPPacket packet = new ParadoxIPPacket(message4, true)
145 .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
146 sendLogonPhasePacket(communicator, packet);
150 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
151 byte[] payload = response.getPayload();
152 if (payload != null && payload.length >= 37) {
153 communicator.setPanelInfoBytes(payload);
154 logger.debug("Phase {} completed successfully.", this);
155 nextState().runPhase(communicator);
157 logger.warn("Received wrong response in phase {}. Response: {}", this, response);
158 LOGOUT.runPhase(communicator);
165 protected CommunicationState nextState() {
170 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
171 logger.debug("Phase {}", this);
172 ParadoxIPPacket packet = new ParadoxIPPacket(IpMessagesConstants.UNKNOWN_IP150_REQUEST_MESSAGE01, false)
173 .setCommand(HeaderCommand.SERIAL_CONNECTION_INITIATED);
174 sendLogonPhasePacket(communicator, packet);
180 protected CommunicationState nextState() {
185 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
186 logger.debug("Phase {}", this);
187 byte[] message6 = new byte[37];
190 ParadoxIPPacket packet = new ParadoxIPPacket(message6, true)
191 .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
192 sendLogonPhasePacket(communicator, packet);
196 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
197 byte[] payload = response.getPayload();
198 ParadoxUtil.printPacket("Init communication sub array: ", payload);
199 logger.debug("Phase {} completed successfully.", this);
200 nextState().runPhase(communicator, payload);
206 protected CommunicationState nextState() {
207 return INITIALIZE_DATA;
211 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
212 if (args != null && args.length == 1) {
213 byte[] initializationMessage = (byte[]) args[0];
214 logger.debug("Phase {}", this);
215 byte[] message7 = generateInitializationRequest(initializationMessage,
216 communicator.getPcPasswordBytes());
217 ParadoxIPPacket packet = new ParadoxIPPacket(message7, true)
218 .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
219 sendLogonPhasePacket(communicator, packet);
221 logger.error("Error in step {}. Missing argument {}", this, args);
222 throw new IllegalArgumentException(
223 "Initialization message not send in request for phase + " + this + ". Arguments= " + args);
227 private byte[] generateInitializationRequest(byte[] initializationMessage, byte[] pcPassword) {
229 // Initialization command
233 initializationMessage[1],
239 initializationMessage[4],
242 initializationMessage[5],
245 initializationMessage[6],
248 initializationMessage[7],
251 initializationMessage[8], initializationMessage[9],
254 pcPassword[0], pcPassword[1],
262 // User code (aligned with PAI)
265 // Module serial number
266 initializationMessage[17], initializationMessage[18], initializationMessage[19],
267 initializationMessage[20],
269 // EVO section 3030-3038 data
270 initializationMessage[21], initializationMessage[22], initializationMessage[23],
271 initializationMessage[24], initializationMessage[25], initializationMessage[26],
272 initializationMessage[27], initializationMessage[28], initializationMessage[29],
275 0x00, 0x00, 0x00, 0x00,
288 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
289 // UGLY - this is the handling of ghost packet which appears after the logon sequence
290 // Read ghost packet affter 300ms then continue with normal flow
291 communicator.getScheduler().schedule(() -> {
292 if (communicator instanceof GenericCommunicator genericCommunicator) {
294 byte[] value = new byte[256];
295 int packetLength = genericCommunicator.getRx().read(value);
296 logger.debug("Reading ghost packet with length={}", packetLength);
297 ParadoxUtil.printPacket("Reading ghost packet", value);
298 } catch (IOException e) {
299 logger.debug("Error reading ghost packet.", e);
302 super.receiveResponse(response, communicator);
304 }, 300, TimeUnit.MILLISECONDS);
310 protected CommunicationState nextState() {
315 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
316 if (communicator instanceof IParadoxCommunicator comm) {
317 comm.initializeData();
319 nextState().runPhase(communicator);
325 protected CommunicationState nextState() {
330 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
331 logger.debug("Phase {}. Setting communicator to status ONLINE.", this);
332 communicator.setOnline(true);
333 logger.info("Successfully established communication with the panel.");
339 protected CommunicationState nextState() {
344 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
345 // For some reason after sending logout packet the connection gets reset from the other end
346 // currently workaround is to run directly offline phase, i.e. close socket from our end
348 // logger.info("Logout packet sent to IP150.");
349 // ParadoxIPPacket logoutPacket = new ParadoxIPPacket(IpMessagesConstants.LOGOUT_MESAGE_BYTES, true)
350 // .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
351 // sendPacket(communicator, logoutPacket);
352 nextState().runPhase(communicator);
358 protected CommunicationState nextState() {
363 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
364 if (communicator != null) {
365 communicator.close();
370 protected final Logger logger = LoggerFactory.getLogger(CommunicationState.class);
372 private static CommunicationState currentState = CommunicationState.OFFLINE;
374 // This method is the entry of logon procedure.
375 public static void login(IParadoxInitialLoginCommunicator communicator) {
376 START.runPhase(communicator);
379 public static void logout(IParadoxInitialLoginCommunicator communicator) {
380 LOGOUT.runPhase(communicator);
383 protected abstract CommunicationState nextState();
385 protected abstract void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args);
387 protected void runPhase(IParadoxInitialLoginCommunicator communicator) {
388 setCurrentState(this);
389 runPhase(communicator, new Object[0]);
392 protected void sendLogonPhasePacket(IParadoxInitialLoginCommunicator communicator, IPPacket packet) {
393 IRequest request = new LogonRequest(this, packet);
394 communicator.submitRequest(request);
398 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
399 if (isPhaseSuccess(response)) {
400 logger.debug("Phase {} completed successfully.", this);
401 nextState().runPhase(communicator);
405 protected boolean isPhaseSuccess(IResponse response) {
409 public static CommunicationState getCurrentState() {
413 public static void setCurrentState(CommunicationState currentState) {
414 CommunicationState.currentState = currentState;