2 * Copyright (c) 2010-2022 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.io.Closeable;
16 import java.io.IOException;
17 import java.text.ParseException;
18 import java.util.TreeSet;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.openhab.binding.velux.internal.bridge.VeluxBridge;
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.handler.VeluxBridgeHandler;
30 import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
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 post- communication as well as a common method for the real communication.
40 * In addition to the generic {@link VeluxBridge} methods, i.e.
42 * <LI>{@link VeluxBridge#bridgeLogin} for pre-communication,</LI>
43 * <LI>{@link VeluxBridge#bridgeLogout} for post-communication,</LI>
44 * <LI>{@link VeluxBridge#bridgeCommunicate} as method for the common communication,</LI>
45 * <LI>{@link VeluxBridge#bridgeAPI} as interfacing method to all interaction prototypes,</LI>
47 * the following class access methods provides the protocol-specific implementation:
49 * <LI>{@link #bridgeDirectCommunicate} for SLIP-based communication.</LI>
50 * <LI>{@link #bridgeAPI} for return all defined protocol-specific implementations which are provided by
51 * {@link org.openhab.binding.velux.internal.bridge.slip.SlipBridgeAPI SlipBridgeAPI}.</LI>
54 * @author Guenther Schreiner - Initial contribution.
55 * @author Andrew Fiddian-Green - Refactored (simplified) the message processing loop
58 public class SlipVeluxBridge extends VeluxBridge implements Closeable {
60 private final Logger logger = LoggerFactory.getLogger(SlipVeluxBridge.class);
63 * ***************************
64 * ***** Private Objects *****
68 * Timeout for sequence of one request with (optional multiple) responses.
70 * This can only happen if there is an unexpected (undocumented) occurrence of events
71 * which has to be discussed along the Velux API documentation.
73 static final long COMMUNICATION_TIMEOUT_MSECS = 60000L;
75 * Wait interval within sequence after the request and no immediate response.
77 * This can happen if the bridge is busy due to a specific command to query and collect information from other
78 * devices via the io-homecontrol protocol.
80 static final long COMMUNICATION_RETRY_MSECS = 5000L;
83 * ***************************
84 * ***** Private Objects *****
87 private final byte[] emptyPacket = new byte[0];
88 private Connection connection = new Connection();
91 * Handler passing the interface methods to other classes.
92 * Can be accessed via method {@link org.openhab.binding.velux.internal.bridge.common.BridgeAPI BridgeAPI}.
94 private BridgeAPI bridgeAPI;
99 * Inherits the initialization of the binding-wide instance for dealing for common informations and
100 * initializes the Velux bridge connection settings.
102 * @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
104 public SlipVeluxBridge(VeluxBridgeHandler bridgeInstance) {
105 super(bridgeInstance);
106 logger.trace("SlipVeluxBridge(constructor) called.");
107 bridgeAPI = new SlipBridgeAPI(bridgeInstance);
108 supportedProtocols = new TreeSet<>();
109 supportedProtocols.add("slip");
110 logger.trace("SlipVeluxBridge(constructor) done.");
116 * De-initializes the binding-wide instance.
119 public void shutdown() {
120 logger.trace("shutdown() called.");
121 connection.resetConnection();
122 logger.trace("shutdown() done.");
126 * Provides information about the base-level communication method and
127 * any kind of available gateway interactions.
129 * <B>Note:</B> the implementation within this class {@link SlipVeluxBridge} as inherited from {@link VeluxBridge}
130 * will return the protocol-specific class implementations.
132 * The information will be initialized by the corresponding API class {@link SlipBridgeAPI}.
134 * @return <b>bridgeAPI</b> of type {@link BridgeAPI} contains all possible methods.
137 public BridgeAPI bridgeAPI() {
138 logger.trace("bridgeAPI() called.");
143 * <B>Method as implementation of abstract superclass method.</B>
145 * Initializes a client/server communication towards <b>Velux</b> veluxBridge
146 * based on the Basic I/O interface {@link Connection#io} and parameters
147 * passed as arguments (see below).
149 * @param communication Structure of interface type {@link SlipBridgeCommunicationProtocol} describing the intended
150 * communication, that is request and response interactions as well as appropriate URL definition.
151 * @param useAuthentication boolean flag to decide whether to use authenticated communication.
152 * @return <b>success</b> of type boolean which signals the success of the communication.
156 protected boolean bridgeDirectCommunicate(BridgeCommunicationProtocol communication, boolean useAuthentication) {
157 logger.trace("bridgeDirectCommunicate(BCP: {}, {}authenticated) called.", communication.name(),
158 useAuthentication ? "" : "un");
159 return bridgeDirectCommunicate((SlipBridgeCommunicationProtocol) communication, useAuthentication);
163 * Returns the timestamp in milliseconds since Unix epoch
164 * of last (potentially faulty) communication.
166 * @return timestamp in milliseconds.
169 public long lastCommunication() {
170 return connection.lastCommunication();
174 * Returns the timestamp in milliseconds since Unix epoch
175 * of last successful communication.
177 * @return timestamp in milliseconds.
180 public long lastSuccessfulCommunication() {
181 return connection.lastSuccessfulCommunication();
185 * Initializes a client/server communication towards the Velux Bridge based on the Basic I/O interface
186 * {@link Connection#io} and parameters passed as arguments (see below).
188 * @param communication a 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 a boolean flag to select whether to use authenticated communication.
192 * @return a boolean which in general signals the success of the communication, but in the
193 * special case of receive-only calls, signals if any products were updated during the call
195 private synchronized boolean bridgeDirectCommunicate(SlipBridgeCommunicationProtocol communication,
196 boolean useAuthentication) {
197 logger.trace("bridgeDirectCommunicate() '{}', {}authenticated", communication.name(),
198 useAuthentication ? "" : "un");
200 // store common parameters as constants for frequent use
201 final short txCmd = communication.getRequestCommand().toShort();
202 final byte[] txData = communication.getRequestDataAsArrayOfBytes();
203 final Command txEnum = Command.get(txCmd);
204 final String txName = txEnum.toString();
205 final boolean isSequentialEnforced = this.bridgeInstance.veluxBridgeConfiguration().isSequentialEnforced;
206 final boolean isProtocolTraceEnabled = this.bridgeInstance.veluxBridgeConfiguration().isProtocolTraceEnabled;
207 final long expiryTime = System.currentTimeMillis() + COMMUNICATION_TIMEOUT_MSECS;
209 // logger format string
210 final String loggerFmt = String.format("bridgeDirectCommunicate() [%s] %s => {} {} {}",
211 this.bridgeInstance.veluxBridgeConfiguration().ipAddress, txName);
213 if (isProtocolTraceEnabled) {
214 Threads.findDeadlocked();
217 logger.debug(loggerFmt, "started =>", Thread.currentThread(), "");
219 boolean looping = false;
220 boolean success = false;
221 boolean sending = false;
222 boolean rcvonly = false;
223 byte[] txPacket = emptyPacket;
225 // handling of the requests
227 case GW_OPENHAB_CLOSE:
228 logger.trace(loggerFmt, "shut down command", "=> executing", "");
229 connection.resetConnection();
233 case GW_OPENHAB_RECEIVEONLY:
234 logger.trace(loggerFmt, "receive-only mode", "=> checking messages", "");
235 if (!connection.isAlive()) {
236 logger.trace(loggerFmt, "no connection", "=> opening", "");
238 } else if (connection.isMessageAvailable()) {
239 logger.trace(loggerFmt, "message(s) waiting", "=> start reading", "");
242 logger.trace(loggerFmt, "no waiting messages", "=> done", "");
248 logger.trace(loggerFmt, "send mode", "=> preparing command", "");
249 SlipEncoding slipEnc = new SlipEncoding(txCmd, txData);
250 if (!slipEnc.isValid()) {
251 logger.debug(loggerFmt, "slip encoding error", "=> aborting", "");
254 txPacket = new SlipRFC1055().encode(slipEnc.toMessage());
255 logger.trace(loggerFmt, "command ready", "=> start sending", "");
256 looping = sending = true;
261 if (System.currentTimeMillis() > expiryTime) {
262 logger.warn(loggerFmt, "process loop time out", "=> aborting", "=> PLEASE REPORT !!");
263 // abort the processing loop
267 // send command (optionally), and receive response
271 if (isProtocolTraceEnabled) {
272 logger.info("sending command {}", txName);
274 if (logger.isTraceEnabled()) {
275 logger.trace(loggerFmt, txName, "=> sending data =>", new Packet(txData));
277 logger.debug(loggerFmt, txName, "=> sending data length =>", txData.length);
280 rxPacket = connection.io(this.bridgeInstance, sending ? txPacket : emptyPacket);
281 // message sent, don't send it again
283 if (rxPacket.length == 0) {
284 // only log in send mode (in receive-only mode, no response is ok)
286 logger.debug(loggerFmt, "no response", "=> aborting", "");
288 // abort the processing loop
291 } catch (IOException e) {
292 logger.debug(loggerFmt, "i/o error =>", e.getMessage(), "=> aborting");
293 // abort the processing loop
297 // RFC1055 decode response
300 rfc1055 = new SlipRFC1055().decode(rxPacket);
301 } catch (ParseException e) {
302 logger.debug(loggerFmt, "parsing error =>", e.getMessage(), "=> aborting");
303 // abort the processing loop
307 // SLIP decode response
308 SlipEncoding slipEnc = new SlipEncoding(rfc1055);
309 if (!slipEnc.isValid()) {
310 logger.debug(loggerFmt, "slip decode error", "=> aborting", "");
311 // abort the processing loop
315 // attributes of the received (rx) response
316 final short rxCmd = slipEnc.getCommand();
317 final byte[] rxData = slipEnc.getData();
318 final Command rxEnum = Command.get(rxCmd);
319 final String rxName = rxEnum.toString();
322 if (logger.isTraceEnabled()) {
323 logger.trace(loggerFmt, rxName, "=> received data =>", new Packet(rxData));
325 logger.debug(loggerFmt, rxName, "=> received data length =>", rxData.length);
327 if (isProtocolTraceEnabled) {
328 logger.info("received message {} => {}", rxName, new Packet(rxData));
331 // handling of the responses
334 byte code = rxData[0];
337 logger.trace(loggerFmt, rxName, getErrorText(code), "=> retrying");
340 case 12: // authentication failed
341 logger.debug(loggerFmt, rxName, getErrorText(code), "=> aborting");
342 resetAuthentication();
346 logger.warn(loggerFmt, rxName, getErrorText(code), "=> aborting");
351 case GW_NODE_INFORMATION_CHANGED_NTF:
352 case GW_ACTIVATION_LOG_UPDATED_NTF:
353 logger.trace(loggerFmt, rxName, "=> ignorable command", "=> continuing");
356 case GW_NODE_STATE_POSITION_CHANGED_NTF:
357 logger.trace(loggerFmt, rxName, "=> special command", "=> starting");
358 SCgetHouseStatus receiver = new SCgetHouseStatus();
359 receiver.setResponse(rxCmd, rxData, isSequentialEnforced);
360 if (receiver.isCommunicationSuccessful()) {
361 bridgeInstance.existingProducts().update(new ProductBridgeIndex(receiver.getNtfNodeID()),
362 receiver.getNtfState(), receiver.getNtfCurrentPosition(), receiver.getNtfTarget());
363 logger.trace(loggerFmt, rxName, "=> special command", "=> product updated");
365 // receive-only: return success to confirm that product(s) were updated
369 logger.trace(loggerFmt, rxName, "=> special command", "=> continuing");
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(loggerFmt, rxName, "=> parallelism allowed", "=> continuing");
379 logger.trace(loggerFmt, rxName, "=> serialism enforced", "=> default processing");
380 // fall through => execute default processing
383 logger.trace(loggerFmt, rxName, "=> applying data length =>", rxData.length);
384 communication.setResponse(rxCmd, rxData, isSequentialEnforced);
385 looping = !communication.isCommunicationFinished();
386 success = communication.isCommunicationSuccessful();
390 // in receive-only mode 'failure` just means that no products were updated, so don't log it as a failure..
391 logger.debug(loggerFmt, "finished", "=>", ((success || rcvonly) ? "success" : "failure"));
396 * Return text description of potential GW_ERROR_NTF error codes, for logging purposes
398 * @param errCode is the GW_ERROR_NTF error code
399 * @return the description message
401 private static String getErrorText(byte errCode) {
404 return "=> (0) not further defined error";
406 return "=> (1) unknown command or command is not accepted at this state";
408 return "=> (2) error on frame structure";
410 return "=> (7) busy, try again later";
412 return "=> (8) bad system table index";
414 return "=> (12) not authenticated";
416 return String.format("=> (%d) unknown error", errCode);
420 public void close() throws IOException {