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 logger.trace("bridgeDirectCommunicate({},{}authenticated) called.", communication.name(),
197 useAuthentication ? "" : "un");
199 assert this.bridgeInstance.veluxBridgeConfiguration().protocol.contentEquals("slip");
201 long communicationStartInMSecs = System.currentTimeMillis();
203 boolean isSequentialEnforced = this.bridgeInstance.veluxBridgeConfiguration().isSequentialEnforced;
204 boolean isProtocolTraceEnabled = this.bridgeInstance.veluxBridgeConfiguration().isProtocolTraceEnabled;
207 short command = communication.getRequestCommand().toShort();
208 byte[] data = communication.getRequestDataAsArrayOfBytes();
209 // For further use at different logging statements
210 String commandString = Command.get(command).toString();
212 if (isProtocolTraceEnabled) {
213 Threads.findDeadlocked();
216 logger.debug("bridgeDirectCommunicate({},{}authenticated) initiated by {}.", commandString,
217 useAuthentication ? "" : "un", Thread.currentThread());
218 boolean success = false;
221 if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
223 "{} bridgeDirectCommunicate({}): communication handshake failed (unexpected sequence of requests/responses).",
224 VeluxBindingConstants.BINDING_VALUES_SEPARATOR, communication.name());
229 if (Command.get(command) == Command.GW_OPENHAB_CLOSE) {
230 logger.trace("bridgeDirectCommunicate(): special command: shutting down connection.");
231 connection.resetConnection();
237 logger.trace("bridgeDirectCommunicate(): working on request {} with {} bytes of data.", commandString,
239 byte[] sendBytes = emptyPacket;
240 if (Command.get(command) == Command.GW_OPENHAB_RECEIVEONLY) {
242 "bridgeDirectCommunicate(): special command: determine whether there is any message waiting.");
243 logger.trace("bridgeDirectCommunicate(): check for a waiting message.");
244 if (!connection.isMessageAvailable()) {
245 logger.trace("bridgeDirectCommunicate(): no message waiting, aborting.");
248 logger.trace("bridgeDirectCommunicate(): there is a message waiting.");
250 SlipEncoding t = new SlipEncoding(command, data);
252 logger.warn("bridgeDirectCommunicate(): SlipEncoding() failed, aborting.");
255 logger.trace("bridgeDirectCommunicate(): transportEncoding={}.", t.toString());
256 sendBytes = new SlipRFC1055().encode(t.toMessage());
259 if (communicationStartInMSecs + COMMUNICATION_TIMEOUT_MSECS < System.currentTimeMillis()) {
260 logger.warn("bridgeDirectCommunicate(): receive takes too long. Please report to maintainer.");
263 byte[] receivedPacket;
265 if (sendBytes.length > 0) {
266 logger.trace("bridgeDirectCommunicate(): sending {} bytes.", sendBytes.length);
267 if (isProtocolTraceEnabled) {
268 logger.info("Sending command {}.", commandString);
271 logger.trace("bridgeDirectCommunicate(): initiating receive-only.");
273 // (Optionally) Send and receive packet.
274 receivedPacket = connection.io(this.bridgeInstance, sendBytes);
275 // Once being sent, it should never be sent again
276 sendBytes = emptyPacket;
277 } catch (Exception e) {
278 logger.warn("bridgeDirectCommunicate(): connection.io returns {}", e.getMessage());
281 logger.trace("bridgeDirectCommunicate(): received packet {}.", new Packet(receivedPacket).toString());
284 response = new SlipRFC1055().decode(receivedPacket);
285 } catch (ParseException e) {
286 logger.warn("bridgeDirectCommunicate(): method SlipRFC1055() raised a decoding error: {}.",
290 SlipEncoding tr = new SlipEncoding(response);
292 logger.warn("bridgeDirectCommunicate(): method SlipEncoding() raised a decoding error.");
295 short responseCommand = tr.getCommand();
296 byte[] responseData = tr.getData();
297 logger.debug("bridgeDirectCommunicate(): working on response {} with {} bytes of data.",
298 Command.get(responseCommand).toString(), responseData.length);
299 if (isProtocolTraceEnabled) {
300 logger.info("Received answer {}.", Command.get(responseCommand).toString());
302 // Handle some common (unexpected) answers
303 switch (Command.get(responseCommand)) {
304 case GW_NODE_INFORMATION_CHANGED_NTF:
305 logger.trace("bridgeDirectCommunicate(): received GW_NODE_INFORMATION_CHANGED_NTF.");
306 logger.trace("bridgeDirectCommunicate(): continue with receiving.");
308 case GW_NODE_STATE_POSITION_CHANGED_NTF:
310 "bridgeDirectCommunicate(): received GW_NODE_STATE_POSITION_CHANGED_NTF, special processing of this packet.");
311 SCgetHouseStatus receiver = new SCgetHouseStatus();
312 receiver.setResponse(responseCommand, responseData, isSequentialEnforced);
313 if (receiver.isCommunicationSuccessful()) {
314 logger.trace("bridgeDirectCommunicate(): existingProducts().update() called.");
315 bridgeInstance.existingProducts().update(new ProductBridgeIndex(receiver.getNtfNodeID()),
316 receiver.getNtfState(), receiver.getNtfCurrentPosition(), receiver.getNtfTarget());
318 logger.trace("bridgeDirectCommunicate(): continue with receiving.");
321 switch (responseData[0]) {
324 "bridgeDirectCommunicate(): received GW_ERROR_NTF on {} (Not further defined error), aborting.",
329 "bridgeDirectCommunicate(): received GW_ERROR_NTF (Unknown Command or command is not accepted at this state) on {}, aborting.",
334 "bridgeDirectCommunicate(): received GW_ERROR_NTF (ERROR on Frame Structure) on {}, aborting.",
339 "bridgeDirectCommunicate(): received GW_ERROR_NTF (Busy. Try again later) on {}, retrying.",
341 sendBytes = emptyPacket;
345 "bridgeDirectCommunicate(): received GW_ERROR_NTF (Bad system table index) on {}, aborting.",
350 "bridgeDirectCommunicate(): received GW_ERROR_NTF (Not authenticated) on {}, aborting.",
352 resetAuthentication();
355 logger.warn("bridgeDirectCommunicate(): received GW_ERROR_NTF ({}) on {}, aborting.",
356 responseData[0], commandString);
359 case GW_ACTIVATION_LOG_UPDATED_NTF:
360 logger.info("bridgeDirectCommunicate(): received GW_ACTIVATION_LOG_UPDATED_NTF.");
361 logger.trace("bridgeDirectCommunicate(): continue with receiving.");
364 case GW_COMMAND_RUN_STATUS_NTF:
365 case GW_COMMAND_REMAINING_TIME_NTF:
366 case GW_SESSION_FINISHED_NTF:
367 if (!isSequentialEnforced) {
369 "bridgeDirectCommunicate(): response ignored due to activated parallelism, continue with receiving.");
375 logger.trace("bridgeDirectCommunicate(): passes back command {} and data {}.",
376 new CommandNumber(responseCommand).toString(), new Packet(responseData).toString());
377 communication.setResponse(responseCommand, responseData, isSequentialEnforced);
378 } while (!communication.isCommunicationFinished());
379 success = communication.isCommunicationSuccessful();
380 } while (false); // communication
381 logger.debug("bridgeDirectCommunicate({}) returns {}.", commandString, success ? "success" : "failure");