2 * Copyright (c) 2010-2022 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.model.ParadoxPanel;
26 import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * The {@link CommunicationState}. This is enum based state machine used mostly for the logon process orchestration.
33 * @author Konstantin Polihronov - Initial contribution
35 public enum CommunicationState implements IResponseReceiver {
39 protected CommunicationState nextState() {
44 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
45 String password = communicator.getPassword();
46 logger.debug("Phase {}", this);
47 if (communicator.isEncrypted()) {
48 EncryptionHandler.getInstance().updateKey(ParadoxUtil.getBytesFromString(password));
50 ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxUtil.getBytesFromString(password), false)
51 .setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
52 sendLogonPhasePacket(communicator, packet);
56 protected boolean isPhaseSuccess(IResponse response) {
57 byte payloadResponseByte = response.getPayload()[0];
58 if (payloadResponseByte == 0x00) {
59 logger.info("Login - Login to IP150 - OK");
63 byte headerResponseByte = response.getHeader()[4];
64 switch (headerResponseByte) {
66 logger.warn("Login - Login to IP150 failed - Incorrect password");
70 logger.warn("Login - IP module is busy");
74 switch (payloadResponseByte) {
76 logger.warn("Login - Invalid password");
80 logger.warn("Login - User already connected");
83 logger.warn("Login - Connection refused");
89 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
90 if (isPhaseSuccess(response)) {
91 // Retrieve new encryption key from first packet response and update it in encryption handler
92 if (communicator.isEncrypted()) {
93 byte[] payload = response.getPayload();
94 byte[] receivedKey = Arrays.copyOfRange(payload, 1, 17);
95 EncryptionHandler.getInstance().updateKey(receivedKey);
98 logger.debug("Phase {} completed successfully.", this);
99 nextState().runPhase(communicator);
106 protected CommunicationState nextState() {
111 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
112 logger.debug("Phase {}", this);
113 ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxIPPacket.EMPTY_PAYLOAD, false)
114 .setCommand(HeaderCommand.LOGIN_COMMAND1);
115 sendLogonPhasePacket(communicator, packet);
121 protected CommunicationState nextState() {
126 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
127 logger.debug("Phase {}", this);
128 ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxIPPacket.EMPTY_PAYLOAD, false)
129 .setCommand(HeaderCommand.LOGIN_COMMAND2);
130 sendLogonPhasePacket(communicator, packet);
136 protected CommunicationState nextState() {
141 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
142 logger.debug("Phase {}", this);
143 byte[] message4 = new byte[37];
145 ParadoxIPPacket packet = new ParadoxIPPacket(message4, true)
146 .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
147 sendLogonPhasePacket(communicator, packet);
151 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
152 byte[] payload = response.getPayload();
153 if (payload != null && payload.length >= 37) {
154 communicator.setPanelInfoBytes(payload);
155 logger.debug("Phase {} completed successfully.", this);
156 nextState().runPhase(communicator);
158 logger.warn("Received wrong response in phase {}. Response: {}", this, response);
159 LOGOUT.runPhase(communicator);
166 protected CommunicationState nextState() {
171 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
172 logger.debug("Phase {}", this);
173 ParadoxIPPacket packet = new ParadoxIPPacket(IpMessagesConstants.UNKNOWN_IP150_REQUEST_MESSAGE01, false)
174 .setCommand(HeaderCommand.SERIAL_CONNECTION_INITIATED);
175 sendLogonPhasePacket(communicator, packet);
181 protected CommunicationState nextState() {
186 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
187 logger.debug("Phase {}", this);
188 byte[] message6 = new byte[37];
191 ParadoxIPPacket packet = new ParadoxIPPacket(message6, true)
192 .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
193 sendLogonPhasePacket(communicator, packet);
197 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
198 byte[] payload = response.getPayload();
199 ParadoxUtil.printPacket("Init communication sub array: ", payload);
200 logger.debug("Phase {} completed successfully.", this);
201 nextState().runPhase(communicator, payload);
207 protected CommunicationState nextState() {
208 return INITIALIZE_DATA;
212 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
213 if (args != null && args.length == 1) {
214 byte[] initializationMessage = (byte[]) args[0];
215 logger.debug("Phase {}", this);
216 byte[] message7 = generateInitializationRequest(initializationMessage,
217 communicator.getPcPasswordBytes());
218 ParadoxIPPacket packet = new ParadoxIPPacket(message7, true)
219 .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
220 sendLogonPhasePacket(communicator, packet);
222 logger.error("Error in step {}. Missing argument {}", this, args);
223 throw new IllegalArgumentException(
224 "Initialization message not send in request for phase + " + this + ". Arguments= " + args);
228 private byte[] generateInitializationRequest(byte[] initializationMessage, byte[] pcPassword) {
229 byte[] message7 = new byte[] {
230 // Initialization command
234 initializationMessage[1],
240 initializationMessage[4],
243 initializationMessage[5],
246 initializationMessage[6],
249 initializationMessage[7],
252 initializationMessage[8], initializationMessage[9],
255 pcPassword[0], pcPassword[1],
263 // User code (aligned with PAI)
266 // Module serial number
267 initializationMessage[17], initializationMessage[18], initializationMessage[19],
268 initializationMessage[20],
270 // EVO section 3030-3038 data
271 initializationMessage[21], initializationMessage[22], initializationMessage[23],
272 initializationMessage[24], initializationMessage[25], initializationMessage[26],
273 initializationMessage[27], initializationMessage[28], initializationMessage[29],
276 0x00, 0x00, 0x00, 0x00,
290 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
291 // UGLY - this is the handling of ghost packet which appears after the logon sequence
292 // Read ghost packet affter 300ms then continue with normal flow
293 communicator.getScheduler().schedule(() -> {
294 if (communicator instanceof GenericCommunicator) {
296 GenericCommunicator genCommunicator = (GenericCommunicator) communicator;
297 byte[] value = new byte[256];
298 int packetLength = genCommunicator.getRx().read(value);
299 logger.debug("Reading ghost packet with length={}", packetLength);
300 ParadoxUtil.printPacket("Reading ghost packet", value);
301 } catch (IOException e) {
302 logger.debug("Error reading ghost packet.", e);
305 super.receiveResponse(response, communicator);
307 }, 300, TimeUnit.MILLISECONDS);
313 protected CommunicationState nextState() {
318 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
319 if (communicator instanceof IParadoxCommunicator) {
320 IParadoxCommunicator comm = (IParadoxCommunicator) communicator;
321 comm.initializeData();
323 nextState().runPhase(communicator);
329 protected CommunicationState nextState() {
334 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
335 logger.debug("Phase {}. Setting communicator to status ONLINE.", this);
336 communicator.setOnline(true);
337 logger.info("Successfully established communication with the panel.");
343 protected CommunicationState nextState() {
348 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
349 // For some reason after sending logout packet the connection gets reset from the other end
350 // currently workaround is to run directly offline phase, i.e. close socket from our end
352 // logger.info("Logout packet sent to IP150.");
353 // ParadoxIPPacket logoutPacket = new ParadoxIPPacket(IpMessagesConstants.LOGOUT_MESAGE_BYTES, true)
354 // .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST).setUnknown0((byte) 0x14);
355 // sendPacket(communicator, logoutPacket);
356 nextState().runPhase(communicator);
362 protected CommunicationState nextState() {
367 protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
368 if (communicator != null) {
369 communicator.close();
371 ParadoxPanel.getInstance().dispose();
375 protected final Logger logger = LoggerFactory.getLogger(CommunicationState.class);
377 private static CommunicationState currentState = CommunicationState.OFFLINE;
379 // This method is the entry of logon procedure.
380 public static void login(IParadoxInitialLoginCommunicator communicator) {
381 START.runPhase(communicator);
384 public static void logout(IParadoxInitialLoginCommunicator communicator) {
385 LOGOUT.runPhase(communicator);
388 protected abstract CommunicationState nextState();
390 protected abstract void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args);
392 protected void runPhase(IParadoxInitialLoginCommunicator communicator) {
393 setCurrentState(this);
394 runPhase(communicator, new Object[0]);
397 protected void sendLogonPhasePacket(IParadoxInitialLoginCommunicator communicator, IPPacket packet) {
398 IRequest request = new LogonRequest(this, packet);
399 communicator.submitRequest(request);
403 public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
404 if (isPhaseSuccess(response)) {
405 logger.debug("Phase {} completed successfully.", this);
406 nextState().runPhase(communicator);
410 protected boolean isPhaseSuccess(IResponse response) {
414 public static CommunicationState getCurrentState() {
418 public static void setCurrentState(CommunicationState currentState) {
419 CommunicationState.currentState = currentState;