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;
15 import java.nio.ByteBuffer;
16 import java.nio.ByteOrder;
17 import java.util.HashMap;
19 import java.util.NavigableMap;
20 import java.util.TreeMap;
21 import java.util.UUID;
22 import java.util.concurrent.ScheduledExecutorService;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.bluetooth.BaseBluetoothDevice;
29 import org.openhab.binding.bluetooth.BluetoothAddress;
30 import org.openhab.binding.bluetooth.BluetoothBindingConstants;
31 import org.openhab.binding.bluetooth.BluetoothCharacteristic;
32 import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
33 import org.openhab.binding.bluetooth.BluetoothDescriptor;
34 import org.openhab.binding.bluetooth.BluetoothDevice;
35 import org.openhab.binding.bluetooth.BluetoothService;
36 import org.openhab.binding.bluetooth.bluegiga.handler.BlueGigaBridgeHandler;
37 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaEventListener;
38 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaResponse;
39 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaAttributeValueEvent;
40 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaFindInformationFoundEvent;
41 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaGroupFoundEvent;
42 import org.openhab.binding.bluetooth.bluegiga.internal.command.attributeclient.BlueGigaProcedureCompletedEvent;
43 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaConnectionStatusEvent;
44 import org.openhab.binding.bluetooth.bluegiga.internal.command.connection.BlueGigaDisconnectedEvent;
45 import org.openhab.binding.bluetooth.bluegiga.internal.command.gap.BlueGigaScanResponseEvent;
46 import org.openhab.binding.bluetooth.bluegiga.internal.eir.EirDataType;
47 import org.openhab.binding.bluetooth.bluegiga.internal.eir.EirPacket;
48 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BgApiResponse;
49 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.BluetoothAddressType;
50 import org.openhab.binding.bluetooth.bluegiga.internal.enumeration.ConnectionStatusFlag;
51 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
52 import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
53 import org.openhab.binding.bluetooth.notification.BluetoothScanNotification.BluetoothBeaconType;
54 import org.openhab.core.common.ThreadPoolManager;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * An extended {@link BluetoothDevice} class to handle BlueGiga specific information
61 * @author Chris Jackson - Initial contribution
64 public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements BlueGigaEventListener {
65 private final long TIMEOUT_SEC = 60;
67 private final Logger logger = LoggerFactory.getLogger(BlueGigaBluetoothDevice.class);
69 private Map<Integer, UUID> handleToUUID = new HashMap<>();
70 private NavigableMap<Integer, BlueGigaBluetoothCharacteristic> handleToCharacteristic = new TreeMap<>();
72 // BlueGiga needs to know the address type when connecting
73 private BluetoothAddressType addressType = BluetoothAddressType.UNKNOWN;
76 private final BlueGigaBridgeHandler bgHandler;
78 // An enum to use in the state machine for interacting with the device
79 private enum BlueGigaProcedure {
83 READ_CHARACTERISTIC_DECL,
90 private BlueGigaProcedure procedureProgress = BlueGigaProcedure.NONE;
92 // Somewhere to remember what characteristic we're working on
93 private @Nullable BluetoothCharacteristic procedureCharacteristic;
95 // The connection handle if the device is connected
96 private int connection = -1;
98 private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
100 private @Nullable ScheduledFuture<?> connectTimer;
101 private @Nullable ScheduledFuture<?> procedureTimer;
103 private Runnable connectTimeoutTask = new Runnable() {
106 if (connectionState == ConnectionState.CONNECTING) {
107 logger.debug("Connection timeout for device {}", address);
108 connectionState = ConnectionState.DISCONNECTED;
113 private Runnable procedureTimeoutTask = new Runnable() {
116 logger.debug("Procedure {} timeout for device {}", procedureProgress, address);
117 procedureProgress = BlueGigaProcedure.NONE;
118 procedureCharacteristic = null;
123 * Creates a new {@link BlueGigaBluetoothDevice} which extends {@link BluetoothDevice} for the BlueGiga
126 * @param bgHandler the {@link BlueGigaBridgeHandler} that provides the link to the dongle
127 * @param address the {@link BluetoothAddress} for this device
128 * @param addressType the {@link BluetoothAddressType} of this device
130 public BlueGigaBluetoothDevice(BlueGigaBridgeHandler bgHandler, BluetoothAddress address,
131 BluetoothAddressType addressType) {
132 super(bgHandler, address);
134 logger.debug("Creating new BlueGiga device {}", address);
136 this.bgHandler = bgHandler;
137 this.addressType = addressType;
139 bgHandler.addEventListener(this);
140 updateLastSeenTime();
143 public void setAddressType(BluetoothAddressType addressType) {
144 this.addressType = addressType;
148 public boolean connect() {
149 if (connection != -1) {
150 // We're already connected
154 cancelTimer(connectTimer);
155 if (bgHandler.bgConnect(address, addressType)) {
156 connectionState = ConnectionState.CONNECTING;
157 connectTimer = startTimer(connectTimeoutTask, 10);
160 connectionState = ConnectionState.DISCONNECTED;
166 public boolean disconnect() {
167 if (connection == -1) {
168 // We're already disconnected
172 return bgHandler.bgDisconnect(connection);
176 public boolean discoverServices() {
177 if (procedureProgress != BlueGigaProcedure.NONE) {
181 cancelTimer(procedureTimer);
182 if (!bgHandler.bgFindPrimaryServices(connection)) {
186 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
187 procedureProgress = BlueGigaProcedure.GET_SERVICES;
192 public boolean enableNotifications(BluetoothCharacteristic characteristic) {
193 if (connection == -1) {
194 logger.debug("Cannot enable notifications, device not connected {}", this);
198 BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
199 if (ch.isNotificationEnabled()) {
203 BluetoothDescriptor descriptor = ch
204 .getDescriptor(BluetoothDescriptor.GattDescriptor.CLIENT_CHARACTERISTIC_CONFIGURATION.getUUID());
206 if (descriptor == null || descriptor.getHandle() == 0) {
207 logger.debug("unable to find CCC for characteristic {}", characteristic.getUuid());
211 if (procedureProgress != BlueGigaProcedure.NONE) {
212 logger.debug("Procedure already in progress {}", procedureProgress);
216 int[] value = { 1, 0 };
217 byte[] bvalue = toBytes(value);
218 descriptor.setValue(bvalue);
220 cancelTimer(procedureTimer);
221 if (!bgHandler.bgWriteCharacteristic(connection, descriptor.getHandle(), value)) {
222 logger.debug("bgWriteCharacteristic returned false");
226 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
227 procedureProgress = BlueGigaProcedure.NOTIFICATION_ENABLE;
228 procedureCharacteristic = characteristic;
231 // we intentionally sleep here in order to give this procedure a chance to complete.
232 // ideally we would use locks/conditions to make this wait until completiong but
233 // I have a better solution planned for later. - Connor Petty
235 } catch (InterruptedException e) {
236 Thread.currentThread().interrupt();
242 public boolean disableNotifications(BluetoothCharacteristic characteristic) {
243 if (connection == -1) {
244 logger.debug("Cannot enable notifications, device not connected {}", this);
248 BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
249 if (ch.isNotificationEnabled()) {
253 BluetoothDescriptor descriptor = ch
254 .getDescriptor(BluetoothDescriptor.GattDescriptor.CLIENT_CHARACTERISTIC_CONFIGURATION.getUUID());
256 if (descriptor == null || descriptor.getHandle() == 0) {
257 logger.debug("unable to find CCC for characteristic {}", characteristic.getUuid());
261 if (procedureProgress != BlueGigaProcedure.NONE) {
262 logger.debug("Procedure already in progress {}", procedureProgress);
266 int[] value = { 0, 0 };
267 byte[] bvalue = toBytes(value);
268 descriptor.setValue(bvalue);
270 cancelTimer(procedureTimer);
271 if (!bgHandler.bgWriteCharacteristic(connection, descriptor.getHandle(), value)) {
272 logger.debug("bgWriteCharacteristic returned false");
276 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
277 procedureProgress = BlueGigaProcedure.NOTIFICATION_DISABLE;
278 procedureCharacteristic = characteristic;
281 // we intentionally sleep here in order to give this procedure a chance to complete.
282 // ideally we would use locks/conditions to make this wait until completiong but
283 // I have a better solution planned for later. - Connor Petty
285 } catch (InterruptedException e) {
286 Thread.currentThread().interrupt();
292 public boolean enableNotifications(BluetoothDescriptor descriptor) {
293 // TODO will be implemented in a followup PR
298 public boolean disableNotifications(BluetoothDescriptor descriptor) {
299 // TODO will be implemented in a followup PR
304 public boolean readCharacteristic(@Nullable BluetoothCharacteristic characteristic) {
305 if (characteristic == null || characteristic.getHandle() == 0) {
308 if (connection == -1) {
312 if (procedureProgress != BlueGigaProcedure.NONE) {
316 cancelTimer(procedureTimer);
317 if (!bgHandler.bgReadCharacteristic(connection, characteristic.getHandle())) {
320 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
321 procedureProgress = BlueGigaProcedure.CHARACTERISTIC_READ;
322 procedureCharacteristic = characteristic;
328 public boolean writeCharacteristic(@Nullable BluetoothCharacteristic characteristic) {
329 if (characteristic == null || characteristic.getHandle() == 0) {
332 if (connection == -1) {
336 if (procedureProgress != BlueGigaProcedure.NONE) {
340 cancelTimer(procedureTimer);
341 if (!bgHandler.bgWriteCharacteristic(connection, characteristic.getHandle(), characteristic.getValue())) {
345 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
346 procedureProgress = BlueGigaProcedure.CHARACTERISTIC_WRITE;
347 procedureCharacteristic = characteristic;
353 public void bluegigaEventReceived(BlueGigaResponse event) {
354 if (event instanceof BlueGigaScanResponseEvent) {
355 handleScanEvent((BlueGigaScanResponseEvent) event);
358 else if (event instanceof BlueGigaGroupFoundEvent) {
359 handleGroupFoundEvent((BlueGigaGroupFoundEvent) event);
362 else if (event instanceof BlueGigaFindInformationFoundEvent) {
363 // A Characteristic has been discovered
364 handleFindInformationFoundEvent((BlueGigaFindInformationFoundEvent) event);
367 else if (event instanceof BlueGigaProcedureCompletedEvent) {
368 handleProcedureCompletedEvent((BlueGigaProcedureCompletedEvent) event);
371 else if (event instanceof BlueGigaConnectionStatusEvent) {
372 handleConnectionStatusEvent((BlueGigaConnectionStatusEvent) event);
375 else if (event instanceof BlueGigaDisconnectedEvent) {
376 handleDisconnectedEvent((BlueGigaDisconnectedEvent) event);
379 else if (event instanceof BlueGigaAttributeValueEvent) {
380 handleAttributeValueEvent((BlueGigaAttributeValueEvent) event);
384 private void handleScanEvent(BlueGigaScanResponseEvent event) {
385 // Check if this is addressed to this device
386 if (!address.equals(new BluetoothAddress(event.getSender()))) {
390 logger.trace("scanEvent: {}", event);
391 updateLastSeenTime();
393 // Set device properties
394 rssi = event.getRssi();
395 addressType = event.getAddressType();
397 byte[] manufacturerData = null;
399 // If the packet contains data, then process it and add anything relevant to the device...
400 if (event.getData().length > 0) {
401 EirPacket eir = new EirPacket(event.getData());
402 for (EirDataType record : eir.getRecords().keySet()) {
403 if (logger.isTraceEnabled()) {
404 logger.trace(" EirDataType: {}={}", record, eir.getRecord(record));
410 case EIR_MANUFACTURER_SPECIFIC:
411 obj = eir.getRecord(EirDataType.EIR_MANUFACTURER_SPECIFIC);
414 @SuppressWarnings("unchecked")
415 Map<Short, int[]> eirRecord = (Map<Short, int[]>) obj;
416 Map.Entry<Short, int[]> eirEntry = eirRecord.entrySet().iterator().next();
418 manufacturer = eirEntry.getKey().intValue();
420 int[] manufacturerInt = eirEntry.getValue();
421 manufacturerData = new byte[manufacturerInt.length + 2];
422 // Convert short Company ID to bytes and add it to manufacturerData
423 manufacturerData[0] = (byte) (manufacturer & 0xff);
424 manufacturerData[1] = (byte) ((manufacturer >> 8) & 0xff);
425 // Add Convert int custom data nd add it to manufacturerData
426 for (int i = 0; i < manufacturerInt.length; i++) {
427 manufacturerData[i + 2] = (byte) manufacturerInt[i];
429 } catch (ClassCastException e) {
430 logger.debug("Unsupported manufacturer specific record received from device {}",
437 name = (String) eir.getRecord(record);
439 case EIR_SLAVEINTERVALRANGE:
441 case EIR_SVC_DATA_UUID128:
443 case EIR_SVC_DATA_UUID16:
445 case EIR_SVC_DATA_UUID32:
447 case EIR_SVC_UUID128_INCOMPLETE:
448 case EIR_SVC_UUID16_COMPLETE:
449 case EIR_SVC_UUID16_INCOMPLETE:
450 case EIR_SVC_UUID32_COMPLETE:
451 case EIR_SVC_UUID32_INCOMPLETE:
452 case EIR_SVC_UUID128_COMPLETE:
453 // addServices((List<UUID>) eir.getRecord(record));
456 obj = eir.getRecord(EirDataType.EIR_TXPOWER);
467 if (connectionState == ConnectionState.DISCOVERING) {
468 // TODO: It could make sense to wait with discovery for non-connectable devices until scan response is
469 // received to eventually retrieve more about the device before it gets discovered. Anyhow, devices
470 // that don't send a scan response at all also have to be supported. See also PR #6995.
472 // Set our state to disconnected
473 connectionState = ConnectionState.DISCONNECTED;
476 // But notify listeners that the state is now DISCOVERED
477 notifyListeners(BluetoothEventType.CONNECTION_STATE,
478 new BluetoothConnectionStatusNotification(ConnectionState.DISCOVERED));
480 // Notify the bridge - for inbox notifications
481 bgHandler.deviceDiscovered(this);
484 // Notify listeners of all scan records - for RSSI, beacon processing (etc)
485 BluetoothScanNotification scanNotification = new BluetoothScanNotification();
486 scanNotification.setRssi(event.getRssi());
488 switch (event.getPacketType()) {
489 case CONNECTABLE_ADVERTISEMENT:
490 case DISCOVERABLE_ADVERTISEMENT:
491 case NON_CONNECTABLE_ADVERTISEMENT:
492 scanNotification.setBeaconType(BluetoothBeaconType.BEACON_ADVERTISEMENT);
495 scanNotification.setBeaconType(BluetoothBeaconType.BEACON_SCANRESPONSE);
501 if (manufacturerData != null) {
502 scanNotification.setManufacturerData(manufacturerData);
505 notifyListeners(BluetoothEventType.SCAN_RECORD, scanNotification);
508 private void handleGroupFoundEvent(BlueGigaGroupFoundEvent event) {
509 // If this is not our connection handle then ignore.
510 if (connection != event.getConnection()) {
514 logger.trace("BlueGiga Group: {} event={}", this, event);
515 updateLastSeenTime();
517 BluetoothService service = new BluetoothService(event.getUuid(), true, event.getStart(), event.getEnd());
521 private void handleFindInformationFoundEvent(BlueGigaFindInformationFoundEvent event) {
522 // If this is not our connection handle then ignore.
523 if (connection != event.getConnection()) {
527 logger.trace("BlueGiga FindInfo: {} event={}", this, event);
528 updateLastSeenTime();
530 int handle = event.getChrHandle();
531 UUID attUUID = event.getUuid();
533 BluetoothService service = getServiceByHandle(handle);
534 if (service == null) {
535 logger.debug("BlueGiga: Unable to find service for handle {}", handle);
538 handleToUUID.put(handle, attUUID);
540 if (BluetoothBindingConstants.ATTR_CHARACTERISTIC_DECLARATION.equals(attUUID)) {
541 BlueGigaBluetoothCharacteristic characteristic = new BlueGigaBluetoothCharacteristic(handle);
542 characteristic.setService(service);
543 handleToCharacteristic.put(handle, characteristic);
545 Integer chrHandle = handleToCharacteristic.floorKey(handle);
546 if (chrHandle == null) {
547 logger.debug("BlueGiga: Unable to find characteristic for handle {}", handle);
550 BlueGigaBluetoothCharacteristic characteristic = handleToCharacteristic.get(chrHandle);
551 characteristic.addDescriptor(new BluetoothDescriptor(characteristic, attUUID, handle));
555 private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event) {
556 // If this is not our connection handle then ignore.
557 if (connection != event.getConnection()) {
561 if (procedureProgress == BlueGigaProcedure.NONE) {
562 logger.debug("BlueGiga procedure completed but procedure is null with connection {}, address {}",
563 connection, address);
567 cancelTimer(procedureTimer);
568 updateLastSeenTime();
570 // The current procedure is now complete - move on...
571 switch (procedureProgress) {
573 // We've downloaded all services, now get the characteristics
574 if (bgHandler.bgFindCharacteristics(connection)) {
575 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
576 procedureProgress = BlueGigaProcedure.GET_CHARACTERISTICS;
578 procedureProgress = BlueGigaProcedure.NONE;
581 case GET_CHARACTERISTICS:
582 // We've downloaded all attributes, now read the characteristic declarations
583 if (bgHandler.bgReadCharacteristicDeclarations(connection)) {
584 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
585 procedureProgress = BlueGigaProcedure.READ_CHARACTERISTIC_DECL;
587 procedureProgress = BlueGigaProcedure.NONE;
590 case READ_CHARACTERISTIC_DECL:
591 // We've downloaded read all the declarations, we are done now
592 procedureProgress = BlueGigaProcedure.NONE;
593 notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
595 case CHARACTERISTIC_READ:
597 notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, procedureCharacteristic,
598 BluetoothCompletionStatus.ERROR);
599 procedureProgress = BlueGigaProcedure.NONE;
600 procedureCharacteristic = null;
602 case CHARACTERISTIC_WRITE:
603 // The write completed - failure or success
604 BluetoothCompletionStatus result = event.getResult() == BgApiResponse.SUCCESS
605 ? BluetoothCompletionStatus.SUCCESS
606 : BluetoothCompletionStatus.ERROR;
607 notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, procedureCharacteristic, result);
608 procedureProgress = BlueGigaProcedure.NONE;
609 procedureCharacteristic = null;
611 case NOTIFICATION_ENABLE:
612 boolean success = event.getResult() == BgApiResponse.SUCCESS;
614 logger.debug("write to descriptor failed");
616 ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(success);
617 procedureProgress = BlueGigaProcedure.NONE;
618 procedureCharacteristic = null;
620 case NOTIFICATION_DISABLE:
621 success = event.getResult() == BgApiResponse.SUCCESS;
623 logger.debug("write to descriptor failed");
625 ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(!success);
626 procedureProgress = BlueGigaProcedure.NONE;
627 procedureCharacteristic = null;
634 private void handleConnectionStatusEvent(BlueGigaConnectionStatusEvent event) {
635 // Check if this is addressed to this device
636 if (!address.equals(new BluetoothAddress(event.getAddress()))) {
640 cancelTimer(connectTimer);
641 updateLastSeenTime();
643 // If we're connected, then remember the connection handle
644 if (event.getFlags().contains(ConnectionStatusFlag.CONNECTION_CONNECTED)) {
645 connectionState = ConnectionState.CONNECTED;
646 connection = event.getConnection();
647 notifyListeners(BluetoothEventType.CONNECTION_STATE,
648 new BluetoothConnectionStatusNotification(connectionState));
652 private void handleDisconnectedEvent(BlueGigaDisconnectedEvent event) {
653 // If this is not our connection handle then ignore.
654 if (connection != event.getConnection()) {
658 for (BlueGigaBluetoothCharacteristic ch : handleToCharacteristic.values()) {
659 ch.setNotificationEnabled(false);
662 cancelTimer(procedureTimer);
663 connectionState = ConnectionState.DISCONNECTED;
665 procedureProgress = BlueGigaProcedure.NONE;
667 notifyListeners(BluetoothEventType.CONNECTION_STATE,
668 new BluetoothConnectionStatusNotification(connectionState));
671 private void handleAttributeValueEvent(BlueGigaAttributeValueEvent event) {
672 // If this is not our connection handle then ignore.
673 if (connection != event.getConnection()) {
677 updateLastSeenTime();
679 logger.trace("BlueGiga AttributeValue: {} event={}", this, event);
681 int handle = event.getAttHandle();
683 Map.Entry<Integer, BlueGigaBluetoothCharacteristic> entry = handleToCharacteristic.floorEntry(handle);
685 logger.debug("BlueGiga didn't find characteristic for event {}", event);
689 BlueGigaBluetoothCharacteristic characteristic = entry.getValue();
691 if (handle == entry.getKey()) {
692 // this is the declaration
693 if (parseDeclaration(characteristic, event.getValue())) {
694 BluetoothService service = getServiceByHandle(handle);
695 if (service == null) {
696 logger.debug("BlueGiga: Unable to find service for handle {}", handle);
699 service.addCharacteristic(characteristic);
703 if (handle == characteristic.getHandle()) {
704 characteristic.setValue(event.getValue().clone());
706 // If this is the characteristic we were reading, then send a read completion
707 if (procedureProgress == BlueGigaProcedure.CHARACTERISTIC_READ && procedureCharacteristic != null
708 && procedureCharacteristic.getHandle() == event.getAttHandle()) {
709 procedureProgress = BlueGigaProcedure.NONE;
710 procedureCharacteristic = null;
711 notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
712 BluetoothCompletionStatus.SUCCESS);
716 // Notify the user of the updated value
717 notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
719 // it must be one of the descriptors we need to update
720 UUID attUUID = handleToUUID.get(handle);
721 BluetoothDescriptor descriptor = characteristic.getDescriptor(attUUID);
722 descriptor.setValue(toBytes(event.getValue()));
723 notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, descriptor);
727 private static byte @Nullable [] toBytes(int @Nullable [] value) {
731 byte[] ret = new byte[value.length];
732 for (int i = 0; i < value.length; i++) {
733 ret[i] = (byte) value[i];
738 private boolean parseDeclaration(BlueGigaBluetoothCharacteristic ch, int[] value) {
739 ByteBuffer buffer = ByteBuffer.wrap(toBytes(value));
740 buffer.order(ByteOrder.LITTLE_ENDIAN);
742 ch.setProperties(Byte.toUnsignedInt(buffer.get()));
743 ch.setHandle(Short.toUnsignedInt(buffer.getShort()));
745 switch (buffer.remaining()) {
747 long key = Short.toUnsignedLong(buffer.getShort());
748 ch.setUUID(BluetoothBindingConstants.createBluetoothUUID(key));
751 key = Integer.toUnsignedLong(buffer.getInt());
752 ch.setUUID(BluetoothBindingConstants.createBluetoothUUID(key));
755 long lower = buffer.getLong();
756 long upper = buffer.getLong();
757 ch.setUUID(new UUID(upper, lower));
760 logger.debug("Unexpected uuid length: {}", buffer.remaining());
766 * Clean up and release memory.
769 public void dispose() {
770 if (connectionState == ConnectionState.CONNECTED) {
773 cancelTimer(connectTimer);
774 cancelTimer(procedureTimer);
775 bgHandler.removeEventListener(this);
776 procedureProgress = BlueGigaProcedure.NONE;
777 connectionState = ConnectionState.DISCOVERING;
781 private void cancelTimer(@Nullable ScheduledFuture<?> task) {
787 private ScheduledFuture<?> startTimer(Runnable command, long timeout) {
788 return scheduler.schedule(command, timeout, TimeUnit.SECONDS);