]> git.basschouten.com Git - openhab-addons.git/blob
a6c54f65cc5773c3c9a3092f68300d439aeca511
[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.bluetooth.bluegiga.handler;
14
15 import java.io.BufferedInputStream;
16 import java.io.BufferedOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.util.Map;
21 import java.util.Optional;
22 import java.util.UUID;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.concurrent.Future;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
29 import org.apache.commons.io.IOUtils;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
33 import org.openhab.binding.bluetooth.BluetoothAddress;
34 import org.openhab.binding.bluetooth.BluetoothBindingConstants;
35 import org.openhab.binding.bluetooth.bluegiga.BlueGigaAdapterConstants;
36 import org.openhab.binding.bluetooth.bluegiga.BlueGigaBluetoothDevice;
37 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaCommand;
38 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaConfiguration;
39 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaEventListener;
40 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaException;
41 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaHandlerListener;
42 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaResponse;
43 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaSerialHandler;
44 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaTransactionManager;
45 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaAttributeWriteCommand;
46 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaAttributeWriteResponse;
47 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaFindInformationCommand;
48 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaFindInformationResponse;
49 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeCommand;
50 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeResponse;
51 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleCommand;
52 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleResponse;
53 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaConnectionStatusEvent;
54 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectCommand;
55 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectResponse;
56 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectedEvent;
57 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaConnectDirectCommand;
58 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaConnectDirectResponse;
59 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaDiscoverCommand;
60 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaDiscoverResponse;
61 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaEndProcedureCommand;
62 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaEndProcedureResponse;
63 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaScanResponseEvent;
64 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetModeCommand;
65 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetModeResponse;
66 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetScanParametersCommand;
67 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetScanParametersResponse;
68 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaAddressGetCommand;
69 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaAddressGetResponse;
70 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetConnectionsCommand;
71 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetConnectionsResponse;
72 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetInfoCommand;
73 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetInfoResponse;
74 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BgApiResponse;
75 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BluetoothAddressType;
76 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapConnectableMode;
77 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverMode;
78 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverableMode;
79 import org.openhab.core.common.ThreadPoolManager;
80 import org.openhab.core.io.transport.serial.PortInUseException;
81 import org.openhab.core.io.transport.serial.SerialPort;
82 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
83 import org.openhab.core.io.transport.serial.SerialPortManager;
84 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
85 import org.openhab.core.thing.Bridge;
86 import org.openhab.core.thing.Thing;
87 import org.openhab.core.thing.ThingStatus;
88 import org.openhab.core.thing.ThingStatusDetail;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
91
92 /**
93  * The {@link BlueGigaBridgeHandler} is responsible for interfacing to the BlueGiga Bluetooth adapter.
94  * It provides a private interface for {@link BlueGigaBluetoothDevice}s to access the dongle and provides top
95  * level adaptor functionality for scanning and arbitration.
96  * <p>
97  * The handler provides the serial interface to the dongle via the BlueGiga BG-API library.
98  * <p>
99  * In the BlueGiga dongle, we leave scanning enabled most of the time. Normally, it's just passive scanning, and active
100  * scanning is enabled when we want to include new devices. Passive scanning is enough for us to receive beacons etc
101  * that are transmitted periodically, and active scanning will get more information which may be useful when we are
102  * including new devices.
103  *
104  * @author Chris Jackson - Initial contribution
105  * @author Kai Kreuzer - Made handler implement BlueGigaHandlerListener
106  * @author Pauli Anttila - Many improvements
107  */
108 @NonNullByDefault
109 public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGigaBluetoothDevice>
110         implements BlueGigaEventListener, BlueGigaHandlerListener {
111
112     private final Logger logger = LoggerFactory.getLogger(BlueGigaBridgeHandler.class);
113
114     private final int COMMAND_TIMEOUT_MS = 5000;
115     private final int INITIALIZATION_INTERVAL_SEC = 60;
116
117     private final SerialPortManager serialPortManager;
118
119     private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("BlueGiga");
120
121     // The serial port.
122     private Optional<SerialPort> serialPort = Optional.empty();
123
124     private BlueGigaConfiguration configuration = new BlueGigaConfiguration();
125
126     // The serial port input stream.
127     private Optional<InputStream> inputStream = Optional.empty();
128
129     // The serial port output stream.
130     private Optional<OutputStream> outputStream = Optional.empty();
131
132     // The BlueGiga API handler
133     private Optional<BlueGigaSerialHandler> serialHandler = Optional.empty();
134
135     // The BlueGiga transaction manager
136     private Optional<BlueGigaTransactionManager> transactionManager = Optional.empty();
137
138     // The maximum number of connections this interface supports
139     private int maxConnections = 0;
140
141     // Our BT address
142     private @Nullable BluetoothAddress address;
143
144     // Map of open connections
145     private final Map<Integer, BluetoothAddress> connections = new ConcurrentHashMap<>();
146
147     private volatile boolean initComplete = false;
148
149     private @Nullable ScheduledFuture<?> initTask;
150     private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
151     private @Nullable ScheduledFuture<?> discoveryTask;
152
153     private @Nullable Future<?> passiveScanIdleTimer;
154
155     public BlueGigaBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
156         super(bridge);
157         this.serialPortManager = serialPortManager;
158     }
159
160     @Override
161     public void initialize() {
162         super.initialize();
163         Optional<BlueGigaConfiguration> cfg = Optional.of(getConfigAs(BlueGigaConfiguration.class));
164         if (cfg.isPresent()) {
165             configuration = cfg.get();
166             initTask = executor.scheduleWithFixedDelay(this::start, 0, INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
167         } else {
168             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR);
169         }
170     }
171
172     @Override
173     public void dispose() {
174         stop();
175         stopScheduledTasks();
176         if (initTask != null) {
177             initTask.cancel(true);
178         }
179         super.dispose();
180     }
181
182     private void start() {
183         try {
184             if (!initComplete) {
185                 logger.debug("Initialize BlueGiga");
186                 logger.debug("Using configuration: {}", configuration);
187                 stop();
188                 if (openSerialPort(configuration.port, 115200)) {
189                     serialHandler = Optional.of(new BlueGigaSerialHandler(inputStream.get(), outputStream.get()));
190                     transactionManager = Optional.of(new BlueGigaTransactionManager(serialHandler.get(), executor));
191                     serialHandler.get().addHandlerListener(this);
192                     transactionManager.get().addEventListener(this);
193                     updateStatus(ThingStatus.UNKNOWN);
194
195                     try {
196                         // Stop any procedures that are running
197                         bgEndProcedure();
198
199                         // Set mode to non-discoverable etc.
200                         bgSetMode();
201
202                         // Get maximum parallel connections
203                         maxConnections = readMaxConnections().getMaxconn();
204
205                         // Close all connections so we start from a known position
206                         for (int connection = 0; connection < maxConnections; connection++) {
207                             sendCommandWithoutChecks(
208                                     new BlueGigaDisconnectCommand.CommandBuilder().withConnection(connection).build(),
209                                     BlueGigaDisconnectResponse.class);
210                         }
211
212                         // Get our Bluetooth address
213                         address = new BluetoothAddress(readAddress().getAddress());
214
215                         updateThingProperties();
216
217                         initComplete = true;
218                         updateStatus(ThingStatus.ONLINE);
219                         startScheduledTasks();
220                     } catch (BlueGigaException e) {
221                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
222                                 "Initialization of BlueGiga controller failed");
223                     }
224                 }
225             }
226         } catch (RuntimeException e) {
227             // Avoid scheduled task to shutdown
228             // e.g. when BlueGiga module is detached
229             logger.debug("Start failed", e);
230         }
231     }
232
233     private void stop() {
234         if (transactionManager.isPresent()) {
235             transactionManager.get().removeEventListener(this);
236             transactionManager.get().close();
237             transactionManager = Optional.empty();
238         }
239         if (serialHandler.isPresent()) {
240             serialHandler.get().removeHandlerListener(this);
241             serialHandler.get().close();
242             serialHandler = Optional.empty();
243         }
244         address = null;
245         initComplete = false;
246         connections.clear();
247         closeSerialPort();
248     }
249
250     private void schedulePassiveScan() {
251         cancelScheduledPassiveScan();
252         passiveScanIdleTimer = executor.schedule(() -> {
253             if (!activeScanEnabled) {
254                 logger.debug("Activate passive scan");
255                 bgEndProcedure();
256                 bgStartScanning(false, configuration.passiveScanInterval, configuration.passiveScanWindow);
257             } else {
258                 logger.debug("Ignore passive scan activation as active scan is active");
259             }
260         }, configuration.passiveScanIdleTime, TimeUnit.MILLISECONDS);
261     }
262
263     private void cancelScheduledPassiveScan() {
264         if (passiveScanIdleTimer != null) {
265             passiveScanIdleTimer.cancel(true);
266         }
267     }
268
269     private void startScheduledTasks() {
270         schedulePassiveScan();
271         logger.debug("Start scheduled task to remove inactive devices");
272         discoveryTask = scheduler.scheduleWithFixedDelay(this::refreshDiscoveredDevices, 0, 10, TimeUnit.SECONDS);
273     }
274
275     private void stopScheduledTasks() {
276         cancelScheduledPassiveScan();
277         if (removeInactiveDevicesTask != null) {
278             removeInactiveDevicesTask.cancel(true);
279             removeInactiveDevicesTask = null;
280         }
281         if (discoveryTask != null) {
282             discoveryTask.cancel(true);
283             discoveryTask = null;
284         }
285     }
286
287     private BlueGigaGetConnectionsResponse readMaxConnections() throws BlueGigaException {
288         return sendCommandWithoutChecks(new BlueGigaGetConnectionsCommand(), BlueGigaGetConnectionsResponse.class);
289     }
290
291     private BlueGigaAddressGetResponse readAddress() throws BlueGigaException {
292         return sendCommandWithoutChecks(new BlueGigaAddressGetCommand(), BlueGigaAddressGetResponse.class);
293     }
294
295     private BlueGigaGetInfoResponse readInfo() throws BlueGigaException {
296         return sendCommandWithoutChecks(new BlueGigaGetInfoCommand(), BlueGigaGetInfoResponse.class);
297     }
298
299     private void updateThingProperties() throws BlueGigaException {
300         BlueGigaGetInfoResponse infoResponse = readInfo();
301
302         Map<String, String> properties = editProperties();
303         properties.put(BluetoothBindingConstants.PROPERTY_MAXCONNECTIONS, Integer.toString(maxConnections));
304         properties.put(Thing.PROPERTY_FIRMWARE_VERSION,
305                 String.format("%d.%d", infoResponse.getMajor(), infoResponse.getMinor()));
306         properties.put(Thing.PROPERTY_HARDWARE_VERSION, Integer.toString(infoResponse.getHardware()));
307         properties.put(BlueGigaAdapterConstants.PROPERTY_PROTOCOL, Integer.toString(infoResponse.getProtocolVersion()));
308         properties.put(BlueGigaAdapterConstants.PROPERTY_LINKLAYER, Integer.toString(infoResponse.getLlVersion()));
309         updateProperties(properties);
310     }
311
312     private boolean openSerialPort(final String serialPortName, int baudRate) {
313         logger.debug("Connecting to serial port '{}'", serialPortName);
314         try {
315             SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
316             if (portIdentifier == null) {
317                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port does not exist");
318                 return false;
319             }
320             SerialPort sp = portIdentifier.open("org.openhab.binding.bluetooth.bluegiga", 2000);
321             sp.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
322
323             sp.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_OUT);
324             sp.enableReceiveThreshold(1);
325             sp.enableReceiveTimeout(2000);
326
327             // RXTX serial port library causes high CPU load
328             // Start event listener, which will just sleep and slow down event loop
329             sp.notifyOnDataAvailable(true);
330
331             logger.info("Connected to serial port '{}'.", serialPortName);
332
333             try {
334                 inputStream = Optional.of(new BufferedInputStream(sp.getInputStream()));
335                 outputStream = Optional.of(new BufferedOutputStream(sp.getOutputStream()));
336             } catch (IOException e) {
337                 logger.error("Error getting serial streams", e);
338                 return false;
339             }
340             serialPort = Optional.of(sp);
341             return true;
342         } catch (PortInUseException e) {
343             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
344                     "Serial Error: Port in use");
345             return false;
346         } catch (UnsupportedCommOperationException e) {
347             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
348                     "Serial Error: Unsupported operation");
349             return false;
350         }
351     }
352
353     private void closeSerialPort() {
354         serialPort.ifPresent(sp -> {
355             sp.removeEventListener();
356             try {
357                 sp.disableReceiveTimeout();
358             } catch (Exception e) {
359                 // Ignore all as RXTX seems to send arbitrary exceptions when BlueGiga module is detached
360             } finally {
361                 outputStream.ifPresent(output -> {
362                     IOUtils.closeQuietly(output);
363                 });
364                 inputStream.ifPresent(input -> {
365                     IOUtils.closeQuietly(input);
366                 });
367                 sp.close();
368                 logger.debug("Closed serial port.");
369                 serialPort = Optional.empty();
370                 inputStream = Optional.empty();
371                 outputStream = Optional.empty();
372             }
373         });
374     }
375
376     @Override
377     public void scanStart() {
378         super.scanStart();
379         logger.debug("Start active scan");
380         // Stop the passive scan
381         cancelScheduledPassiveScan();
382         bgEndProcedure();
383
384         // Start a active scan
385         bgStartScanning(true, configuration.activeScanInterval, configuration.activeScanWindow);
386     }
387
388     @Override
389     public void scanStop() {
390         super.scanStop();
391         logger.debug("Stop active scan");
392
393         // Stop the active scan
394         bgEndProcedure();
395
396         // Start a passive scan after idle delay
397         schedulePassiveScan();
398     }
399
400     @Override
401     public @Nullable BluetoothAddress getAddress() {
402         BluetoothAddress addr = address;
403         if (addr != null) {
404             return addr;
405         } else {
406             throw new IllegalStateException("Adapter has not been initialized yet!");
407         }
408     }
409
410     @Override
411     protected BlueGigaBluetoothDevice createDevice(BluetoothAddress address) {
412         return new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
413     }
414
415     /**
416      * Connects to a device.
417      * <p>
418      * If the device is already connected, or the attempt to connect failed, then we return false. If we have reached
419      * the maximum number of connections supported by this dongle, then we return false.
420      *
421      * @param address the device {@link BluetoothAddress} to connect to
422      * @param addressType the {@link BluetoothAddressType} of the device
423      * @return true if the connection was started
424      */
425     public boolean bgConnect(BluetoothAddress address, BluetoothAddressType addressType) {
426         // Check the connection to make sure we're not already connected to this device
427         if (connections.containsValue(address)) {
428             return false;
429         }
430
431         // FIXME: When getting here, I always found all connections to be already taken and thus the code never
432         // proceeded. Relaxing this condition did not do any obvious harm, but now guaranteed that the services are
433         // queried from the device.
434         if (connections.size() == maxConnections + 1) {
435             logger.debug("BlueGiga: Attempt to connect to {} but no connections available.", address);
436             return false;
437         }
438
439         logger.debug("BlueGiga Connect: address {}.", address);
440
441         // @formatter:off
442         BlueGigaConnectDirectCommand command = new BlueGigaConnectDirectCommand.CommandBuilder()
443                 .withAddress(address.toString())
444                 .withAddrType(addressType)
445                 .withConnIntervalMin(configuration.connIntervalMin)
446                 .withConnIntervalMax(configuration.connIntervalMax)
447                 .withLatency(configuration.connLatency)
448                 .withTimeout(configuration.connTimeout)
449                 .build();
450         // @formatter:on
451         try {
452             return sendCommand(command, BlueGigaConnectDirectResponse.class, true).getResult() == BgApiResponse.SUCCESS;
453         } catch (BlueGigaException e) {
454             logger.debug("Error occured when sending connect command to device {}, reason: {}.", address,
455                     e.getMessage());
456             return false;
457         }
458     }
459
460     /**
461      * Close a connection using {@link BlueGigaDisconnectCommand}
462      *
463      * @param connectionHandle
464      * @return
465      */
466     public boolean bgDisconnect(int connectionHandle) {
467         logger.debug("BlueGiga Disconnect: connection {}", connectionHandle);
468         BlueGigaDisconnectCommand command = new BlueGigaDisconnectCommand.CommandBuilder()
469                 .withConnection(connectionHandle).build();
470
471         try {
472             return sendCommand(command, BlueGigaDisconnectResponse.class, true).getResult() == BgApiResponse.SUCCESS;
473         } catch (BlueGigaException e) {
474             logger.debug("Error occured when sending disconnect command to device {}, reason: {}.", address,
475                     e.getMessage());
476             return false;
477         }
478     }
479
480     /**
481      * Start a read of all primary services using {@link BlueGigaReadByGroupTypeCommand}
482      *
483      * @param connectionHandle
484      * @return true if successful
485      */
486     public boolean bgFindPrimaryServices(int connectionHandle) {
487         logger.debug("BlueGiga FindPrimary: connection {}", connectionHandle);
488         // @formatter:off
489         BlueGigaReadByGroupTypeCommand command = new BlueGigaReadByGroupTypeCommand.CommandBuilder()
490                 .withConnection(connectionHandle)
491                 .withStart(1)
492                 .withEnd(65535)
493                 .withUuid(UUID.fromString("00002800-0000-1000-8000-00805F9B34FB"))
494                 .build();
495         // @formatter:on
496         try {
497             return sendCommand(command, BlueGigaReadByGroupTypeResponse.class, true)
498                     .getResult() == BgApiResponse.SUCCESS;
499         } catch (BlueGigaException e) {
500             logger.debug("Error occured when sending read primary services command to device {}, reason: {}.", address,
501                     e.getMessage());
502             return false;
503         }
504     }
505
506     /**
507      * Start a read of all characteristics using {@link BlueGigaFindInformationCommand}
508      *
509      * @param connectionHandle
510      * @return true if successful
511      */
512     public boolean bgFindCharacteristics(int connectionHandle) {
513         logger.debug("BlueGiga Find: connection {}", connectionHandle);
514         // @formatter:off
515         BlueGigaFindInformationCommand command = new BlueGigaFindInformationCommand.CommandBuilder()
516                 .withConnection(connectionHandle)
517                 .withStart(1)
518                 .withEnd(65535)
519                 .build();
520         // @formatter:on
521         try {
522             return sendCommand(command, BlueGigaFindInformationResponse.class, true)
523                     .getResult() == BgApiResponse.SUCCESS;
524         } catch (BlueGigaException e) {
525             logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
526                     e.getMessage());
527             return false;
528         }
529     }
530
531     /**
532      * Read a characteristic using {@link BlueGigaReadByHandleCommand}
533      *
534      * @param connectionHandle
535      * @param handle
536      * @return true if successful
537      */
538     public boolean bgReadCharacteristic(int connectionHandle, int handle) {
539         logger.debug("BlueGiga Read: connection {}, handle {}", connectionHandle, handle);
540         // @formatter:off
541         BlueGigaReadByHandleCommand command = new BlueGigaReadByHandleCommand.CommandBuilder()
542                 .withConnection(connectionHandle)
543                 .withChrHandle(handle)
544                 .build();
545         // @formatter:on
546         try {
547             return sendCommand(command, BlueGigaReadByHandleResponse.class, true).getResult() == BgApiResponse.SUCCESS;
548         } catch (BlueGigaException e) {
549             logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
550                     e.getMessage());
551             return false;
552         }
553     }
554
555     /**
556      * Write a characteristic using {@link BlueGigaAttributeWriteCommand}
557      *
558      * @param connectionHandle
559      * @param handle
560      * @param value
561      * @return true if successful
562      */
563     public boolean bgWriteCharacteristic(int connectionHandle, int handle, int[] value) {
564         logger.debug("BlueGiga Write: connection {}, handle {}", connectionHandle, handle);
565         // @formatter:off
566         BlueGigaAttributeWriteCommand command = new BlueGigaAttributeWriteCommand.CommandBuilder()
567                 .withConnection(connectionHandle)
568                 .withAttHandle(handle)
569                 .withData(value)
570                 .build();
571         // @formatter:on
572         try {
573             return sendCommand(command, BlueGigaAttributeWriteResponse.class, true)
574                     .getResult() == BgApiResponse.SUCCESS;
575         } catch (BlueGigaException e) {
576             logger.debug("Error occured when sending write characteristics command to device {}, reason: {}.", address,
577                     e.getMessage());
578             return false;
579         }
580     }
581
582     /*
583      * The following methods are private methods for handling the BlueGiga protocol
584      */
585     private boolean bgEndProcedure() {
586         try {
587             return sendCommandWithoutChecks(new BlueGigaEndProcedureCommand(), BlueGigaEndProcedureResponse.class)
588                     .getResult() == BgApiResponse.SUCCESS;
589         } catch (BlueGigaException e) {
590             logger.debug("Error occured when sending end procedure command.");
591             return false;
592         }
593     }
594
595     private boolean bgSetMode() {
596         try {
597             // @formatter:off
598             BlueGigaSetModeCommand command = new BlueGigaSetModeCommand.CommandBuilder()
599                     .withConnect(GapConnectableMode.GAP_NON_CONNECTABLE)
600                     .withDiscover(GapDiscoverableMode.GAP_NON_DISCOVERABLE)
601                     .build();
602             // @formatter:on
603             return sendCommandWithoutChecks(command, BlueGigaSetModeResponse.class)
604                     .getResult() == BgApiResponse.SUCCESS;
605         } catch (BlueGigaException e) {
606             logger.debug("Error occured when sending set mode command, reason: {}", e.getMessage());
607             return false;
608         }
609     }
610
611     /**
612      * Starts scanning on the dongle
613      *
614      * @param active true for active scanning
615      */
616     private boolean bgStartScanning(boolean active, int interval, int window) {
617         try {
618             // @formatter:off
619             BlueGigaSetScanParametersCommand scanCommand = new BlueGigaSetScanParametersCommand.CommandBuilder()
620                     .withActiveScanning(active)
621                     .withScanInterval(interval)
622                     .withScanWindow(window)
623                     .build();
624             // @formatter:on
625             if (sendCommand(scanCommand, BlueGigaSetScanParametersResponse.class, false)
626                     .getResult() == BgApiResponse.SUCCESS) {
627                 BlueGigaDiscoverCommand discoverCommand = new BlueGigaDiscoverCommand.CommandBuilder()
628                         .withMode(GapDiscoverMode.GAP_DISCOVER_OBSERVATION).build();
629                 if (sendCommand(discoverCommand, BlueGigaDiscoverResponse.class, false)
630                         .getResult() == BgApiResponse.SUCCESS) {
631                     logger.debug("{} scanning succesfully started.", active ? "Active" : "Passive");
632                     return true;
633                 }
634             }
635         } catch (BlueGigaException e) {
636             logger.debug("Error occured when sending start scan command, reason: {}", e.getMessage());
637         }
638         logger.debug("Scan start failed.");
639         return false;
640     }
641
642     /**
643      * Send command only if initialization phase is successfully done
644      */
645     private <T extends BlueGigaResponse> T sendCommand(BlueGigaCommand command, Class<T> expectedResponse,
646             boolean schedulePassiveScan) throws BlueGigaException {
647         if (!initComplete) {
648             throw new BlueGigaException("BlueGiga not initialized");
649         }
650
651         if (schedulePassiveScan) {
652             cancelScheduledPassiveScan();
653         }
654         try {
655             return sendCommandWithoutChecks(command, expectedResponse);
656         } finally {
657             if (schedulePassiveScan) {
658                 schedulePassiveScan();
659             }
660         }
661     }
662
663     /**
664      * Forcefully send command without any checks
665      */
666     private <T extends BlueGigaResponse> T sendCommandWithoutChecks(BlueGigaCommand command, Class<T> expectedResponse)
667             throws BlueGigaException {
668         if (transactionManager.isPresent()) {
669             return transactionManager.get().sendTransaction(command, expectedResponse, COMMAND_TIMEOUT_MS);
670         } else {
671             throw new BlueGigaException("Transaction manager missing");
672         }
673     }
674
675     /**
676      * Add an event listener for the BlueGiga events
677      *
678      * @param listener the {@link BlueGigaEventListener} to add
679      */
680     public void addEventListener(BlueGigaEventListener listener) {
681         transactionManager.ifPresent(manager -> {
682             manager.addEventListener(listener);
683         });
684     }
685
686     /**
687      * Remove an event listener for the BlueGiga events
688      *
689      * @param listener the {@link BlueGigaEventListener} to remove
690      */
691     public void removeEventListener(BlueGigaEventListener listener) {
692         transactionManager.ifPresent(manager -> {
693             manager.removeEventListener(listener);
694         });
695     }
696
697     @Override
698     public void bluegigaEventReceived(@Nullable BlueGigaResponse event) {
699         if (event instanceof BlueGigaScanResponseEvent) {
700             if (initComplete) {
701                 BlueGigaScanResponseEvent scanEvent = (BlueGigaScanResponseEvent) event;
702
703                 // We use the scan event to add any devices we hear to the devices list
704                 // The device gets created, and then manages itself for discovery etc.
705                 BluetoothAddress sender = new BluetoothAddress(scanEvent.getSender());
706                 BlueGigaBluetoothDevice device = getDevice(sender);
707                 device.setAddressType(scanEvent.getAddressType());
708                 deviceDiscovered(device);
709             } else {
710                 logger.trace("Ignore BlueGigaScanResponseEvent as initialization is not complete");
711             }
712             return;
713         }
714
715         if (event instanceof BlueGigaConnectionStatusEvent) {
716             BlueGigaConnectionStatusEvent connectionEvent = (BlueGigaConnectionStatusEvent) event;
717             connections.put(connectionEvent.getConnection(), new BluetoothAddress(connectionEvent.getAddress()));
718         }
719
720         if (event instanceof BlueGigaDisconnectedEvent) {
721             BlueGigaDisconnectedEvent disconnectedEvent = (BlueGigaDisconnectedEvent) event;
722             connections.remove(disconnectedEvent.getConnection());
723         }
724     }
725
726     @Override
727     public void bluegigaClosed(Exception reason) {
728         logger.debug("BlueGiga connection closed, request reinitialization");
729         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());
730         initComplete = false;
731     }
732 }