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.bluetooth.bluegiga.internal;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
19 import java.util.concurrent.CopyOnWriteArraySet;
21 import org.apache.commons.io.IOUtils;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaEndProcedureCommand;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
28 * The main handler class for interacting with the BlueGiga serial API. This class provides conversion of packets from
29 * the serial stream into command and response classes.
31 * @author Chris Jackson - Initial contribution and API
32 * @author Pauli Anttila - Split serial handler and transaction management
36 public class BlueGigaSerialHandler {
38 private static final int BLE_MAX_LENGTH = 64;
40 private final Logger logger = LoggerFactory.getLogger(BlueGigaSerialHandler.class);
43 * The event listeners will be notified of any asynchronous events
45 private final Set<BlueGigaSerialEventListener> eventListeners = new CopyOnWriteArraySet<>();
48 * The event listeners will be notified of any life-cycle events of the handler.
50 private final Set<BlueGigaHandlerListener> handlerListeners = new CopyOnWriteArraySet<>();
53 * Flag reflecting that parser has been closed and parser parserThread
56 private boolean close = false;
58 private final OutputStream outputStream;
59 private final InputStream inputStream;
60 private final Thread parserThread;
62 public BlueGigaSerialHandler(final InputStream inputStream, final OutputStream outputStream) {
63 this.outputStream = outputStream;
64 this.inputStream = inputStream;
67 parserThread = createBlueGigaBLEHandler();
68 parserThread.setDaemon(true);
72 // wait until the daemon thread kicks off, e.g. when it is ready to receive any commands
73 while (parserThread.getState() == Thread.State.NEW) {
78 throw new IllegalStateException("BlueGiga handler thread failed to start");
80 } catch (InterruptedException ignore) {
86 private void flush() {
87 // Send End Procedure command to end all activity and flush input buffer to start from know state
88 logger.trace("Flush input stream");
89 sendFrame(new BlueGigaEndProcedureCommand(), false);
91 // Wait BlueGiga controller have stopped all activity
93 logger.trace("Bytes available: {}", inputStream.available());
94 IOUtils.skipFully(inputStream, inputStream.available());
95 } catch (InterruptedException e) {
97 } catch (IOException e) {
100 logger.trace("Flush done");
104 * Requests parser thread to shutdown. Waits forever while the parser thread is getting shut down.
106 public void close() {
111 * Requests parser thread to shutdown. Waits specified milliseconds while the parser thread is getting shut down.
113 * @param timeout milliseconds to wait
115 public void close(long timeout) {
118 parserThread.interrupt();
119 // Give a fair chance to shutdown nicely
121 IOUtils.closeQuietly(outputStream);
122 IOUtils.closeQuietly(inputStream);
123 parserThread.join(0);
124 } catch (InterruptedException e) {
125 logger.warn("Interrupted in packet parser thread shutdown join.");
128 handlerListeners.clear();
129 eventListeners.clear();
130 logger.debug("Closed");
134 * Checks if parser thread is alive.
136 * @return true if parser thread is alive.
138 public boolean isAlive() {
139 return parserThread.isAlive() && !close;
142 public void sendFrame(BlueGigaCommand bleFrame) throws IllegalStateException {
143 sendFrame(bleFrame, true);
146 private void sendFrame(BlueGigaCommand bleFrame, boolean checkIsAlive) throws IllegalStateException {
152 logger.trace("sendFrame: {}", bleFrame);
154 int[] payload = bleFrame.serialize();
155 if (logger.isTraceEnabled()) {
156 logger.trace("BLE TX: {}", printHex(payload, payload.length));
158 for (int b : payload) {
159 outputStream.write(b);
161 outputStream.flush();
163 } catch (IOException e) {
164 throw new BlueGigaException("Error sending BLE frame", e);
168 public void addEventListener(BlueGigaSerialEventListener listener) {
169 eventListeners.add(listener);
172 public void removeEventListener(BlueGigaSerialEventListener listener) {
173 eventListeners.remove(listener);
176 public void addHandlerListener(BlueGigaHandlerListener listener) {
177 handlerListeners.add(listener);
180 public void removeHandlerListener(BlueGigaHandlerListener listener) {
181 handlerListeners.remove(listener);
185 * Notify any transaction listeners when we receive a response.
187 * @param response the response data received
189 private void notifyEventListeners(final BlueGigaResponse response) {
190 // Notify the listeners
191 for (final BlueGigaSerialEventListener listener : eventListeners) {
193 listener.bluegigaFrameReceived(response);
194 } catch (Exception ex) {
195 logger.warn("Execution error of a BlueGigaHandlerListener listener.", ex);
201 * Notify handler event listeners that the handler was bluegigaClosed due to an error specified as an argument.
203 * @param reason the reason to bluegigaClosed
205 private void notifyEventListeners(final Exception reason) {
206 // It should be safe enough not to use the NotificationService as this is a fatal error, no any further actions
207 // can be done with the handler, a new handler should be re-created
208 // There is another reason why NotificationService can't be used - the listeners should be notified immediately
209 for (final BlueGigaHandlerListener listener : handlerListeners) {
211 listener.bluegigaClosed(reason);
212 } catch (Exception ex) {
213 logger.warn("Execution error of a BlueGigaHandlerListener listener.", ex);
218 private String printHex(int[] data, int len) {
219 StringBuilder builder = new StringBuilder();
221 for (int cnt = 0; cnt < len; cnt++) {
222 builder.append(String.format("%02X ", data[cnt]));
225 return builder.toString();
228 private void checkIfAlive() {
230 throw new IllegalStateException("Bluegiga handler is dead. Most likely because of IO errors. "
231 + "Re-initialization of the BlueGigaSerialHandler is required.");
235 private Thread createBlueGigaBLEHandler() {
236 final int framecheckParams[] = new int[] { 0x00, 0x7F, 0xC0, 0xF8, 0xE0 };
237 return new Thread("BlueGigaBLEHandler") {
240 int exceptionCnt = 0;
241 logger.trace("BlueGiga BLE thread started");
242 int[] inputBuffer = new int[BLE_MAX_LENGTH];
248 int val = inputStream.read();
253 inputBuffer[inputCount++] = val;
255 if (inputCount == 1) {
256 if (inputStream.markSupported()) {
257 inputStream.mark(BLE_MAX_LENGTH);
261 if (inputCount < 4) {
262 // The BGAPI protocol has no packet framing, and no error detection, so we do a few
263 // sanity checks on the header to try and allow resyncronisation should there be an
265 // Byte 0: Check technology type is bluetooth and high length is 0
266 // Byte 1: Check length is less than 64 bytes
267 // Byte 2: Check class ID is less than 8
268 // Byte 3: Check command ID is less than 16
269 if ((val & framecheckParams[inputCount]) != 0) {
270 logger.debug("BlueGiga framing error byte {} = {}", inputCount, val);
271 if (inputStream.markSupported()) {
277 } else if (inputCount == 4) {
278 // Process the header to get the length
279 inputLength = inputBuffer[1] + (inputBuffer[0] & 0x02 << 8) + 4;
280 if (inputLength > 64) {
281 logger.debug("BLE length larger than 64 bytes ({})", inputLength);
284 if (inputCount == inputLength) {
285 // End of packet reached - process
286 BlueGigaResponse responsePacket = BlueGigaResponsePackets.getPacket(inputBuffer);
288 if (logger.isTraceEnabled()) {
289 logger.trace("BLE RX: {}", printHex(inputBuffer, inputLength));
290 logger.trace("BLE RX: {}", responsePacket);
292 if (responsePacket != null) {
293 notifyEventListeners(responsePacket);
300 } catch (final IOException e) {
301 logger.debug("BlueGiga BLE IOException: ", e);
303 if (exceptionCnt++ > 10) {
304 logger.error("BlueGiga BLE exception count exceeded, closing handler");
306 notifyEventListeners(e);
310 logger.debug("BlueGiga BLE exited.");