]> git.basschouten.com Git - openhab-addons.git/blob
16f4c1f35ec1a67bcfece3c9a167f835bc3351e6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.velux.internal.bridge.slip;
14
15 import java.text.ParseException;
16 import java.util.TreeSet;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.openhab.binding.velux.internal.VeluxBindingConstants;
20 import org.openhab.binding.velux.internal.bridge.VeluxBridge;
21 import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
22 import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
23 import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
24 import org.openhab.binding.velux.internal.bridge.slip.io.Connection;
25 import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
26 import org.openhab.binding.velux.internal.bridge.slip.utils.SlipEncoding;
27 import org.openhab.binding.velux.internal.bridge.slip.utils.SlipRFC1055;
28 import org.openhab.binding.velux.internal.development.Threads;
29 import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
30 import org.openhab.binding.velux.internal.things.VeluxKLFAPI.CommandNumber;
31 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * SLIP-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
37  * <P>
38  * It provides methods for pre- and postcommunication
39  * as well as a common method for the real communication.
40  * <P>
41  * In addition to the generic {@link VeluxBridge} methods, i.e.
42  * <UL>
43  * <LI>{@link VeluxBridge#bridgeLogin} for pre-communication,</LI>
44  * <LI>{@link VeluxBridge#bridgeLogout} for post-communication,</LI>
45  * <LI>{@link VeluxBridge#bridgeCommunicate} as method for the common communication,</LI>
46  * <LI>{@link VeluxBridge#bridgeAPI} as interfacing method to all interaction prototypes,</LI>
47  * </UL>
48  * the following class access methods provides the protocol-specific implementation:
49  * <UL>
50  * <LI>{@link #bridgeDirectCommunicate} for SLIP-based communication.</LI>
51  * <LI>{@link #bridgeAPI} for return all defined protocol-specific implementations which are provided by
52  * {@link org.openhab.binding.velux.internal.bridge.slip.SlipBridgeAPI SlipBridgeAPI}.</LI>
53  * </UL>
54  *
55  * @author Guenther Schreiner - Initial contribution.
56  */
57 @NonNullByDefault
58 public class SlipVeluxBridge extends VeluxBridge {
59     private final Logger logger = LoggerFactory.getLogger(SlipVeluxBridge.class);
60
61     /*
62      * ***************************
63      * ***** Private Objects *****
64      */
65
66     /**
67      * Timeout for sequence of one request with (optional multiple) responses.
68      * <P>
69      * This can only happen if there is an unexpected (undocumented) occurrence of events
70      * which has to be discussed along the Velux API documentation.
71      */
72     static final long COMMUNICATION_TIMEOUT_MSECS = 60000L;
73     /**
74      * Wait interval within sequence after the request and no immediate response.
75      * <P>
76      * This can happen if the bridge is busy due to a specific command to query and collect information from other
77      * devices via the io-homecontrol protocol.
78      */
79     static final long COMMUNICATION_RETRY_MSECS = 5000L;
80
81     /*
82      * ***************************
83      * ***** Private Objects *****
84      */
85
86     private final byte[] emptyPacket = new byte[0];
87     private Connection connection = new Connection();
88
89     /**
90      * Handler passing the interface methods to other classes.
91      * Can be accessed via method {@link org.openhab.binding.velux.internal.bridge.common.BridgeAPI BridgeAPI}.
92      */
93     private BridgeAPI bridgeAPI;
94
95     /**
96      * Constructor.
97      * <P>
98      * Inherits the initialization of the binding-wide instance for dealing for common informations and
99      * initializes the Velux bridge connection settings.
100      *
101      * @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
102      */
103     public SlipVeluxBridge(VeluxBridgeInstance bridgeInstance) {
104         super(bridgeInstance);
105         logger.trace("SlipVeluxBridge(constructor) called.");
106         bridgeAPI = new SlipBridgeAPI(bridgeInstance);
107         supportedProtocols = new TreeSet<>();
108         supportedProtocols.add("slip");
109         logger.trace("SlipVeluxBridge(constructor) done.");
110     }
111
112     /**
113      * Destructor.
114      * <P>
115      * De-initializes the binding-wide instance.
116      */
117     @Override
118     public void shutdown() {
119         logger.trace("shutdown() called.");
120         connection.resetConnection();
121         logger.trace("shutdown() done.");
122     }
123
124     /**
125      * Provides information about the base-level communication method and
126      * any kind of available gateway interactions.
127      * <P>
128      * <B>Note:</B> the implementation within this class {@link SlipVeluxBridge} as inherited from {@link VeluxBridge}
129      * will return the protocol-specific class implementations.
130      * <P>
131      * The information will be initialized by the corresponding API class {@link SlipBridgeAPI}.
132      *
133      * @return <b>bridgeAPI</b> of type {@link BridgeAPI} contains all possible methods.
134      */
135     @Override
136     public BridgeAPI bridgeAPI() {
137         logger.trace("bridgeAPI() called.");
138         return bridgeAPI;
139     }
140
141     /**
142      * <B>Method as implementation of abstract superclass method.</B>
143      * <P>
144      * Initializes a client/server communication towards <b>Velux</b> veluxBridge
145      * based on the Basic I/O interface {@link Connection#io} and parameters
146      * passed as arguments (see below).
147      *
148      * @param communication Structure of interface type {@link SlipBridgeCommunicationProtocol} describing the intended
149      *            communication, that is request and response interactions as well as appropriate URL definition.
150      * @param useAuthentication boolean flag to decide whether to use authenticated communication.
151      * @return <b>success</b> of type boolean which signals the success of the communication.
152      *
153      */
154     @Override
155     protected boolean bridgeDirectCommunicate(BridgeCommunicationProtocol communication, boolean useAuthentication) {
156         logger.trace("bridgeDirectCommunicate(BCP: {},{}authenticated) called.", communication.name(),
157                 useAuthentication ? "" : "un");
158         return bridgeDirectCommunicate((SlipBridgeCommunicationProtocol) communication, useAuthentication);
159     }
160
161     /**
162      * Returns the timestamp in milliseconds since Unix epoch
163      * of last (potentially faulty) communication.
164      *
165      * @return timestamp in milliseconds.
166      */
167     @Override
168     public long lastCommunication() {
169         return connection.lastCommunication();
170     }
171
172     /**
173      * Returns the timestamp in milliseconds since Unix epoch
174      * of last successful communication.
175      *
176      * @return timestamp in milliseconds.
177      */
178     @Override
179     public long lastSuccessfulCommunication() {
180         return connection.lastSuccessfulCommunication();
181     }
182
183     /**
184      * Initializes a client/server communication towards <b>Velux</b> veluxBridge
185      * based on the Basic I/O interface {@link Connection#io} and parameters
186      * passed as arguments (see below).
187      *
188      * @param communication Structure of interface type {@link SlipBridgeCommunicationProtocol} describing the
189      *            intended communication, that is request and response interactions as well as appropriate URL
190      *            definition.
191      * @param useAuthentication boolean flag to decide whether to use authenticated communication.
192      * @return <b>success</b> of type boolean which signals the success of the communication.
193      */
194     private synchronized boolean bridgeDirectCommunicate(SlipBridgeCommunicationProtocol communication,
195             boolean useAuthentication) {
196         String host = this.bridgeInstance.veluxBridgeConfiguration().ipAddress;
197         logger.trace("bridgeDirectCommunicate({},{}authenticated) on {} called.", host, communication.name(),
198                 useAuthentication ? "" : "un");
199
200         assert this.bridgeInstance.veluxBridgeConfiguration().protocol.contentEquals("slip");
201
202         long communicationStartInMSecs = System.currentTimeMillis();
203
204         boolean isSequentialEnforced = this.bridgeInstance.veluxBridgeConfiguration().isSequentialEnforced;
205         boolean isProtocolTraceEnabled = this.bridgeInstance.veluxBridgeConfiguration().isProtocolTraceEnabled;
206
207         // From parameters
208         short command = communication.getRequestCommand().toShort();
209         byte[] data = communication.getRequestDataAsArrayOfBytes();
210         // For further use at different logging statements
211         String commandString = Command.get(command).toString();
212
213         if (isProtocolTraceEnabled) {
214             Threads.findDeadlocked();
215         }
216
217         logger.debug("bridgeDirectCommunicate({},{}authenticated) on {} initiated by {}.", host, commandString,
218                 useAuthentication ? "" : "un", Thread.currentThread());
219         boolean success = false;
220
221         communication: do {
222             if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
223                 logger.warn(
224                         "{} bridgeDirectCommunicate({}) on {}: communication handshake failed (unexpected sequence of requests/responses).",
225                         VeluxBindingConstants.BINDING_VALUES_SEPARATOR, communication.name(), host);
226                 break;
227             }
228
229             // Special handling
230             if (Command.get(command) == Command.GW_OPENHAB_CLOSE) {
231                 logger.trace("bridgeDirectCommunicate(): special command: shutting down connection.");
232                 connection.resetConnection();
233                 success = true;
234                 continue;
235             }
236
237             // Normal processing
238             logger.trace("bridgeDirectCommunicate() on {}: working on request {} with {} bytes of data.", host,
239                     commandString, data.length);
240             byte[] sendBytes = emptyPacket;
241             if (Command.get(command) == Command.GW_OPENHAB_RECEIVEONLY) {
242                 logger.trace(
243                         "bridgeDirectCommunicate() on {}: special command: determine whether there is any message waiting.",
244                         host);
245                 logger.trace("bridgeDirectCommunicate(): check for a waiting message.");
246                 if (!connection.isMessageAvailable()) {
247                     logger.trace("bridgeDirectCommunicate() on {}: no message waiting, aborting.", host);
248                     break communication;
249                 }
250                 logger.trace("bridgeDirectCommunicate() on {}: there is a message waiting.", host);
251             } else {
252                 SlipEncoding t = new SlipEncoding(command, data);
253                 if (!t.isValid()) {
254                     logger.warn("bridgeDirectCommunicate() on {}: SlipEncoding() failed, aborting.", host);
255                     break;
256                 }
257                 logger.trace("bridgeDirectCommunicate() on {}: transportEncoding={}.", host, t.toString());
258                 sendBytes = new SlipRFC1055().encode(t.toMessage());
259             }
260             do {
261                 if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
262                     logger.warn("bridgeDirectCommunicate() on {}: receive takes too long. Please report to maintainer.",
263                             host);
264                     break communication;
265                 }
266                 byte[] receivedPacket;
267                 try {
268                     if (sendBytes.length > 0) {
269                         logger.trace("bridgeDirectCommunicate() on {}: sending {} bytes.", host, sendBytes.length);
270                         if (isProtocolTraceEnabled) {
271                             logger.info("Sending command {}.", commandString);
272                         }
273                     } else {
274                         logger.trace("bridgeDirectCommunicate() on {}: initiating receive-only.", host);
275                     }
276                     // (Optionally) Send and receive packet.
277                     receivedPacket = connection.io(this.bridgeInstance, sendBytes);
278                     // Once being sent, it should never be sent again
279                     sendBytes = emptyPacket;
280                 } catch (Exception e) {
281                     logger.warn("bridgeDirectCommunicate() on {}: connection.io returns {}", host, e.getMessage());
282                     break communication;
283                 }
284                 logger.trace("bridgeDirectCommunicate() on {}: received packet {}.", host,
285                         new Packet(receivedPacket).toString());
286                 byte[] response;
287                 try {
288                     response = new SlipRFC1055().decode(receivedPacket);
289                 } catch (ParseException e) {
290                     logger.warn("bridgeDirectCommunicate() on {}: method SlipRFC1055() raised a decoding error: {}.",
291                             host, e.getMessage());
292                     break communication;
293                 }
294                 SlipEncoding tr = new SlipEncoding(response);
295                 if (!tr.isValid()) {
296                     logger.warn("bridgeDirectCommunicate() on {}: method SlipEncoding() raised a decoding error.",
297                             host);
298                     break communication;
299                 }
300                 short responseCommand = tr.getCommand();
301                 byte[] responseData = tr.getData();
302                 logger.debug("bridgeDirectCommunicate() on {}: working on response {} with {} bytes of data.", host,
303                         Command.get(responseCommand).toString(), responseData.length);
304                 if (isProtocolTraceEnabled) {
305                     logger.info("Received answer {}.", Command.get(responseCommand).toString());
306                 }
307                 // Handle some common (unexpected) answers
308                 switch (Command.get(responseCommand)) {
309                     case GW_NODE_INFORMATION_CHANGED_NTF:
310                         logger.trace("bridgeDirectCommunicate() on {}: received GW_NODE_INFORMATION_CHANGED_NTF.",
311                                 host);
312                         logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
313                         continue;
314                     case GW_NODE_STATE_POSITION_CHANGED_NTF:
315                         logger.trace(
316                                 "bridgeDirectCommunicate() on {}: received GW_NODE_STATE_POSITION_CHANGED_NTF, special processing of this packet.",
317                                 host);
318                         SCgetHouseStatus receiver = new SCgetHouseStatus();
319                         receiver.setResponse(responseCommand, responseData, isSequentialEnforced);
320                         if (receiver.isCommunicationSuccessful()) {
321                             logger.trace("bridgeDirectCommunicate() on {}: existingProducts().update() called.", host);
322                             bridgeInstance.existingProducts().update(new ProductBridgeIndex(receiver.getNtfNodeID()),
323                                     receiver.getNtfState(), receiver.getNtfCurrentPosition(), receiver.getNtfTarget());
324                         }
325                         logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
326                         continue;
327                     case GW_ERROR_NTF:
328                         switch (responseData[0]) {
329                             case 0:
330                                 logger.warn(
331                                         "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF on {} (Not further defined error), aborting.",
332                                         host, commandString);
333                                 break communication;
334                             case 1:
335                                 logger.warn(
336                                         "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Unknown Command or command is not accepted at this state) on {}, aborting.",
337                                         host, commandString);
338                                 break communication;
339                             case 2:
340                                 logger.warn(
341                                         "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (ERROR on Frame Structure) on {}, aborting.",
342                                         host, commandString);
343                                 break communication;
344                             case 7:
345                                 logger.trace(
346                                         "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Busy. Try again later) on {}, retrying.",
347                                         host, commandString);
348                                 sendBytes = emptyPacket;
349                                 continue;
350                             case 8:
351                                 logger.warn(
352                                         "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Bad system table index) on {}, aborting.",
353                                         host, commandString);
354                                 break communication;
355                             case 12:
356                                 logger.warn(
357                                         "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Not authenticated) on {}, aborting.",
358                                         host, commandString);
359                                 resetAuthentication();
360                                 break communication;
361                             default:
362                                 logger.warn(
363                                         "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF ({}) on {}, aborting.",
364                                         host, responseData[0], commandString);
365                                 break communication;
366                         }
367                     case GW_ACTIVATION_LOG_UPDATED_NTF:
368                         logger.info("bridgeDirectCommunicate() on {}: received GW_ACTIVATION_LOG_UPDATED_NTF.", host);
369                         logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
370                         continue;
371
372                     case GW_COMMAND_RUN_STATUS_NTF:
373                     case GW_COMMAND_REMAINING_TIME_NTF:
374                     case GW_SESSION_FINISHED_NTF:
375                         if (!isSequentialEnforced) {
376                             logger.trace(
377                                     "bridgeDirectCommunicate() on {}: response ignored due to activated parallelism, continue with receiving.",
378                                     host);
379                             continue;
380                         }
381
382                     default:
383                 }
384                 logger.trace("bridgeDirectCommunicate() on {}: passes back command {} and data {}.", host,
385                         new CommandNumber(responseCommand).toString(), new Packet(responseData).toString());
386                 communication.setResponse(responseCommand, responseData, isSequentialEnforced);
387             } while (!communication.isCommunicationFinished());
388             success = communication.isCommunicationSuccessful();
389         } while (false); // communication
390         logger.debug("bridgeDirectCommunicate({}) on {}: returns {}.", commandString, host,
391                 success ? "success" : "failure");
392         return success;
393     }
394 }