2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bluetooth.bluegiga.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.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;
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;
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.
97 * The handler provides the serial interface to the dongle via the BlueGiga BG-API library.
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.
104 * @author Chris Jackson - Initial contribution
105 * @author Kai Kreuzer - Made handler implement BlueGigaHandlerListener
106 * @author Pauli Anttila - Many improvements
109 public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGigaBluetoothDevice>
110 implements BlueGigaEventListener, BlueGigaHandlerListener {
112 private final Logger logger = LoggerFactory.getLogger(BlueGigaBridgeHandler.class);
114 private final int COMMAND_TIMEOUT_MS = 5000;
115 private final int INITIALIZATION_INTERVAL_SEC = 60;
117 private final SerialPortManager serialPortManager;
119 private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("BlueGiga");
122 private Optional<SerialPort> serialPort = Optional.empty();
124 private BlueGigaConfiguration configuration = new BlueGigaConfiguration();
126 // The serial port input stream.
127 private Optional<InputStream> inputStream = Optional.empty();
129 // The serial port output stream.
130 private Optional<OutputStream> outputStream = Optional.empty();
132 // The BlueGiga API handler
133 private Optional<BlueGigaSerialHandler> serialHandler = Optional.empty();
135 // The BlueGiga transaction manager
136 private Optional<BlueGigaTransactionManager> transactionManager = Optional.empty();
138 // The maximum number of connections this interface supports
139 private int maxConnections = 0;
142 private @Nullable BluetoothAddress address;
144 // Map of open connections
145 private final Map<Integer, BluetoothAddress> connections = new ConcurrentHashMap<>();
147 private volatile boolean initComplete = false;
149 private @Nullable ScheduledFuture<?> initTask;
150 private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
151 private @Nullable ScheduledFuture<?> discoveryTask;
153 private @Nullable Future<?> passiveScanIdleTimer;
155 public BlueGigaBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
157 this.serialPortManager = serialPortManager;
161 public void 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);
168 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR);
173 public void dispose() {
175 stopScheduledTasks();
176 if (initTask != null) {
177 initTask.cancel(true);
182 private void start() {
185 logger.debug("Initialize BlueGiga");
186 logger.debug("Using configuration: {}", configuration);
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);
196 // Stop any procedures that are running
199 // Set mode to non-discoverable etc.
202 // Get maximum parallel connections
203 maxConnections = readMaxConnections().getMaxconn();
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);
212 // Get our Bluetooth address
213 address = new BluetoothAddress(readAddress().getAddress());
215 updateThingProperties();
218 updateStatus(ThingStatus.ONLINE);
219 startScheduledTasks();
220 } catch (BlueGigaException e) {
221 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
222 "Initialization of BlueGiga controller failed");
226 } catch (RuntimeException e) {
227 // Avoid scheduled task to shutdown
228 // e.g. when BlueGiga module is detached
229 logger.debug("Start failed", e);
233 private void stop() {
234 if (transactionManager.isPresent()) {
235 transactionManager.get().removeEventListener(this);
236 transactionManager.get().close();
237 transactionManager = Optional.empty();
239 if (serialHandler.isPresent()) {
240 serialHandler.get().removeHandlerListener(this);
241 serialHandler.get().close();
242 serialHandler = Optional.empty();
245 initComplete = false;
250 private void schedulePassiveScan() {
251 cancelScheduledPassiveScan();
252 passiveScanIdleTimer = executor.schedule(() -> {
253 if (!activeScanEnabled) {
254 logger.debug("Activate passive scan");
256 bgStartScanning(false, configuration.passiveScanInterval, configuration.passiveScanWindow);
258 logger.debug("Ignore passive scan activation as active scan is active");
260 }, configuration.passiveScanIdleTime, TimeUnit.MILLISECONDS);
263 private void cancelScheduledPassiveScan() {
264 if (passiveScanIdleTimer != null) {
265 passiveScanIdleTimer.cancel(true);
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);
275 private void stopScheduledTasks() {
276 cancelScheduledPassiveScan();
277 if (removeInactiveDevicesTask != null) {
278 removeInactiveDevicesTask.cancel(true);
279 removeInactiveDevicesTask = null;
281 if (discoveryTask != null) {
282 discoveryTask.cancel(true);
283 discoveryTask = null;
287 private BlueGigaGetConnectionsResponse readMaxConnections() throws BlueGigaException {
288 return sendCommandWithoutChecks(new BlueGigaGetConnectionsCommand(), BlueGigaGetConnectionsResponse.class);
291 private BlueGigaAddressGetResponse readAddress() throws BlueGigaException {
292 return sendCommandWithoutChecks(new BlueGigaAddressGetCommand(), BlueGigaAddressGetResponse.class);
295 private BlueGigaGetInfoResponse readInfo() throws BlueGigaException {
296 return sendCommandWithoutChecks(new BlueGigaGetInfoCommand(), BlueGigaGetInfoResponse.class);
299 private void updateThingProperties() throws BlueGigaException {
300 BlueGigaGetInfoResponse infoResponse = readInfo();
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);
312 private boolean openSerialPort(final String serialPortName, int baudRate) {
313 logger.debug("Connecting to serial port '{}'", serialPortName);
315 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
316 if (portIdentifier == null) {
317 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port does not exist");
320 SerialPort sp = portIdentifier.open("org.openhab.binding.bluetooth.bluegiga", 2000);
321 sp.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
323 sp.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_OUT);
324 sp.enableReceiveThreshold(1);
325 sp.enableReceiveTimeout(2000);
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);
331 logger.info("Connected to serial port '{}'.", serialPortName);
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);
340 serialPort = Optional.of(sp);
342 } catch (PortInUseException e) {
343 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
344 "Serial Error: Port in use");
346 } catch (UnsupportedCommOperationException e) {
347 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
348 "Serial Error: Unsupported operation");
353 private void closeSerialPort() {
354 serialPort.ifPresent(sp -> {
355 sp.removeEventListener();
357 sp.disableReceiveTimeout();
358 } catch (Exception e) {
359 // Ignore all as RXTX seems to send arbitrary exceptions when BlueGiga module is detached
361 outputStream.ifPresent(output -> {
362 IOUtils.closeQuietly(output);
364 inputStream.ifPresent(input -> {
365 IOUtils.closeQuietly(input);
368 logger.debug("Closed serial port.");
369 serialPort = Optional.empty();
370 inputStream = Optional.empty();
371 outputStream = Optional.empty();
377 public void scanStart() {
379 logger.debug("Start active scan");
380 // Stop the passive scan
381 cancelScheduledPassiveScan();
384 // Start a active scan
385 bgStartScanning(true, configuration.activeScanInterval, configuration.activeScanWindow);
389 public void scanStop() {
391 logger.debug("Stop active scan");
393 // Stop the active scan
396 // Start a passive scan after idle delay
397 schedulePassiveScan();
401 public @Nullable BluetoothAddress getAddress() {
402 BluetoothAddress addr = address;
406 throw new IllegalStateException("Adapter has not been initialized yet!");
411 protected BlueGigaBluetoothDevice createDevice(BluetoothAddress address) {
412 return new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
416 * Connects to a device.
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.
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
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)) {
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);
439 logger.debug("BlueGiga Connect: address {}.", address);
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)
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,
461 * Close a connection using {@link BlueGigaDisconnectCommand}
463 * @param connectionHandle
466 public boolean bgDisconnect(int connectionHandle) {
467 logger.debug("BlueGiga Disconnect: connection {}", connectionHandle);
468 BlueGigaDisconnectCommand command = new BlueGigaDisconnectCommand.CommandBuilder()
469 .withConnection(connectionHandle).build();
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,
481 * Start a read of all primary services using {@link BlueGigaReadByGroupTypeCommand}
483 * @param connectionHandle
484 * @return true if successful
486 public boolean bgFindPrimaryServices(int connectionHandle) {
487 logger.debug("BlueGiga FindPrimary: connection {}", connectionHandle);
489 BlueGigaReadByGroupTypeCommand command = new BlueGigaReadByGroupTypeCommand.CommandBuilder()
490 .withConnection(connectionHandle)
493 .withUuid(UUID.fromString("00002800-0000-1000-8000-00805F9B34FB"))
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,
507 * Start a read of all characteristics using {@link BlueGigaFindInformationCommand}
509 * @param connectionHandle
510 * @return true if successful
512 public boolean bgFindCharacteristics(int connectionHandle) {
513 logger.debug("BlueGiga Find: connection {}", connectionHandle);
515 BlueGigaFindInformationCommand command = new BlueGigaFindInformationCommand.CommandBuilder()
516 .withConnection(connectionHandle)
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,
532 * Read a characteristic using {@link BlueGigaReadByHandleCommand}
534 * @param connectionHandle
536 * @return true if successful
538 public boolean bgReadCharacteristic(int connectionHandle, int handle) {
539 logger.debug("BlueGiga Read: connection {}, handle {}", connectionHandle, handle);
541 BlueGigaReadByHandleCommand command = new BlueGigaReadByHandleCommand.CommandBuilder()
542 .withConnection(connectionHandle)
543 .withChrHandle(handle)
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,
556 * Write a characteristic using {@link BlueGigaAttributeWriteCommand}
558 * @param connectionHandle
561 * @return true if successful
563 public boolean bgWriteCharacteristic(int connectionHandle, int handle, int[] value) {
564 logger.debug("BlueGiga Write: connection {}, handle {}", connectionHandle, handle);
566 BlueGigaAttributeWriteCommand command = new BlueGigaAttributeWriteCommand.CommandBuilder()
567 .withConnection(connectionHandle)
568 .withAttHandle(handle)
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,
583 * The following methods are private methods for handling the BlueGiga protocol
585 private boolean bgEndProcedure() {
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.");
595 private boolean bgSetMode() {
598 BlueGigaSetModeCommand command = new BlueGigaSetModeCommand.CommandBuilder()
599 .withConnect(GapConnectableMode.GAP_NON_CONNECTABLE)
600 .withDiscover(GapDiscoverableMode.GAP_NON_DISCOVERABLE)
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());
612 * Starts scanning on the dongle
614 * @param active true for active scanning
616 private boolean bgStartScanning(boolean active, int interval, int window) {
619 BlueGigaSetScanParametersCommand scanCommand = new BlueGigaSetScanParametersCommand.CommandBuilder()
620 .withActiveScanning(active)
621 .withScanInterval(interval)
622 .withScanWindow(window)
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");
635 } catch (BlueGigaException e) {
636 logger.debug("Error occured when sending start scan command, reason: {}", e.getMessage());
638 logger.debug("Scan start failed.");
643 * Send command only if initialization phase is successfully done
645 private <T extends BlueGigaResponse> T sendCommand(BlueGigaCommand command, Class<T> expectedResponse,
646 boolean schedulePassiveScan) throws BlueGigaException {
648 throw new BlueGigaException("BlueGiga not initialized");
651 if (schedulePassiveScan) {
652 cancelScheduledPassiveScan();
655 return sendCommandWithoutChecks(command, expectedResponse);
657 if (schedulePassiveScan) {
658 schedulePassiveScan();
664 * Forcefully send command without any checks
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);
671 throw new BlueGigaException("Transaction manager missing");
676 * Add an event listener for the BlueGiga events
678 * @param listener the {@link BlueGigaEventListener} to add
680 public void addEventListener(BlueGigaEventListener listener) {
681 transactionManager.ifPresent(manager -> {
682 manager.addEventListener(listener);
687 * Remove an event listener for the BlueGiga events
689 * @param listener the {@link BlueGigaEventListener} to remove
691 public void removeEventListener(BlueGigaEventListener listener) {
692 transactionManager.ifPresent(manager -> {
693 manager.removeEventListener(listener);
698 public void bluegigaEventReceived(@Nullable BlueGigaResponse event) {
699 if (event instanceof BlueGigaScanResponseEvent) {
701 BlueGigaScanResponseEvent scanEvent = (BlueGigaScanResponseEvent) event;
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);
710 logger.trace("Ignore BlueGigaScanResponseEvent as initialization is not complete");
715 if (event instanceof BlueGigaConnectionStatusEvent) {
716 BlueGigaConnectionStatusEvent connectionEvent = (BlueGigaConnectionStatusEvent) event;
717 connections.put(connectionEvent.getConnection(), new BluetoothAddress(connectionEvent.getAddress()));
720 if (event instanceof BlueGigaDisconnectedEvent) {
721 BlueGigaDisconnectedEvent disconnectedEvent = (BlueGigaDisconnectedEvent) event;
722 connections.remove(disconnectedEvent.getConnection());
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;