2 * Copyright (c) 2010-2023 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.handler;
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;
21 import java.util.Optional;
22 import java.util.UUID;
23 import java.util.concurrent.CancellationException;
24 import java.util.concurrent.CompletableFuture;
25 import java.util.concurrent.CompletionException;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.Future;
29 import java.util.concurrent.ScheduledExecutorService;
30 import java.util.concurrent.ScheduledFuture;
31 import java.util.concurrent.TimeUnit;
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
36 import org.openhab.binding.bluetooth.BluetoothAddress;
37 import org.openhab.binding.bluetooth.BluetoothBindingConstants;
38 import org.openhab.binding.bluetooth.bluegiga.BlueGigaAdapterConstants;
39 import org.openhab.binding.bluetooth.bluegiga.BlueGigaBluetoothDevice;
40 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaCommand;
41 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaConfiguration;
42 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaEventListener;
43 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaException;
44 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaHandlerListener;
45 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaResponse;
46 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaSerialHandler;
47 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaTransactionManager;
48 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaAttributeWriteCommand;
49 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaAttributeWriteResponse;
50 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaFindInformationCommand;
51 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaFindInformationResponse;
52 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeCommand;
53 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeResponse;
54 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleCommand;
55 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleResponse;
56 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByTypeCommand;
57 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByTypeResponse;
58 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaConnectionStatusEvent;
59 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectCommand;
60 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectResponse;
61 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectedEvent;
62 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaConnectDirectCommand;
63 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaConnectDirectResponse;
64 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaDiscoverCommand;
65 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaDiscoverResponse;
66 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaEndProcedureCommand;
67 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaEndProcedureResponse;
68 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaScanResponseEvent;
69 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetModeCommand;
70 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetModeResponse;
71 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetScanParametersCommand;
72 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetScanParametersResponse;
73 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaAddressGetCommand;
74 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaAddressGetResponse;
75 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetConnectionsCommand;
76 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetConnectionsResponse;
77 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetInfoCommand;
78 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetInfoResponse;
79 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BgApiResponse;
80 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BluetoothAddressType;
81 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapConnectableMode;
82 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverMode;
83 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverableMode;
84 import org.openhab.binding.bluetooth.util.RetryException;
85 import org.openhab.binding.bluetooth.util.RetryFuture;
86 import org.openhab.core.common.ThreadPoolManager;
87 import org.openhab.core.io.transport.serial.PortInUseException;
88 import org.openhab.core.io.transport.serial.SerialPort;
89 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
90 import org.openhab.core.io.transport.serial.SerialPortManager;
91 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
92 import org.openhab.core.thing.Bridge;
93 import org.openhab.core.thing.Thing;
94 import org.openhab.core.thing.ThingStatus;
95 import org.openhab.core.thing.ThingStatusDetail;
96 import org.slf4j.Logger;
97 import org.slf4j.LoggerFactory;
100 * The {@link BlueGigaBridgeHandler} is responsible for interfacing to the BlueGiga Bluetooth adapter.
101 * It provides a private interface for {@link BlueGigaBluetoothDevice}s to access the dongle and provides top
102 * level adaptor functionality for scanning and arbitration.
104 * The handler provides the serial interface to the dongle via the BlueGiga BG-API library.
106 * In the BlueGiga dongle, we leave scanning enabled most of the time. Normally, it's just passive scanning, and active
107 * scanning is enabled when we want to include new devices. Passive scanning is enough for us to receive beacons etc
108 * that are transmitted periodically, and active scanning will get more information which may be useful when we are
109 * including new devices.
111 * @author Chris Jackson - Initial contribution
112 * @author Kai Kreuzer - Made handler implement BlueGigaHandlerListener
113 * @author Pauli Anttila - Many improvements
116 public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGigaBluetoothDevice>
117 implements BlueGigaEventListener, BlueGigaHandlerListener {
119 private final Logger logger = LoggerFactory.getLogger(BlueGigaBridgeHandler.class);
121 private static final int COMMAND_TIMEOUT_MS = 5000;
122 private static final int INITIALIZATION_INTERVAL_SEC = 60;
124 private final SerialPortManager serialPortManager;
126 private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("BlueGiga");
128 private BlueGigaConfiguration configuration = new BlueGigaConfiguration();
130 // The serial port input stream.
131 private Optional<InputStream> inputStream = Optional.empty();
133 // The serial port output stream.
134 private Optional<OutputStream> outputStream = Optional.empty();
136 // The BlueGiga API handler
137 private CompletableFuture<BlueGigaSerialHandler> serialHandler = CompletableFuture
138 .failedFuture(new IllegalStateException("Uninitialized"));
140 // The BlueGiga transaction manager
141 @NonNullByDefault({})
142 private CompletableFuture<BlueGigaTransactionManager> transactionManager = CompletableFuture
143 .failedFuture(new IllegalStateException("Uninitialized"));
145 // The maximum number of connections this interface supports
146 private int maxConnections = 0;
149 private @Nullable BluetoothAddress address;
151 // Map of open connections
152 private final Map<Integer, BluetoothAddress> connections = new ConcurrentHashMap<>();
154 private volatile boolean initComplete = false;
156 private CompletableFuture<SerialPort> serialPortFuture = CompletableFuture
157 .failedFuture(new IllegalStateException("Uninitialized"));
159 private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
160 private @Nullable ScheduledFuture<?> discoveryTask;
161 private @Nullable ScheduledFuture<?> initTask;
163 private @Nullable Future<?> passiveScanIdleTimer;
165 public BlueGigaBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
167 this.serialPortManager = serialPortManager;
171 public void initialize() {
173 updateStatus(ThingStatus.UNKNOWN);
174 if (initTask == null) {
175 initTask = scheduler.scheduleWithFixedDelay(this::checkInit, 0, 10, TimeUnit.SECONDS);
179 protected void checkInit() {
180 boolean init = false;
182 if (!serialHandler.get().isAlive()) {
183 logger.debug("BLE serial handler seems to be dead, reinitilize");
187 } catch (InterruptedException e) {
189 } catch (ExecutionException e) {
194 logger.debug("Initialize BlueGiga");
199 private void start() {
200 Optional<BlueGigaConfiguration> cfg = Optional.of(getConfigAs(BlueGigaConfiguration.class));
201 if (cfg.isPresent()) {
202 initComplete = false;
203 configuration = cfg.get();
204 serialPortFuture = RetryFuture.callWithRetry(() -> {
205 var localFuture = serialPortFuture;
206 logger.debug("Using configuration: {}", configuration);
208 String serialPortName = configuration.port;
209 int baudRate = 115200;
211 logger.debug("Connecting to serial port '{}'", serialPortName);
213 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
214 if (portIdentifier == null) {
215 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port does not exist");
216 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
218 SerialPort sp = portIdentifier.open("org.openhab.binding.bluetooth.bluegiga", 2000);
219 sp.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
220 SerialPort.PARITY_NONE);
222 sp.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_OUT);
223 sp.enableReceiveThreshold(1);
224 sp.enableReceiveTimeout(2000);
226 // RXTX serial port library causes high CPU load
227 // Start event listener, which will just sleep and slow down event loop
228 sp.notifyOnDataAvailable(true);
230 logger.info("Connected to serial port '{}'.", serialPortName);
233 inputStream = Optional.of(new BufferedInputStream(sp.getInputStream()));
234 outputStream = Optional.of(new BufferedOutputStream(sp.getOutputStream()));
235 } catch (IOException e) {
236 logger.error("Error getting serial streams", e);
237 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
239 // if this future has been cancelled while this was running, then we
240 // need to make sure that we close this port
241 localFuture.whenComplete((port, th) -> {
243 // we need to shut down the port now.
248 } catch (PortInUseException e) {
249 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
250 "Serial Error: Port in use");
251 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
252 } catch (UnsupportedCommOperationException e) {
253 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
254 "Serial Error: Unsupported operation");
255 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
256 } catch (RuntimeException ex) {
257 logger.debug("Start failed", ex);
258 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
262 serialHandler = serialPortFuture
263 .thenApply(sp -> new BlueGigaSerialHandler(getThing().getUID().getAsString(), inputStream.get(),
264 outputStream.get()));
265 transactionManager = serialHandler.thenApply(sh -> {
266 BlueGigaTransactionManager th = new BlueGigaTransactionManager(sh, executor);
267 sh.addHandlerListener(this);
268 th.addEventListener(this);
271 transactionManager.thenRun(() -> {
273 // Stop any procedures that are running
276 // Set mode to non-discoverable etc.
279 // Get maximum parallel connections
280 maxConnections = readMaxConnections().getMaxconn();
282 // Close all connections so we start from a known position
283 for (int connection = 0; connection < maxConnections; connection++) {
284 sendCommandWithoutChecks(
285 new BlueGigaDisconnectCommand.CommandBuilder().withConnection(connection).build(),
286 BlueGigaDisconnectResponse.class);
289 // Get our Bluetooth address
290 address = new BluetoothAddress(readAddress().getAddress());
292 updateThingProperties();
295 updateStatus(ThingStatus.ONLINE);
296 startScheduledTasks();
297 } catch (BlueGigaException e) {
298 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
299 "Initialization of BlueGiga controller failed");
301 }).exceptionally(th -> {
302 if (th instanceof CompletionException && th.getCause() instanceof CancellationException) {
303 // cancellation is a normal reason for failure, so no need to print it.
306 logger.warn("Error initializing bluegiga", th);
311 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR);
316 public void dispose() {
318 ScheduledFuture<?> task = initTask;
327 private void stop() {
328 logger.info("Stop BlueGiga");
329 transactionManager.thenAccept(tman -> {
330 tman.removeEventListener(this);
333 serialHandler.thenAccept(sh -> {
334 sh.removeHandlerListener(this);
338 initComplete = false;
341 serialPortFuture.thenAccept(this::closeSerialPort);
342 serialPortFuture.cancel(false);
343 stopScheduledTasks();
346 private void schedulePassiveScan() {
347 cancelScheduledPassiveScan();
348 passiveScanIdleTimer = executor.schedule(() -> {
349 if (!activeScanEnabled) {
350 logger.debug("Activate passive scan");
352 bgStartScanning(false, configuration.passiveScanInterval, configuration.passiveScanWindow);
354 logger.debug("Ignore passive scan activation as active scan is active");
356 }, configuration.passiveScanIdleTime, TimeUnit.MILLISECONDS);
359 private void cancelScheduledPassiveScan() {
361 Future<?> scanTimer = passiveScanIdleTimer;
362 if (scanTimer != null) {
363 scanTimer.cancel(true);
367 private void startScheduledTasks() {
368 schedulePassiveScan();
369 discoveryTask = scheduler.scheduleWithFixedDelay(this::refreshDiscoveredDevices, 0, 10, TimeUnit.SECONDS);
372 private void stopScheduledTasks() {
373 cancelScheduledPassiveScan();
375 ScheduledFuture<?> removeTask = removeInactiveDevicesTask;
376 if (removeTask != null) {
377 removeTask.cancel(true);
381 ScheduledFuture<?> discoverTask = discoveryTask;
382 if (discoverTask != null) {
383 discoverTask.cancel(true);
388 private BlueGigaGetConnectionsResponse readMaxConnections() throws BlueGigaException {
389 return sendCommandWithoutChecks(new BlueGigaGetConnectionsCommand(), BlueGigaGetConnectionsResponse.class);
392 private BlueGigaAddressGetResponse readAddress() throws BlueGigaException {
393 return sendCommandWithoutChecks(new BlueGigaAddressGetCommand(), BlueGigaAddressGetResponse.class);
396 private BlueGigaGetInfoResponse readInfo() throws BlueGigaException {
397 return sendCommandWithoutChecks(new BlueGigaGetInfoCommand(), BlueGigaGetInfoResponse.class);
400 private void updateThingProperties() throws BlueGigaException {
401 BlueGigaGetInfoResponse infoResponse = readInfo();
403 Map<String, String> properties = editProperties();
404 properties.put(BluetoothBindingConstants.PROPERTY_MAXCONNECTIONS, Integer.toString(maxConnections));
405 properties.put(Thing.PROPERTY_FIRMWARE_VERSION,
406 String.format("%d.%d", infoResponse.getMajor(), infoResponse.getMinor()));
407 properties.put(Thing.PROPERTY_HARDWARE_VERSION, Integer.toString(infoResponse.getHardware()));
408 properties.put(BlueGigaAdapterConstants.PROPERTY_PROTOCOL, Integer.toString(infoResponse.getProtocolVersion()));
409 properties.put(BlueGigaAdapterConstants.PROPERTY_LINKLAYER, Integer.toString(infoResponse.getLlVersion()));
410 updateProperties(properties);
413 private void closeSerialPort(SerialPort sp) {
414 sp.removeEventListener();
416 sp.disableReceiveTimeout();
417 } catch (Exception e) {
418 // Ignore all as RXTX seems to send arbitrary exceptions when BlueGiga module is detached
420 outputStream.ifPresent(output -> {
423 } catch (IOException e) {
426 inputStream.ifPresent(input -> {
429 } catch (IOException e) {
433 logger.debug("Closed serial port.");
434 inputStream = Optional.empty();
435 outputStream = Optional.empty();
440 public void scanStart() {
442 logger.debug("Start active scan");
443 // Stop the passive scan
444 cancelScheduledPassiveScan();
447 // Start an active scan
448 bgStartScanning(true, configuration.activeScanInterval, configuration.activeScanWindow);
452 public void scanStop() {
454 logger.debug("Stop active scan");
456 // Stop the active scan
459 // Start a passive scan after idle delay
460 schedulePassiveScan();
464 public @Nullable BluetoothAddress getAddress() {
465 BluetoothAddress addr = address;
469 throw new IllegalStateException("Adapter has not been initialized yet!");
474 protected BlueGigaBluetoothDevice createDevice(BluetoothAddress address) {
475 return new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
479 * Connects to a device.
481 * If the device is already connected, or the attempt to connect failed, then we return false. If we have reached
482 * the maximum number of connections supported by this dongle, then we return false.
484 * @param address the device {@link BluetoothAddress} to connect to
485 * @param addressType the {@link BluetoothAddressType} of the device
486 * @return true if the connection was started
488 public boolean bgConnect(BluetoothAddress address, BluetoothAddressType addressType) {
489 // Check the connection to make sure we're not already connected to this device
490 if (connections.containsValue(address)) {
494 // FIXME: When getting here, I always found all connections to be already taken and thus the code never
495 // proceeded. Relaxing this condition did not do any obvious harm, but now guaranteed that the services are
496 // queried from the device.
497 if (connections.size() == maxConnections + 1) {
498 logger.debug("BlueGiga: Attempt to connect to {} but no connections available.", address);
502 logger.debug("BlueGiga Connect: address {}.", address);
505 BlueGigaConnectDirectCommand command = new BlueGigaConnectDirectCommand.CommandBuilder()
506 .withAddress(address.toString())
507 .withAddrType(addressType)
508 .withConnIntervalMin(configuration.connIntervalMin)
509 .withConnIntervalMax(configuration.connIntervalMax)
510 .withLatency(configuration.connLatency)
511 .withTimeout(configuration.connTimeout)
515 return sendCommand(command, BlueGigaConnectDirectResponse.class, true).getResult() == BgApiResponse.SUCCESS;
516 } catch (BlueGigaException e) {
517 logger.debug("Error occured when sending connect command to device {}, reason: {}.", address,
524 * Close a connection using {@link BlueGigaDisconnectCommand}
526 * @param connectionHandle
529 public boolean bgDisconnect(int connectionHandle) {
530 logger.debug("BlueGiga Disconnect: connection {}", connectionHandle);
531 BlueGigaDisconnectCommand command = new BlueGigaDisconnectCommand.CommandBuilder()
532 .withConnection(connectionHandle).build();
535 return sendCommand(command, BlueGigaDisconnectResponse.class, true).getResult() == BgApiResponse.SUCCESS;
536 } catch (BlueGigaException e) {
537 logger.debug("Error occured when sending disconnect command to device {}, reason: {}.", address,
544 * Start a read of all primary services using {@link BlueGigaReadByGroupTypeCommand}
546 * @param connectionHandle
547 * @return true if successful
549 public boolean bgFindPrimaryServices(int connectionHandle) {
550 logger.debug("BlueGiga FindPrimary: connection {}", connectionHandle);
552 BlueGigaReadByGroupTypeCommand command = new BlueGigaReadByGroupTypeCommand.CommandBuilder()
553 .withConnection(connectionHandle)
556 .withUuid(UUID.fromString("00002800-0000-1000-8000-00805F9B34FB"))
560 return sendCommand(command, BlueGigaReadByGroupTypeResponse.class, true)
561 .getResult() == BgApiResponse.SUCCESS;
562 } catch (BlueGigaException e) {
563 logger.debug("Error occured when sending read primary services command to device {}, reason: {}.", address,
570 * Start a read of all characteristics using {@link BlueGigaFindInformationCommand}
572 * @param connectionHandle
573 * @return true if successful
575 public boolean bgFindCharacteristics(int connectionHandle) {
576 logger.debug("BlueGiga Find: connection {}", connectionHandle);
578 BlueGigaFindInformationCommand command = new BlueGigaFindInformationCommand.CommandBuilder()
579 .withConnection(connectionHandle)
585 return sendCommand(command, BlueGigaFindInformationResponse.class, true)
586 .getResult() == BgApiResponse.SUCCESS;
587 } catch (BlueGigaException e) {
588 logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
594 public boolean bgReadCharacteristicDeclarations(int connectionHandle) {
595 logger.debug("BlueGiga Find: connection {}", connectionHandle);
597 BlueGigaReadByTypeCommand command = new BlueGigaReadByTypeCommand.CommandBuilder()
598 .withConnection(connectionHandle)
601 .withUUID(BluetoothBindingConstants.ATTR_CHARACTERISTIC_DECLARATION)
605 return sendCommand(command, BlueGigaReadByTypeResponse.class, true).getResult() == BgApiResponse.SUCCESS;
606 } catch (BlueGigaException e) {
607 logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
614 * Read a characteristic using {@link BlueGigaReadByHandleCommand}
616 * @param connectionHandle
618 * @return true if successful
620 public boolean bgReadCharacteristic(int connectionHandle, int handle) {
621 logger.debug("BlueGiga Read: connection {}, handle {}", connectionHandle, handle);
623 BlueGigaReadByHandleCommand command = new BlueGigaReadByHandleCommand.CommandBuilder()
624 .withConnection(connectionHandle)
625 .withChrHandle(handle)
629 return sendCommand(command, BlueGigaReadByHandleResponse.class, true).getResult() == BgApiResponse.SUCCESS;
630 } catch (BlueGigaException e) {
631 logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
638 * Write a characteristic using {@link BlueGigaAttributeWriteCommand}
640 * @param connectionHandle
643 * @return true if successful
645 public boolean bgWriteCharacteristic(int connectionHandle, int handle, int[] value) {
646 logger.debug("BlueGiga Write: connection {}, handle {}", connectionHandle, handle);
648 BlueGigaAttributeWriteCommand command = new BlueGigaAttributeWriteCommand.CommandBuilder()
649 .withConnection(connectionHandle)
650 .withAttHandle(handle)
655 return sendCommand(command, BlueGigaAttributeWriteResponse.class, true)
656 .getResult() == BgApiResponse.SUCCESS;
657 } catch (BlueGigaException e) {
658 logger.debug("Error occured when sending write characteristics command to device {}, reason: {}.", address,
665 * The following methods are private methods for handling the BlueGiga protocol
667 private boolean bgEndProcedure() {
669 return sendCommandWithoutChecks(new BlueGigaEndProcedureCommand(), BlueGigaEndProcedureResponse.class)
670 .getResult() == BgApiResponse.SUCCESS;
671 } catch (BlueGigaException e) {
672 logger.debug("Error occured when sending end procedure command.");
677 private boolean bgSetMode() {
680 BlueGigaSetModeCommand command = new BlueGigaSetModeCommand.CommandBuilder()
681 .withConnect(GapConnectableMode.GAP_NON_CONNECTABLE)
682 .withDiscover(GapDiscoverableMode.GAP_NON_DISCOVERABLE)
685 return sendCommandWithoutChecks(command, BlueGigaSetModeResponse.class)
686 .getResult() == BgApiResponse.SUCCESS;
687 } catch (BlueGigaException e) {
688 logger.debug("Error occured when sending set mode command, reason: {}", e.getMessage());
694 * Starts scanning on the dongle
696 * @param active true for active scanning
698 private boolean bgStartScanning(boolean active, int interval, int window) {
701 BlueGigaSetScanParametersCommand scanCommand = new BlueGigaSetScanParametersCommand.CommandBuilder()
702 .withActiveScanning(active)
703 .withScanInterval(interval)
704 .withScanWindow(window)
707 if (sendCommand(scanCommand, BlueGigaSetScanParametersResponse.class, false)
708 .getResult() == BgApiResponse.SUCCESS) {
709 BlueGigaDiscoverCommand discoverCommand = new BlueGigaDiscoverCommand.CommandBuilder()
710 .withMode(GapDiscoverMode.GAP_DISCOVER_OBSERVATION).build();
711 if (sendCommand(discoverCommand, BlueGigaDiscoverResponse.class, false)
712 .getResult() == BgApiResponse.SUCCESS) {
713 logger.debug("{} scanning successfully started.", active ? "Active" : "Passive");
717 } catch (BlueGigaException e) {
718 logger.debug("Error occured when sending start scan command, reason: {}", e.getMessage());
720 logger.debug("Scan start failed.");
725 * Send command only if initialization phase is successfully done
727 private <T extends BlueGigaResponse> T sendCommand(BlueGigaCommand command, Class<T> expectedResponse,
728 boolean schedulePassiveScan) throws BlueGigaException {
730 throw new BlueGigaException("BlueGiga not initialized");
733 if (schedulePassiveScan) {
734 cancelScheduledPassiveScan();
737 return sendCommandWithoutChecks(command, expectedResponse);
739 if (schedulePassiveScan) {
740 schedulePassiveScan();
746 * Forcefully send command without any checks
748 private <T extends BlueGigaResponse> T sendCommandWithoutChecks(BlueGigaCommand command, Class<T> expectedResponse)
749 throws BlueGigaException {
750 BlueGigaTransactionManager manager = transactionManager.getNow(null);
751 if (manager != null) {
752 return manager.sendTransaction(command, expectedResponse, COMMAND_TIMEOUT_MS);
754 throw new BlueGigaException("Transaction manager missing");
759 * Add an event listener for the BlueGiga events
761 * @param listener the {@link BlueGigaEventListener} to add
763 public void addEventListener(BlueGigaEventListener listener) {
764 transactionManager.thenAccept(manager -> {
765 manager.addEventListener(listener);
770 * Remove an event listener for the BlueGiga events
772 * @param listener the {@link BlueGigaEventListener} to remove
774 public void removeEventListener(BlueGigaEventListener listener) {
775 transactionManager.thenAccept(manager -> {
776 manager.removeEventListener(listener);
781 public void bluegigaEventReceived(@Nullable BlueGigaResponse event) {
782 if (event instanceof BlueGigaScanResponseEvent) {
784 BlueGigaScanResponseEvent scanEvent = (BlueGigaScanResponseEvent) event;
786 // We use the scan event to add any devices we hear to the devices list
787 // The device gets created, and then manages itself for discovery etc.
788 BluetoothAddress sender = new BluetoothAddress(scanEvent.getSender());
789 BlueGigaBluetoothDevice device = getDevice(sender);
790 device.setAddressType(scanEvent.getAddressType());
791 deviceDiscovered(device);
793 logger.trace("Ignore BlueGigaScanResponseEvent as initialization is not complete");
798 if (event instanceof BlueGigaConnectionStatusEvent) {
799 BlueGigaConnectionStatusEvent connectionEvent = (BlueGigaConnectionStatusEvent) event;
800 connections.put(connectionEvent.getConnection(), new BluetoothAddress(connectionEvent.getAddress()));
803 if (event instanceof BlueGigaDisconnectedEvent) {
804 BlueGigaDisconnectedEvent disconnectedEvent = (BlueGigaDisconnectedEvent) event;
805 connections.remove(disconnectedEvent.getConnection());
810 public void bluegigaClosed(Exception reason) {
811 logger.debug("BlueGiga connection closed, request reinitialization, reason: {}", reason.getMessage());
812 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());