2 * Copyright (c) 2010-2021 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.apache.commons.io.IOUtils;
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
37 import org.openhab.binding.bluetooth.BluetoothAddress;
38 import org.openhab.binding.bluetooth.BluetoothBindingConstants;
39 import org.openhab.binding.bluetooth.bluegiga.BlueGigaAdapterConstants;
40 import org.openhab.binding.bluetooth.bluegiga.BlueGigaBluetoothDevice;
41 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaCommand;
42 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaConfiguration;
43 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaEventListener;
44 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaException;
45 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaHandlerListener;
46 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaResponse;
47 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaSerialHandler;
48 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaTransactionManager;
49 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaAttributeWriteCommand;
50 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaAttributeWriteResponse;
51 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaFindInformationCommand;
52 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaFindInformationResponse;
53 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeCommand;
54 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByGroupTypeResponse;
55 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleCommand;
56 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByHandleResponse;
57 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByTypeCommand;
58 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaReadByTypeResponse;
59 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaConnectionStatusEvent;
60 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectCommand;
61 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectResponse;
62 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectedEvent;
63 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaConnectDirectCommand;
64 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaConnectDirectResponse;
65 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaDiscoverCommand;
66 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaDiscoverResponse;
67 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaEndProcedureCommand;
68 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaEndProcedureResponse;
69 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaScanResponseEvent;
70 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetModeCommand;
71 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetModeResponse;
72 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetScanParametersCommand;
73 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaSetScanParametersResponse;
74 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaAddressGetCommand;
75 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaAddressGetResponse;
76 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetConnectionsCommand;
77 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetConnectionsResponse;
78 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetInfoCommand;
79 import org.openhab.binding.bluetooth.bluegiga.internal.command.system.BlueGigaGetInfoResponse;
80 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BgApiResponse;
81 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BluetoothAddressType;
82 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapConnectableMode;
83 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverMode;
84 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.GapDiscoverableMode;
85 import org.openhab.binding.bluetooth.util.RetryException;
86 import org.openhab.binding.bluetooth.util.RetryFuture;
87 import org.openhab.core.common.ThreadPoolManager;
88 import org.openhab.core.io.transport.serial.PortInUseException;
89 import org.openhab.core.io.transport.serial.SerialPort;
90 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
91 import org.openhab.core.io.transport.serial.SerialPortManager;
92 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
93 import org.openhab.core.thing.Bridge;
94 import org.openhab.core.thing.Thing;
95 import org.openhab.core.thing.ThingStatus;
96 import org.openhab.core.thing.ThingStatusDetail;
97 import org.slf4j.Logger;
98 import org.slf4j.LoggerFactory;
101 * The {@link BlueGigaBridgeHandler} is responsible for interfacing to the BlueGiga Bluetooth adapter.
102 * It provides a private interface for {@link BlueGigaBluetoothDevice}s to access the dongle and provides top
103 * level adaptor functionality for scanning and arbitration.
105 * The handler provides the serial interface to the dongle via the BlueGiga BG-API library.
107 * In the BlueGiga dongle, we leave scanning enabled most of the time. Normally, it's just passive scanning, and active
108 * scanning is enabled when we want to include new devices. Passive scanning is enough for us to receive beacons etc
109 * that are transmitted periodically, and active scanning will get more information which may be useful when we are
110 * including new devices.
112 * @author Chris Jackson - Initial contribution
113 * @author Kai Kreuzer - Made handler implement BlueGigaHandlerListener
114 * @author Pauli Anttila - Many improvements
117 public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGigaBluetoothDevice>
118 implements BlueGigaEventListener, BlueGigaHandlerListener {
120 private final Logger logger = LoggerFactory.getLogger(BlueGigaBridgeHandler.class);
122 private final int COMMAND_TIMEOUT_MS = 5000;
123 private final int INITIALIZATION_INTERVAL_SEC = 60;
125 private final SerialPortManager serialPortManager;
127 private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("BlueGiga");
129 private BlueGigaConfiguration configuration = new BlueGigaConfiguration();
131 // The serial port input stream.
132 private Optional<InputStream> inputStream = Optional.empty();
134 // The serial port output stream.
135 private Optional<OutputStream> outputStream = Optional.empty();
137 // The BlueGiga API handler
138 private CompletableFuture<BlueGigaSerialHandler> serialHandler = CompletableFuture
139 .failedFuture(new IllegalStateException("Uninitialized"));
141 // The BlueGiga transaction manager
142 @NonNullByDefault({})
143 private CompletableFuture<BlueGigaTransactionManager> transactionManager = CompletableFuture
144 .failedFuture(new IllegalStateException("Uninitialized"));
146 // The maximum number of connections this interface supports
147 private int maxConnections = 0;
150 private @Nullable BluetoothAddress address;
152 // Map of open connections
153 private final Map<Integer, BluetoothAddress> connections = new ConcurrentHashMap<>();
155 private volatile boolean initComplete = false;
157 private CompletableFuture<SerialPort> serialPortFuture = CompletableFuture
158 .failedFuture(new IllegalStateException("Uninitialized"));
160 private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
161 private @Nullable ScheduledFuture<?> discoveryTask;
162 private @Nullable ScheduledFuture<?> initTask;
164 private @Nullable Future<?> passiveScanIdleTimer;
166 public BlueGigaBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
168 this.serialPortManager = serialPortManager;
172 public void initialize() {
174 updateStatus(ThingStatus.UNKNOWN);
175 if (initTask == null) {
176 initTask = scheduler.scheduleWithFixedDelay(this::checkInit, 0, 10, TimeUnit.SECONDS);
180 protected void checkInit() {
181 boolean init = false;
183 if (!serialHandler.get().isAlive()) {
184 logger.debug("BLE serial handler seems to be dead, reinitilize");
188 } catch (InterruptedException e) {
190 } catch (ExecutionException e) {
195 logger.debug("Initialize BlueGiga");
200 private void start() {
201 Optional<BlueGigaConfiguration> cfg = Optional.of(getConfigAs(BlueGigaConfiguration.class));
202 if (cfg.isPresent()) {
203 initComplete = false;
204 configuration = cfg.get();
205 serialPortFuture = RetryFuture.callWithRetry(() -> {
206 var localFuture = serialPortFuture;
207 logger.debug("Using configuration: {}", configuration);
209 String serialPortName = configuration.port;
210 int baudRate = 115200;
212 logger.debug("Connecting to serial port '{}'", serialPortName);
214 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
215 if (portIdentifier == null) {
216 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port does not exist");
217 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
219 SerialPort sp = portIdentifier.open("org.openhab.binding.bluetooth.bluegiga", 2000);
220 sp.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
221 SerialPort.PARITY_NONE);
223 sp.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_OUT);
224 sp.enableReceiveThreshold(1);
225 sp.enableReceiveTimeout(2000);
227 // RXTX serial port library causes high CPU load
228 // Start event listener, which will just sleep and slow down event loop
229 sp.notifyOnDataAvailable(true);
231 logger.info("Connected to serial port '{}'.", serialPortName);
234 inputStream = Optional.of(new BufferedInputStream(sp.getInputStream()));
235 outputStream = Optional.of(new BufferedOutputStream(sp.getOutputStream()));
236 } catch (IOException e) {
237 logger.error("Error getting serial streams", e);
238 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
240 // if this future has been cancelled while this was running, then we
241 // need to make sure that we close this port
242 localFuture.whenComplete((port, th) -> {
244 // we need to shut down the port now.
249 } catch (PortInUseException e) {
250 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
251 "Serial Error: Port in use");
252 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
253 } catch (UnsupportedCommOperationException e) {
254 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
255 "Serial Error: Unsupported operation");
256 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
257 } catch (RuntimeException ex) {
258 logger.debug("Start failed", ex);
259 throw new RetryException(INITIALIZATION_INTERVAL_SEC, TimeUnit.SECONDS);
263 serialHandler = serialPortFuture
264 .thenApply(sp -> new BlueGigaSerialHandler(getThing().getUID().getAsString(), inputStream.get(),
265 outputStream.get()));
266 transactionManager = serialHandler.thenApply(sh -> {
267 BlueGigaTransactionManager th = new BlueGigaTransactionManager(sh, executor);
268 sh.addHandlerListener(this);
269 th.addEventListener(this);
272 transactionManager.thenRun(() -> {
274 // Stop any procedures that are running
277 // Set mode to non-discoverable etc.
280 // Get maximum parallel connections
281 maxConnections = readMaxConnections().getMaxconn();
283 // Close all connections so we start from a known position
284 for (int connection = 0; connection < maxConnections; connection++) {
285 sendCommandWithoutChecks(
286 new BlueGigaDisconnectCommand.CommandBuilder().withConnection(connection).build(),
287 BlueGigaDisconnectResponse.class);
290 // Get our Bluetooth address
291 address = new BluetoothAddress(readAddress().getAddress());
293 updateThingProperties();
296 updateStatus(ThingStatus.ONLINE);
297 startScheduledTasks();
298 } catch (BlueGigaException e) {
299 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
300 "Initialization of BlueGiga controller failed");
302 }).exceptionally(th -> {
303 if (th instanceof CompletionException && th.getCause() instanceof CancellationException) {
304 // cancellation is a normal reason for failure, so no need to print it.
307 logger.warn("Error initializing bluegiga", th);
312 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR);
317 public void dispose() {
318 if (initTask != null) {
319 initTask.cancel(true);
326 private void stop() {
327 logger.info("Stop BlueGiga");
328 transactionManager.thenAccept(tman -> {
329 tman.removeEventListener(this);
332 serialHandler.thenAccept(sh -> {
333 sh.removeHandlerListener(this);
337 initComplete = false;
340 serialPortFuture.thenAccept(this::closeSerialPort);
341 serialPortFuture.cancel(false);
342 stopScheduledTasks();
345 private void schedulePassiveScan() {
346 cancelScheduledPassiveScan();
347 passiveScanIdleTimer = executor.schedule(() -> {
348 if (!activeScanEnabled) {
349 logger.debug("Activate passive scan");
351 bgStartScanning(false, configuration.passiveScanInterval, configuration.passiveScanWindow);
353 logger.debug("Ignore passive scan activation as active scan is active");
355 }, configuration.passiveScanIdleTime, TimeUnit.MILLISECONDS);
358 private void cancelScheduledPassiveScan() {
359 if (passiveScanIdleTimer != null) {
360 passiveScanIdleTimer.cancel(true);
364 private void startScheduledTasks() {
365 schedulePassiveScan();
366 discoveryTask = scheduler.scheduleWithFixedDelay(this::refreshDiscoveredDevices, 0, 10, TimeUnit.SECONDS);
369 private void stopScheduledTasks() {
370 cancelScheduledPassiveScan();
371 if (removeInactiveDevicesTask != null) {
372 removeInactiveDevicesTask.cancel(true);
373 removeInactiveDevicesTask = null;
375 if (discoveryTask != null) {
376 discoveryTask.cancel(true);
377 discoveryTask = null;
381 private BlueGigaGetConnectionsResponse readMaxConnections() throws BlueGigaException {
382 return sendCommandWithoutChecks(new BlueGigaGetConnectionsCommand(), BlueGigaGetConnectionsResponse.class);
385 private BlueGigaAddressGetResponse readAddress() throws BlueGigaException {
386 return sendCommandWithoutChecks(new BlueGigaAddressGetCommand(), BlueGigaAddressGetResponse.class);
389 private BlueGigaGetInfoResponse readInfo() throws BlueGigaException {
390 return sendCommandWithoutChecks(new BlueGigaGetInfoCommand(), BlueGigaGetInfoResponse.class);
393 private void updateThingProperties() throws BlueGigaException {
394 BlueGigaGetInfoResponse infoResponse = readInfo();
396 Map<String, String> properties = editProperties();
397 properties.put(BluetoothBindingConstants.PROPERTY_MAXCONNECTIONS, Integer.toString(maxConnections));
398 properties.put(Thing.PROPERTY_FIRMWARE_VERSION,
399 String.format("%d.%d", infoResponse.getMajor(), infoResponse.getMinor()));
400 properties.put(Thing.PROPERTY_HARDWARE_VERSION, Integer.toString(infoResponse.getHardware()));
401 properties.put(BlueGigaAdapterConstants.PROPERTY_PROTOCOL, Integer.toString(infoResponse.getProtocolVersion()));
402 properties.put(BlueGigaAdapterConstants.PROPERTY_LINKLAYER, Integer.toString(infoResponse.getLlVersion()));
403 updateProperties(properties);
406 private void closeSerialPort(SerialPort sp) {
407 sp.removeEventListener();
409 sp.disableReceiveTimeout();
410 } catch (Exception e) {
411 // Ignore all as RXTX seems to send arbitrary exceptions when BlueGiga module is detached
413 outputStream.ifPresent(output -> {
414 IOUtils.closeQuietly(output);
416 inputStream.ifPresent(input -> {
417 IOUtils.closeQuietly(input);
420 logger.debug("Closed serial port.");
421 inputStream = Optional.empty();
422 outputStream = Optional.empty();
427 public void scanStart() {
429 logger.debug("Start active scan");
430 // Stop the passive scan
431 cancelScheduledPassiveScan();
434 // Start a active scan
435 bgStartScanning(true, configuration.activeScanInterval, configuration.activeScanWindow);
439 public void scanStop() {
441 logger.debug("Stop active scan");
443 // Stop the active scan
446 // Start a passive scan after idle delay
447 schedulePassiveScan();
451 public @Nullable BluetoothAddress getAddress() {
452 BluetoothAddress addr = address;
456 throw new IllegalStateException("Adapter has not been initialized yet!");
461 protected BlueGigaBluetoothDevice createDevice(BluetoothAddress address) {
462 return new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
466 * Connects to a device.
468 * If the device is already connected, or the attempt to connect failed, then we return false. If we have reached
469 * the maximum number of connections supported by this dongle, then we return false.
471 * @param address the device {@link BluetoothAddress} to connect to
472 * @param addressType the {@link BluetoothAddressType} of the device
473 * @return true if the connection was started
475 public boolean bgConnect(BluetoothAddress address, BluetoothAddressType addressType) {
476 // Check the connection to make sure we're not already connected to this device
477 if (connections.containsValue(address)) {
481 // FIXME: When getting here, I always found all connections to be already taken and thus the code never
482 // proceeded. Relaxing this condition did not do any obvious harm, but now guaranteed that the services are
483 // queried from the device.
484 if (connections.size() == maxConnections + 1) {
485 logger.debug("BlueGiga: Attempt to connect to {} but no connections available.", address);
489 logger.debug("BlueGiga Connect: address {}.", address);
492 BlueGigaConnectDirectCommand command = new BlueGigaConnectDirectCommand.CommandBuilder()
493 .withAddress(address.toString())
494 .withAddrType(addressType)
495 .withConnIntervalMin(configuration.connIntervalMin)
496 .withConnIntervalMax(configuration.connIntervalMax)
497 .withLatency(configuration.connLatency)
498 .withTimeout(configuration.connTimeout)
502 return sendCommand(command, BlueGigaConnectDirectResponse.class, true).getResult() == BgApiResponse.SUCCESS;
503 } catch (BlueGigaException e) {
504 logger.debug("Error occured when sending connect command to device {}, reason: {}.", address,
511 * Close a connection using {@link BlueGigaDisconnectCommand}
513 * @param connectionHandle
516 public boolean bgDisconnect(int connectionHandle) {
517 logger.debug("BlueGiga Disconnect: connection {}", connectionHandle);
518 BlueGigaDisconnectCommand command = new BlueGigaDisconnectCommand.CommandBuilder()
519 .withConnection(connectionHandle).build();
522 return sendCommand(command, BlueGigaDisconnectResponse.class, true).getResult() == BgApiResponse.SUCCESS;
523 } catch (BlueGigaException e) {
524 logger.debug("Error occured when sending disconnect command to device {}, reason: {}.", address,
531 * Start a read of all primary services using {@link BlueGigaReadByGroupTypeCommand}
533 * @param connectionHandle
534 * @return true if successful
536 public boolean bgFindPrimaryServices(int connectionHandle) {
537 logger.debug("BlueGiga FindPrimary: connection {}", connectionHandle);
539 BlueGigaReadByGroupTypeCommand command = new BlueGigaReadByGroupTypeCommand.CommandBuilder()
540 .withConnection(connectionHandle)
543 .withUuid(UUID.fromString("00002800-0000-1000-8000-00805F9B34FB"))
547 return sendCommand(command, BlueGigaReadByGroupTypeResponse.class, true)
548 .getResult() == BgApiResponse.SUCCESS;
549 } catch (BlueGigaException e) {
550 logger.debug("Error occured when sending read primary services command to device {}, reason: {}.", address,
557 * Start a read of all characteristics using {@link BlueGigaFindInformationCommand}
559 * @param connectionHandle
560 * @return true if successful
562 public boolean bgFindCharacteristics(int connectionHandle) {
563 logger.debug("BlueGiga Find: connection {}", connectionHandle);
565 BlueGigaFindInformationCommand command = new BlueGigaFindInformationCommand.CommandBuilder()
566 .withConnection(connectionHandle)
572 return sendCommand(command, BlueGigaFindInformationResponse.class, true)
573 .getResult() == BgApiResponse.SUCCESS;
574 } catch (BlueGigaException e) {
575 logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
581 public boolean bgReadCharacteristicDeclarations(int connectionHandle) {
582 logger.debug("BlueGiga Find: connection {}", connectionHandle);
584 BlueGigaReadByTypeCommand command = new BlueGigaReadByTypeCommand.CommandBuilder()
585 .withConnection(connectionHandle)
588 .withUUID(BluetoothBindingConstants.ATTR_CHARACTERISTIC_DECLARATION)
592 return sendCommand(command, BlueGigaReadByTypeResponse.class, true).getResult() == BgApiResponse.SUCCESS;
593 } catch (BlueGigaException e) {
594 logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
601 * Read a characteristic using {@link BlueGigaReadByHandleCommand}
603 * @param connectionHandle
605 * @return true if successful
607 public boolean bgReadCharacteristic(int connectionHandle, int handle) {
608 logger.debug("BlueGiga Read: connection {}, handle {}", connectionHandle, handle);
610 BlueGigaReadByHandleCommand command = new BlueGigaReadByHandleCommand.CommandBuilder()
611 .withConnection(connectionHandle)
612 .withChrHandle(handle)
616 return sendCommand(command, BlueGigaReadByHandleResponse.class, true).getResult() == BgApiResponse.SUCCESS;
617 } catch (BlueGigaException e) {
618 logger.debug("Error occured when sending read characteristics command to device {}, reason: {}.", address,
625 * Write a characteristic using {@link BlueGigaAttributeWriteCommand}
627 * @param connectionHandle
630 * @return true if successful
632 public boolean bgWriteCharacteristic(int connectionHandle, int handle, int[] value) {
633 logger.debug("BlueGiga Write: connection {}, handle {}", connectionHandle, handle);
635 BlueGigaAttributeWriteCommand command = new BlueGigaAttributeWriteCommand.CommandBuilder()
636 .withConnection(connectionHandle)
637 .withAttHandle(handle)
642 return sendCommand(command, BlueGigaAttributeWriteResponse.class, true)
643 .getResult() == BgApiResponse.SUCCESS;
644 } catch (BlueGigaException e) {
645 logger.debug("Error occured when sending write characteristics command to device {}, reason: {}.", address,
652 * The following methods are private methods for handling the BlueGiga protocol
654 private boolean bgEndProcedure() {
656 return sendCommandWithoutChecks(new BlueGigaEndProcedureCommand(), BlueGigaEndProcedureResponse.class)
657 .getResult() == BgApiResponse.SUCCESS;
658 } catch (BlueGigaException e) {
659 logger.debug("Error occured when sending end procedure command.");
664 private boolean bgSetMode() {
667 BlueGigaSetModeCommand command = new BlueGigaSetModeCommand.CommandBuilder()
668 .withConnect(GapConnectableMode.GAP_NON_CONNECTABLE)
669 .withDiscover(GapDiscoverableMode.GAP_NON_DISCOVERABLE)
672 return sendCommandWithoutChecks(command, BlueGigaSetModeResponse.class)
673 .getResult() == BgApiResponse.SUCCESS;
674 } catch (BlueGigaException e) {
675 logger.debug("Error occured when sending set mode command, reason: {}", e.getMessage());
681 * Starts scanning on the dongle
683 * @param active true for active scanning
685 private boolean bgStartScanning(boolean active, int interval, int window) {
688 BlueGigaSetScanParametersCommand scanCommand = new BlueGigaSetScanParametersCommand.CommandBuilder()
689 .withActiveScanning(active)
690 .withScanInterval(interval)
691 .withScanWindow(window)
694 if (sendCommand(scanCommand, BlueGigaSetScanParametersResponse.class, false)
695 .getResult() == BgApiResponse.SUCCESS) {
696 BlueGigaDiscoverCommand discoverCommand = new BlueGigaDiscoverCommand.CommandBuilder()
697 .withMode(GapDiscoverMode.GAP_DISCOVER_OBSERVATION).build();
698 if (sendCommand(discoverCommand, BlueGigaDiscoverResponse.class, false)
699 .getResult() == BgApiResponse.SUCCESS) {
700 logger.debug("{} scanning succesfully started.", active ? "Active" : "Passive");
704 } catch (BlueGigaException e) {
705 logger.debug("Error occured when sending start scan command, reason: {}", e.getMessage());
707 logger.debug("Scan start failed.");
712 * Send command only if initialization phase is successfully done
714 private <T extends BlueGigaResponse> T sendCommand(BlueGigaCommand command, Class<T> expectedResponse,
715 boolean schedulePassiveScan) throws BlueGigaException {
717 throw new BlueGigaException("BlueGiga not initialized");
720 if (schedulePassiveScan) {
721 cancelScheduledPassiveScan();
724 return sendCommandWithoutChecks(command, expectedResponse);
726 if (schedulePassiveScan) {
727 schedulePassiveScan();
733 * Forcefully send command without any checks
735 private <T extends BlueGigaResponse> T sendCommandWithoutChecks(BlueGigaCommand command, Class<T> expectedResponse)
736 throws BlueGigaException {
737 BlueGigaTransactionManager manager = transactionManager.getNow(null);
738 if (manager != null) {
739 return manager.sendTransaction(command, expectedResponse, COMMAND_TIMEOUT_MS);
741 throw new BlueGigaException("Transaction manager missing");
746 * Add an event listener for the BlueGiga events
748 * @param listener the {@link BlueGigaEventListener} to add
750 public void addEventListener(BlueGigaEventListener listener) {
751 transactionManager.thenAccept(manager -> {
752 manager.addEventListener(listener);
757 * Remove an event listener for the BlueGiga events
759 * @param listener the {@link BlueGigaEventListener} to remove
761 public void removeEventListener(BlueGigaEventListener listener) {
762 transactionManager.thenAccept(manager -> {
763 manager.removeEventListener(listener);
768 public void bluegigaEventReceived(@Nullable BlueGigaResponse event) {
769 if (event instanceof BlueGigaScanResponseEvent) {
771 BlueGigaScanResponseEvent scanEvent = (BlueGigaScanResponseEvent) event;
773 // We use the scan event to add any devices we hear to the devices list
774 // The device gets created, and then manages itself for discovery etc.
775 BluetoothAddress sender = new BluetoothAddress(scanEvent.getSender());
776 BlueGigaBluetoothDevice device = getDevice(sender);
777 device.setAddressType(scanEvent.getAddressType());
778 deviceDiscovered(device);
780 logger.trace("Ignore BlueGigaScanResponseEvent as initialization is not complete");
785 if (event instanceof BlueGigaConnectionStatusEvent) {
786 BlueGigaConnectionStatusEvent connectionEvent = (BlueGigaConnectionStatusEvent) event;
787 connections.put(connectionEvent.getConnection(), new BluetoothAddress(connectionEvent.getAddress()));
790 if (event instanceof BlueGigaDisconnectedEvent) {
791 BlueGigaDisconnectedEvent disconnectedEvent = (BlueGigaDisconnectedEvent) event;
792 connections.remove(disconnectedEvent.getConnection());
797 public void bluegigaClosed(Exception reason) {
798 logger.debug("BlueGiga connection closed, request reinitialization, reason: {}", reason.getMessage());
799 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());