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