2 * Copyright (c) 2010-2020 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.velux.internal.bridge.slip;
15 import java.text.ParseException;
16 import java.util.TreeSet;
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;
36 * SLIP-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
38 * It provides methods for pre- and postcommunication
39 * as well as a common method for the real communication.
41 * In addition to the generic {@link VeluxBridge} methods, i.e.
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>
48 * the following class access methods provides the protocol-specific implementation:
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>
55 * @author Guenther Schreiner - Initial contribution.
58 public class SlipVeluxBridge extends VeluxBridge {
59 private final Logger logger = LoggerFactory.getLogger(SlipVeluxBridge.class);
62 * ***************************
63 * ***** Private Objects *****
67 * Timeout for sequence of one request with (optional multiple) responses.
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.
72 static final long COMMUNICATION_TIMEOUT_MSECS = 60000L;
74 * Wait interval within sequence after the request and no immediate response.
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.
79 static final long COMMUNICATION_RETRY_MSECS = 5000L;
82 * ***************************
83 * ***** Private Objects *****
86 private final byte[] emptyPacket = new byte[0];
87 private Connection connection = new Connection();
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}.
93 private BridgeAPI bridgeAPI;
98 * Inherits the initialization of the binding-wide instance for dealing for common informations and
99 * initializes the Velux bridge connection settings.
101 * @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
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.");
115 * De-initializes the binding-wide instance.
118 public void shutdown() {
119 logger.trace("shutdown() called.");
120 connection.resetConnection();
121 logger.trace("shutdown() done.");
125 * Provides information about the base-level communication method and
126 * any kind of available gateway interactions.
128 * <B>Note:</B> the implementation within this class {@link SlipVeluxBridge} as inherited from {@link VeluxBridge}
129 * will return the protocol-specific class implementations.
131 * The information will be initialized by the corresponding API class {@link SlipBridgeAPI}.
133 * @return <b>bridgeAPI</b> of type {@link BridgeAPI} contains all possible methods.
136 public BridgeAPI bridgeAPI() {
137 logger.trace("bridgeAPI() called.");
142 * <B>Method as implementation of abstract superclass method.</B>
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).
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.
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);
162 * Returns the timestamp in milliseconds since Unix epoch
163 * of last (potentially faulty) communication.
165 * @return timestamp in milliseconds.
168 public long lastCommunication() {
169 return connection.lastCommunication();
173 * Returns the timestamp in milliseconds since Unix epoch
174 * of last successful communication.
176 * @return timestamp in milliseconds.
179 public long lastSuccessfulCommunication() {
180 return connection.lastSuccessfulCommunication();
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).
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
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.
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");
200 assert this.bridgeInstance.veluxBridgeConfiguration().protocol.contentEquals("slip");
202 long communicationStartInMSecs = System.currentTimeMillis();
204 boolean isSequentialEnforced = this.bridgeInstance.veluxBridgeConfiguration().isSequentialEnforced;
205 boolean isProtocolTraceEnabled = this.bridgeInstance.veluxBridgeConfiguration().isProtocolTraceEnabled;
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();
213 if (isProtocolTraceEnabled) {
214 Threads.findDeadlocked();
217 logger.debug("bridgeDirectCommunicate({},{}authenticated) on {} initiated by {}.", host, commandString,
218 useAuthentication ? "" : "un", Thread.currentThread());
219 boolean success = false;
222 if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
224 "{} bridgeDirectCommunicate({}) on {}: communication handshake failed (unexpected sequence of requests/responses).",
225 VeluxBindingConstants.BINDING_VALUES_SEPARATOR, communication.name(), host);
230 if (Command.get(command) == Command.GW_OPENHAB_CLOSE) {
231 logger.trace("bridgeDirectCommunicate(): special command: shutting down connection.");
232 connection.resetConnection();
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) {
243 "bridgeDirectCommunicate() on {}: special command: determine whether there is any message waiting.",
245 logger.trace("bridgeDirectCommunicate(): check for a waiting message.");
246 if (!connection.isMessageAvailable()) {
247 logger.trace("bridgeDirectCommunicate() on {}: no message waiting, aborting.", host);
250 logger.trace("bridgeDirectCommunicate() on {}: there is a message waiting.", host);
252 SlipEncoding t = new SlipEncoding(command, data);
254 logger.warn("bridgeDirectCommunicate() on {}: SlipEncoding() failed, aborting.", host);
257 logger.trace("bridgeDirectCommunicate() on {}: transportEncoding={}.", host, t.toString());
258 sendBytes = new SlipRFC1055().encode(t.toMessage());
261 if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
262 logger.warn("bridgeDirectCommunicate() on {}: receive takes too long. Please report to maintainer.",
266 byte[] receivedPacket;
268 if (sendBytes.length > 0) {
269 logger.trace("bridgeDirectCommunicate() on {}: sending {} bytes.", host, sendBytes.length);
270 if (isProtocolTraceEnabled) {
271 logger.info("Sending command {}.", commandString);
274 logger.trace("bridgeDirectCommunicate() on {}: initiating receive-only.", host);
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());
284 logger.trace("bridgeDirectCommunicate() on {}: received packet {}.", host,
285 new Packet(receivedPacket).toString());
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());
294 SlipEncoding tr = new SlipEncoding(response);
296 logger.warn("bridgeDirectCommunicate() on {}: method SlipEncoding() raised a decoding error.",
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());
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.",
312 logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
314 case GW_NODE_STATE_POSITION_CHANGED_NTF:
316 "bridgeDirectCommunicate() on {}: received GW_NODE_STATE_POSITION_CHANGED_NTF, special processing of this packet.",
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());
325 logger.trace("bridgeDirectCommunicate() on {}: continue with receiving.", host);
328 switch (responseData[0]) {
331 "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF on {} (Not further defined error), aborting.",
332 host, commandString);
336 "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Unknown Command or command is not accepted at this state) on {}, aborting.",
337 host, commandString);
341 "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (ERROR on Frame Structure) on {}, aborting.",
342 host, commandString);
346 "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Busy. Try again later) on {}, retrying.",
347 host, commandString);
348 sendBytes = emptyPacket;
352 "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Bad system table index) on {}, aborting.",
353 host, commandString);
357 "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF (Not authenticated) on {}, aborting.",
358 host, commandString);
359 resetAuthentication();
363 "bridgeDirectCommunicate() on {}: received GW_ERROR_NTF ({}) on {}, aborting.",
364 host, responseData[0], commandString);
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);
372 case GW_COMMAND_RUN_STATUS_NTF:
373 case GW_COMMAND_REMAINING_TIME_NTF:
374 case GW_SESSION_FINISHED_NTF:
375 if (!isSequentialEnforced) {
377 "bridgeDirectCommunicate() on {}: response ignored due to activated parallelism, continue with receiving.",
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");