]> git.basschouten.com Git - openhab-addons.git/blob
0e8bcc593a3dc0910d01ac34dd4692420be0d77b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.paradoxalarm.internal.communication;
14
15 import java.io.IOException;
16 import java.util.Arrays;
17 import java.util.concurrent.TimeUnit;
18
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;
28
29 /**
30  * The {@link CommunicationState}. This is enum based state machine used mostly for the logon process orchestration.
31  *
32  * @author Konstantin Polihronov - Initial contribution
33  */
34 public enum CommunicationState implements IResponseReceiver {
35     START {
36
37         @Override
38         protected CommunicationState nextState() {
39             return STEP2;
40         }
41
42         @Override
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));
48             }
49             ParadoxIPPacket packet = new ParadoxIPPacket(ParadoxUtil.getBytesFromString(password), false)
50                     .setCommand(HeaderCommand.CONNECT_TO_IP_MODULE);
51             sendLogonPhasePacket(communicator, packet);
52         }
53
54         @Override
55         protected boolean isPhaseSuccess(IResponse response) {
56             byte payloadResponseByte = response.getPayload()[0];
57             if (payloadResponseByte == 0x00) {
58                 logger.info("Login - Login to IP150 - OK");
59                 return true;
60             }
61
62             byte headerResponseByte = response.getHeader()[4];
63             switch (headerResponseByte) {
64                 case 0x30:
65                     logger.warn("Login - Login to IP150 failed - Incorrect password");
66                     break;
67                 case 0x78:
68                 case 0x79:
69                     logger.warn("Login - IP module is busy");
70                     break;
71             }
72
73             switch (payloadResponseByte) {
74                 case 0x01:
75                     logger.warn("Login - Invalid password");
76                     break;
77                 case 0x02:
78                 case 0x04:
79                     logger.warn("Login - User already connected");
80                     break;
81                 default:
82                     logger.warn("Login - Connection refused");
83             }
84             return false;
85         }
86
87         @Override
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);
95                 }
96
97                 logger.debug("Phase {} completed successfully.", this);
98                 nextState().runPhase(communicator);
99             }
100         }
101     },
102     STEP2 {
103
104         @Override
105         protected CommunicationState nextState() {
106             return STEP3;
107         }
108
109         @Override
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);
115         }
116     },
117     STEP3 {
118
119         @Override
120         protected CommunicationState nextState() {
121             return STEP4;
122         }
123
124         @Override
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);
130         }
131     },
132     STEP4 {
133
134         @Override
135         protected CommunicationState nextState() {
136             return STEP5;
137         }
138
139         @Override
140         protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
141             logger.debug("Phase {}", this);
142             byte[] message4 = new byte[37];
143             message4[0] = 0x72;
144             ParadoxIPPacket packet = new ParadoxIPPacket(message4, true)
145                     .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
146             sendLogonPhasePacket(communicator, packet);
147         }
148
149         @Override
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);
156             } else {
157                 logger.warn("Received wrong response in phase {}. Response: {}", this, response);
158                 LOGOUT.runPhase(communicator);
159             }
160         }
161     },
162     STEP5 {
163
164         @Override
165         protected CommunicationState nextState() {
166             return STEP6;
167         }
168
169         @Override
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);
175         }
176     },
177     STEP6 {
178
179         @Override
180         protected CommunicationState nextState() {
181             return STEP7;
182         }
183
184         @Override
185         protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
186             logger.debug("Phase {}", this);
187             byte[] message6 = new byte[37];
188             message6[0] = 0x5F;
189             message6[1] = 0x20;
190             ParadoxIPPacket packet = new ParadoxIPPacket(message6, true)
191                     .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST);
192             sendLogonPhasePacket(communicator, packet);
193         }
194
195         @Override
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);
201         }
202     },
203     STEP7 {
204
205         @Override
206         protected CommunicationState nextState() {
207             return INITIALIZE_DATA;
208         }
209
210         @Override
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);
220             } else {
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);
224             }
225         }
226
227         private byte[] generateInitializationRequest(byte[] initializationMessage, byte[] pcPassword) {
228             return new byte[] {
229                     // Initialization command
230                     0x00,
231
232                     // Module address
233                     initializationMessage[1],
234
235                     // Not used
236                     0x00, 0x00,
237
238                     // Product ID
239                     initializationMessage[4],
240
241                     // Software version
242                     initializationMessage[5],
243
244                     // Software revision
245                     initializationMessage[6],
246
247                     // Software ID
248                     initializationMessage[7],
249
250                     // Module ID
251                     initializationMessage[8], initializationMessage[9],
252
253                     // PC Password
254                     pcPassword[0], pcPassword[1],
255
256                     // Modem speed
257                     0x08,
258
259                     // Winload type ID
260                     0x30,
261
262                     // User code (aligned with PAI)
263                     0x02, 0x02, 0x00,
264
265                     // Module serial number
266                     initializationMessage[17], initializationMessage[18], initializationMessage[19],
267                     initializationMessage[20],
268
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],
273
274                     // Not used
275                     0x00, 0x00, 0x00, 0x00,
276
277                     // Source ID
278                     0x00,
279
280                     // Carrier length
281                     0x00,
282
283                     // Checksum
284                     0x00 };
285         }
286
287         @Override
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) {
293                     try {
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);
300                     }
301
302                     super.receiveResponse(response, communicator);
303                 }
304             }, 300, TimeUnit.MILLISECONDS);
305         }
306     },
307     INITIALIZE_DATA {
308
309         @Override
310         protected CommunicationState nextState() {
311             return ONLINE;
312         }
313
314         @Override
315         protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
316             if (communicator instanceof IParadoxCommunicator comm) {
317                 comm.initializeData();
318             }
319             nextState().runPhase(communicator);
320         }
321     },
322     ONLINE {
323
324         @Override
325         protected CommunicationState nextState() {
326             return this;
327         }
328
329         @Override
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.");
334         }
335     },
336     LOGOUT {
337
338         @Override
339         protected CommunicationState nextState() {
340             return OFFLINE;
341         }
342
343         @Override
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
347
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);
353         }
354     },
355     OFFLINE {
356
357         @Override
358         protected CommunicationState nextState() {
359             return this;
360         }
361
362         @Override
363         protected void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args) {
364             if (communicator != null) {
365                 communicator.close();
366             }
367         }
368     };
369
370     protected final Logger logger = LoggerFactory.getLogger(CommunicationState.class);
371
372     private static CommunicationState currentState = CommunicationState.OFFLINE;
373
374     // This method is the entry of logon procedure.
375     public static void login(IParadoxInitialLoginCommunicator communicator) {
376         START.runPhase(communicator);
377     }
378
379     public static void logout(IParadoxInitialLoginCommunicator communicator) {
380         LOGOUT.runPhase(communicator);
381     }
382
383     protected abstract CommunicationState nextState();
384
385     protected abstract void runPhase(IParadoxInitialLoginCommunicator communicator, Object... args);
386
387     protected void runPhase(IParadoxInitialLoginCommunicator communicator) {
388         setCurrentState(this);
389         runPhase(communicator, new Object[0]);
390     }
391
392     protected void sendLogonPhasePacket(IParadoxInitialLoginCommunicator communicator, IPPacket packet) {
393         IRequest request = new LogonRequest(this, packet);
394         communicator.submitRequest(request);
395     }
396
397     @Override
398     public void receiveResponse(IResponse response, IParadoxInitialLoginCommunicator communicator) {
399         if (isPhaseSuccess(response)) {
400             logger.debug("Phase {} completed successfully.", this);
401             nextState().runPhase(communicator);
402         }
403     }
404
405     protected boolean isPhaseSuccess(IResponse response) {
406         return true;
407     }
408
409     public static CommunicationState getCurrentState() {
410         return currentState;
411     }
412
413     public static void setCurrentState(CommunicationState currentState) {
414         CommunicationState.currentState = currentState;
415     }
416 }