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.isNotifying()) {
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 disable notifications, device not connected {}", this);
248 BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
249 if (!ch.isNotifying()) {
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 isNotifying(BluetoothCharacteristic characteristic) {
293 BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
294 return ch.isNotifying();
298 public boolean enableNotifications(BluetoothDescriptor descriptor) {
299 // TODO will be implemented in a followup PR
304 public boolean disableNotifications(BluetoothDescriptor descriptor) {
305 // TODO will be implemented in a followup PR
310 public boolean readCharacteristic(@Nullable BluetoothCharacteristic characteristic) {
311 if (characteristic == null || characteristic.getHandle() == 0) {
314 if (connection == -1) {
318 if (procedureProgress != BlueGigaProcedure.NONE) {
322 cancelTimer(procedureTimer);
323 if (!bgHandler.bgReadCharacteristic(connection, characteristic.getHandle())) {
326 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
327 procedureProgress = BlueGigaProcedure.CHARACTERISTIC_READ;
328 procedureCharacteristic = characteristic;
334 public boolean writeCharacteristic(@Nullable BluetoothCharacteristic characteristic) {
335 if (characteristic == null || characteristic.getHandle() == 0) {
338 if (connection == -1) {
342 if (procedureProgress != BlueGigaProcedure.NONE) {
346 cancelTimer(procedureTimer);
347 if (!bgHandler.bgWriteCharacteristic(connection, characteristic.getHandle(), characteristic.getValue())) {
351 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
352 procedureProgress = BlueGigaProcedure.CHARACTERISTIC_WRITE;
353 procedureCharacteristic = characteristic;
359 public void bluegigaEventReceived(BlueGigaResponse event) {
360 if (event instanceof BlueGigaScanResponseEvent) {
361 handleScanEvent((BlueGigaScanResponseEvent) event);
364 else if (event instanceof BlueGigaGroupFoundEvent) {
365 handleGroupFoundEvent((BlueGigaGroupFoundEvent) event);
368 else if (event instanceof BlueGigaFindInformationFoundEvent) {
369 // A Characteristic has been discovered
370 handleFindInformationFoundEvent((BlueGigaFindInformationFoundEvent) event);
373 else if (event instanceof BlueGigaProcedureCompletedEvent) {
374 handleProcedureCompletedEvent((BlueGigaProcedureCompletedEvent) event);
377 else if (event instanceof BlueGigaConnectionStatusEvent) {
378 handleConnectionStatusEvent((BlueGigaConnectionStatusEvent) event);
381 else if (event instanceof BlueGigaDisconnectedEvent) {
382 handleDisconnectedEvent((BlueGigaDisconnectedEvent) event);
385 else if (event instanceof BlueGigaAttributeValueEvent) {
386 handleAttributeValueEvent((BlueGigaAttributeValueEvent) event);
390 private void handleScanEvent(BlueGigaScanResponseEvent event) {
391 // Check if this is addressed to this device
392 if (!address.equals(new BluetoothAddress(event.getSender()))) {
396 logger.trace("scanEvent: {}", event);
397 updateLastSeenTime();
399 // Set device properties
400 rssi = event.getRssi();
401 addressType = event.getAddressType();
403 byte[] manufacturerData = null;
405 // If the packet contains data, then process it and add anything relevant to the device...
406 if (event.getData().length > 0) {
407 EirPacket eir = new EirPacket(event.getData());
408 for (EirDataType record : eir.getRecords().keySet()) {
409 if (logger.isTraceEnabled()) {
410 logger.trace(" EirDataType: {}={}", record, eir.getRecord(record));
416 case EIR_MANUFACTURER_SPECIFIC:
417 obj = eir.getRecord(EirDataType.EIR_MANUFACTURER_SPECIFIC);
420 @SuppressWarnings("unchecked")
421 Map<Short, int[]> eirRecord = (Map<Short, int[]>) obj;
422 Map.Entry<Short, int[]> eirEntry = eirRecord.entrySet().iterator().next();
424 manufacturer = eirEntry.getKey().intValue();
426 int[] manufacturerInt = eirEntry.getValue();
427 manufacturerData = new byte[manufacturerInt.length + 2];
428 // Convert short Company ID to bytes and add it to manufacturerData
429 manufacturerData[0] = (byte) (manufacturer & 0xff);
430 manufacturerData[1] = (byte) ((manufacturer >> 8) & 0xff);
431 // Add Convert int custom data nd add it to manufacturerData
432 for (int i = 0; i < manufacturerInt.length; i++) {
433 manufacturerData[i + 2] = (byte) manufacturerInt[i];
435 } catch (ClassCastException e) {
436 logger.debug("Unsupported manufacturer specific record received from device {}",
443 name = (String) eir.getRecord(record);
445 case EIR_SLAVEINTERVALRANGE:
447 case EIR_SVC_DATA_UUID128:
449 case EIR_SVC_DATA_UUID16:
451 case EIR_SVC_DATA_UUID32:
453 case EIR_SVC_UUID128_INCOMPLETE:
454 case EIR_SVC_UUID16_COMPLETE:
455 case EIR_SVC_UUID16_INCOMPLETE:
456 case EIR_SVC_UUID32_COMPLETE:
457 case EIR_SVC_UUID32_INCOMPLETE:
458 case EIR_SVC_UUID128_COMPLETE:
459 // addServices((List<UUID>) eir.getRecord(record));
462 obj = eir.getRecord(EirDataType.EIR_TXPOWER);
473 if (connectionState == ConnectionState.DISCOVERING) {
474 // TODO: It could make sense to wait with discovery for non-connectable devices until scan response is
475 // received to eventually retrieve more about the device before it gets discovered. Anyhow, devices
476 // that don't send a scan response at all also have to be supported. See also PR #6995.
478 // Set our state to disconnected
479 connectionState = ConnectionState.DISCONNECTED;
482 // But notify listeners that the state is now DISCOVERED
483 notifyListeners(BluetoothEventType.CONNECTION_STATE,
484 new BluetoothConnectionStatusNotification(ConnectionState.DISCOVERED));
486 // Notify the bridge - for inbox notifications
487 bgHandler.deviceDiscovered(this);
490 // Notify listeners of all scan records - for RSSI, beacon processing (etc)
491 BluetoothScanNotification scanNotification = new BluetoothScanNotification();
492 scanNotification.setRssi(event.getRssi());
494 switch (event.getPacketType()) {
495 case CONNECTABLE_ADVERTISEMENT:
496 case DISCOVERABLE_ADVERTISEMENT:
497 case NON_CONNECTABLE_ADVERTISEMENT:
498 scanNotification.setBeaconType(BluetoothBeaconType.BEACON_ADVERTISEMENT);
501 scanNotification.setBeaconType(BluetoothBeaconType.BEACON_SCANRESPONSE);
507 if (manufacturerData != null) {
508 scanNotification.setManufacturerData(manufacturerData);
511 notifyListeners(BluetoothEventType.SCAN_RECORD, scanNotification);
514 private void handleGroupFoundEvent(BlueGigaGroupFoundEvent event) {
515 // If this is not our connection handle then ignore.
516 if (connection != event.getConnection()) {
520 logger.trace("BlueGiga Group: {} event={}", this, event);
521 updateLastSeenTime();
523 BluetoothService service = new BluetoothService(event.getUuid(), true, event.getStart(), event.getEnd());
527 private void handleFindInformationFoundEvent(BlueGigaFindInformationFoundEvent event) {
528 // If this is not our connection handle then ignore.
529 if (connection != event.getConnection()) {
533 logger.trace("BlueGiga FindInfo: {} event={}", this, event);
534 updateLastSeenTime();
536 int handle = event.getChrHandle();
537 UUID attUUID = event.getUuid();
539 BluetoothService service = getServiceByHandle(handle);
540 if (service == null) {
541 logger.debug("BlueGiga: Unable to find service for handle {}", handle);
544 handleToUUID.put(handle, attUUID);
546 if (BluetoothBindingConstants.ATTR_CHARACTERISTIC_DECLARATION.equals(attUUID)) {
547 BlueGigaBluetoothCharacteristic characteristic = new BlueGigaBluetoothCharacteristic(handle);
548 characteristic.setService(service);
549 handleToCharacteristic.put(handle, characteristic);
551 Integer chrHandle = handleToCharacteristic.floorKey(handle);
552 if (chrHandle == null) {
553 logger.debug("BlueGiga: Unable to find characteristic for handle {}", handle);
556 BlueGigaBluetoothCharacteristic characteristic = handleToCharacteristic.get(chrHandle);
557 characteristic.addDescriptor(new BluetoothDescriptor(characteristic, attUUID, handle));
561 private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event) {
562 // If this is not our connection handle then ignore.
563 if (connection != event.getConnection()) {
567 if (procedureProgress == BlueGigaProcedure.NONE) {
568 logger.debug("BlueGiga procedure completed but procedure is null with connection {}, address {}",
569 connection, address);
573 cancelTimer(procedureTimer);
574 updateLastSeenTime();
576 // The current procedure is now complete - move on...
577 switch (procedureProgress) {
579 // We've downloaded all services, now get the characteristics
580 if (bgHandler.bgFindCharacteristics(connection)) {
581 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
582 procedureProgress = BlueGigaProcedure.GET_CHARACTERISTICS;
584 procedureProgress = BlueGigaProcedure.NONE;
587 case GET_CHARACTERISTICS:
588 // We've downloaded all attributes, now read the characteristic declarations
589 if (bgHandler.bgReadCharacteristicDeclarations(connection)) {
590 procedureTimer = startTimer(procedureTimeoutTask, TIMEOUT_SEC);
591 procedureProgress = BlueGigaProcedure.READ_CHARACTERISTIC_DECL;
593 procedureProgress = BlueGigaProcedure.NONE;
596 case READ_CHARACTERISTIC_DECL:
597 // We've downloaded read all the declarations, we are done now
598 procedureProgress = BlueGigaProcedure.NONE;
599 notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
601 case CHARACTERISTIC_READ:
603 notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, procedureCharacteristic,
604 BluetoothCompletionStatus.ERROR);
605 procedureProgress = BlueGigaProcedure.NONE;
606 procedureCharacteristic = null;
608 case CHARACTERISTIC_WRITE:
609 // The write completed - failure or success
610 BluetoothCompletionStatus result = event.getResult() == BgApiResponse.SUCCESS
611 ? BluetoothCompletionStatus.SUCCESS
612 : BluetoothCompletionStatus.ERROR;
613 notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, procedureCharacteristic, result);
614 procedureProgress = BlueGigaProcedure.NONE;
615 procedureCharacteristic = null;
617 case NOTIFICATION_ENABLE:
618 boolean success = event.getResult() == BgApiResponse.SUCCESS;
620 logger.debug("write to descriptor failed");
622 ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(success);
623 procedureProgress = BlueGigaProcedure.NONE;
624 procedureCharacteristic = null;
626 case NOTIFICATION_DISABLE:
627 success = event.getResult() == BgApiResponse.SUCCESS;
629 logger.debug("write to descriptor failed");
631 ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(!success);
632 procedureProgress = BlueGigaProcedure.NONE;
633 procedureCharacteristic = null;
640 private void handleConnectionStatusEvent(BlueGigaConnectionStatusEvent event) {
641 // Check if this is addressed to this device
642 if (!address.equals(new BluetoothAddress(event.getAddress()))) {
646 cancelTimer(connectTimer);
647 updateLastSeenTime();
649 // If we're connected, then remember the connection handle
650 if (event.getFlags().contains(ConnectionStatusFlag.CONNECTION_CONNECTED)) {
651 connectionState = ConnectionState.CONNECTED;
652 connection = event.getConnection();
653 notifyListeners(BluetoothEventType.CONNECTION_STATE,
654 new BluetoothConnectionStatusNotification(connectionState));
658 private void handleDisconnectedEvent(BlueGigaDisconnectedEvent event) {
659 // If this is not our connection handle then ignore.
660 if (connection != event.getConnection()) {
664 for (BlueGigaBluetoothCharacteristic ch : handleToCharacteristic.values()) {
665 ch.setNotifying(false);
668 cancelTimer(procedureTimer);
669 connectionState = ConnectionState.DISCONNECTED;
671 procedureProgress = BlueGigaProcedure.NONE;
673 notifyListeners(BluetoothEventType.CONNECTION_STATE,
674 new BluetoothConnectionStatusNotification(connectionState));
677 private void handleAttributeValueEvent(BlueGigaAttributeValueEvent event) {
678 // If this is not our connection handle then ignore.
679 if (connection != event.getConnection()) {
683 updateLastSeenTime();
685 logger.trace("BlueGiga AttributeValue: {} event={}", this, event);
687 int handle = event.getAttHandle();
689 Map.Entry<Integer, BlueGigaBluetoothCharacteristic> entry = handleToCharacteristic.floorEntry(handle);
691 logger.debug("BlueGiga didn't find characteristic for event {}", event);
695 BlueGigaBluetoothCharacteristic characteristic = entry.getValue();
697 if (handle == entry.getKey()) {
698 // this is the declaration
699 if (parseDeclaration(characteristic, event.getValue())) {
700 BluetoothService service = getServiceByHandle(handle);
701 if (service == null) {
702 logger.debug("BlueGiga: Unable to find service for handle {}", handle);
705 service.addCharacteristic(characteristic);
709 if (handle == characteristic.getHandle()) {
710 characteristic.setValue(event.getValue().clone());
712 // If this is the characteristic we were reading, then send a read completion
713 if (procedureProgress == BlueGigaProcedure.CHARACTERISTIC_READ && procedureCharacteristic != null
714 && procedureCharacteristic.getHandle() == event.getAttHandle()) {
715 procedureProgress = BlueGigaProcedure.NONE;
716 procedureCharacteristic = null;
717 notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
718 BluetoothCompletionStatus.SUCCESS);
722 // Notify the user of the updated value
723 notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
725 // it must be one of the descriptors we need to update
726 UUID attUUID = handleToUUID.get(handle);
727 BluetoothDescriptor descriptor = characteristic.getDescriptor(attUUID);
728 descriptor.setValue(toBytes(event.getValue()));
729 notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, descriptor);
733 private static byte @Nullable [] toBytes(int @Nullable [] value) {
737 byte[] ret = new byte[value.length];
738 for (int i = 0; i < value.length; i++) {
739 ret[i] = (byte) value[i];
744 private boolean parseDeclaration(BlueGigaBluetoothCharacteristic ch, int[] value) {
745 ByteBuffer buffer = ByteBuffer.wrap(toBytes(value));
746 buffer.order(ByteOrder.LITTLE_ENDIAN);
748 ch.setProperties(Byte.toUnsignedInt(buffer.get()));
749 ch.setHandle(Short.toUnsignedInt(buffer.getShort()));
751 switch (buffer.remaining()) {
753 long key = Short.toUnsignedLong(buffer.getShort());
754 ch.setUUID(BluetoothBindingConstants.createBluetoothUUID(key));
757 key = Integer.toUnsignedLong(buffer.getInt());
758 ch.setUUID(BluetoothBindingConstants.createBluetoothUUID(key));
761 long lower = buffer.getLong();
762 long upper = buffer.getLong();
763 ch.setUUID(new UUID(upper, lower));
766 logger.debug("Unexpected uuid length: {}", buffer.remaining());
772 * Clean up and release memory.
775 public void dispose() {
776 if (connectionState == ConnectionState.CONNECTED) {
779 cancelTimer(connectTimer);
780 cancelTimer(procedureTimer);
781 bgHandler.removeEventListener(this);
782 procedureProgress = BlueGigaProcedure.NONE;
783 connectionState = ConnectionState.DISCOVERING;
787 private void cancelTimer(@Nullable ScheduledFuture<?> task) {
793 private ScheduledFuture<?> startTimer(Runnable command, long timeout) {
794 return scheduler.schedule(command, timeout, TimeUnit.SECONDS);