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;
15 import java.util.Collection;
16 import java.util.UUID;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
21 import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
26 * The {@link BluetoothDevice} class provides a base implementation of a Bluetooth Low Energy device
28 * @author Chris Jackson - Initial contribution
29 * @author Kai Kreuzer - Refactored class to use Integer instead of int, fixed bugs, diverse improvements
30 * @author Connor Petty - Made most of the methods abstract
31 * @author Peter Rosenberg - Improve notifications
34 public abstract class BluetoothDevice {
36 private final Logger logger = LoggerFactory.getLogger(BluetoothDevice.class);
39 * Enumeration of Bluetooth connection states
42 public enum ConnectionState {
44 * Device is still being discovered and is not available for use.
48 * Device has been discovered. This is used for the initial notification that the device is available.
52 * Device is disconnected.
56 * A connection is in progress.
60 * The device is connected.
64 * A disconnection is in progress.
69 protected enum BluetoothEventType {
72 CHARACTERISTIC_READ_COMPLETE,
73 CHARACTERISTIC_WRITE_COMPLETE,
74 CHARACTERISTIC_UPDATED,
81 * The adapter the device is accessed through
83 protected final BluetoothAdapter adapter;
86 * Devices Bluetooth address
88 protected final BluetoothAddress address;
91 * Construct a Bluetooth device taking the Bluetooth address
96 public BluetoothDevice(BluetoothAdapter adapter, BluetoothAddress address) {
97 this.address = address;
98 this.adapter = adapter;
102 * Returns the name of the Bluetooth device.
104 * @return The devices name
106 public abstract @Nullable String getName();
109 * Returns the manufacturer ID of the device
111 * @return an integer with manufacturer ID of the device, or null if not known
113 public abstract @Nullable Integer getManufacturerId();
116 * Returns the last Transmit Power value or null if no transmit power has been received
118 * @return the last reported transmitter power value in dBm
120 public abstract @Nullable Integer getTxPower();
123 * Returns the last Receive Signal Strength Indicator (RSSI) value or null if no RSSI has been received
125 * @return the last RSSI value in dBm
127 public abstract @Nullable Integer getRssi();
130 * Returns the physical address of the device.
132 * @return The physical address of the device
134 public BluetoothAddress getAddress() {
139 * Returns the adapter through which the device is accessed
141 * @return The adapter through which the device is accessed
143 public BluetoothAdapter getAdapter() {
148 * Returns a {@link BluetoothService} if the requested service is supported
150 * @return the {@link BluetoothService} or null if the service is not supported.
152 public abstract @Nullable BluetoothService getServices(UUID uuid);
155 * Returns a list of supported service UUIDs
157 * @return list of supported {@link BluetoothService}s.
159 public abstract Collection<BluetoothService> getServices();
162 * Check if the device supports the specified service
164 * @param uuid the service {@link UUID}
165 * @return true if the service is supported
167 public abstract boolean supportsService(UUID uuid);
170 * Get the current connection state for this device
172 * @return the current {@link ConnectionState}
174 public abstract ConnectionState getConnectionState();
177 * Connects to a device. This is an asynchronous method. Once the connection state is updated, the
178 * {@link BluetoothDeviceListener.onConnectionState} method will be called with the connection state.
180 * If the device is already connected, this will return false.
182 * @return true if the connection process is started successfully
184 public abstract boolean connect();
187 * Disconnects from a device. Once the connection state is updated, the
188 * {@link BluetoothDeviceListener.onConnectionState}
189 * method will be called with the connection state.
191 * If the device is not currently connected, this will return false.
193 * @return true if the disconnection process is started successfully
195 public abstract boolean disconnect();
198 * Starts a discovery on a device. This will iterate through all services and characteristics to build up a view of
201 * This method should be called before attempting to read or write characteristics.
203 * @return true if the discovery process is started successfully
205 public abstract boolean discoverServices();
208 * Gets a Bluetooth characteristic if it is known.
210 * Note that this method will not search for a characteristic in the remote device if it is not known.
211 * You must have previously connected to the device so that the device services and characteristics can
214 * @param uuid the {@link UUID} of the characteristic to return
215 * @return the {@link BluetoothCharacteristic} or null if the characteristic is not found in the device
217 public @Nullable BluetoothCharacteristic getCharacteristic(UUID uuid) {
218 for (BluetoothService service : getServices()) {
219 if (service.providesCharacteristic(uuid)) {
220 return service.getCharacteristic(uuid);
227 * Reads a characteristic. Only a single read or write operation can be requested at once. Attempting to perform an
228 * operation when one is already in progress will result in subsequent calls returning false.
230 * This is an asynchronous method. Once the read is complete
231 * {@link BluetoothDeviceListener.onCharacteristicReadComplete}
232 * method will be called with the completion state.
234 * Note that {@link BluetoothDeviceListener.onCharacteristicUpdate} will be called when the read value is received.
236 * @param characteristic the {@link BluetoothCharacteristic} to read.
237 * @return true if the characteristic read is started successfully
239 public abstract boolean readCharacteristic(BluetoothCharacteristic characteristic);
242 * Writes a characteristic. Only a single read or write operation can be requested at once. Attempting to perform an
243 * operation when one is already in progress will result in subsequent calls returning false.
245 * This is an asynchronous method. Once the write is complete
246 * {@link BluetoothDeviceListener.onCharacteristicWriteComplete} method will be called with the completion state.
248 * @param characteristic the {@link BluetoothCharacteristic} to read.
249 * @return true if the characteristic write is started successfully
251 public abstract boolean writeCharacteristic(BluetoothCharacteristic characteristic);
254 * Returns if notification is enabled for the given characteristic.
256 * @param characteristic the {@link BluetoothCharacteristic} to check if notifications are enabled.
257 * @return true if notification is enabled, false if notification is disabled, characteristic is missing on device
258 * or notifications are not supported.
260 public abstract boolean isNotifying(BluetoothCharacteristic characteristic);
263 * Enables notifications for a characteristic. Only a single read or write operation can be requested at once.
264 * Attempting to perform an operation when one is already in progress will result in subsequent calls returning
267 * Notifications result in CHARACTERISTIC_UPDATED events to the listeners.
269 * @param characteristic the {@link BluetoothCharacteristic} to receive notifications for.
270 * @return true if the characteristic notification is started successfully
272 public abstract boolean enableNotifications(BluetoothCharacteristic characteristic);
275 * Disables notifications for a characteristic. Only a single read or write operation can be requested at once.
276 * Attempting to perform an operation when one is already in progress will result in subsequent calls returning
279 * @param characteristic the {@link BluetoothCharacteristic} to disable notifications for.
280 * @return true if the characteristic notification is stopped successfully
282 public abstract boolean disableNotifications(BluetoothCharacteristic characteristic);
285 * Enables notifications for a descriptor. Only a single read or write operation can be requested at once.
286 * Attempting to perform an operation when one is already in progress will result in subsequent calls returning
289 * Notifications result in DESCRIPTOR_UPDATED events to the listeners.
291 * @param descriptor the {@link BluetoothDescriptor} to receive notifications for.
292 * @return true if the descriptor notification is started successfully
294 public abstract boolean enableNotifications(BluetoothDescriptor descriptor);
297 * Disables notifications for a descriptor. Only a single read or write operation can be requested at once.
298 * Attempting to perform an operation when one is already in progress will result in subsequent calls returning
301 * @param descriptor the {@link BluetoothDescriptor} to disable notifications for.
302 * @return true if the descriptor notification is stopped successfully
304 public abstract boolean disableNotifications(BluetoothDescriptor descriptor);
307 * Adds a service to the device.
309 * @param service the new {@link BluetoothService} to add
310 * @return true if the service was added or false if the service was already supported
312 protected abstract boolean addService(BluetoothService service);
315 * Gets a service based on the handle.
316 * This will return a service if the handle falls within the start and end handles for the service.
318 * @param handle the handle for the service
319 * @return the {@link BluetoothService} or null if the service was not found
321 protected @Nullable BluetoothService getServiceByHandle(int handle) {
322 for (BluetoothService service : getServices()) {
323 if (service.getHandleStart() <= handle && service.getHandleEnd() >= handle) {
331 * Gets a characteristic based on the handle.
333 * @param handle the handle for the characteristic
334 * @return the {@link BluetoothCharacteristic} or null if the characteristic was not found
336 protected @Nullable BluetoothCharacteristic getCharacteristicByHandle(int handle) {
337 BluetoothService service = getServiceByHandle(handle);
338 if (service != null) {
339 return service.getCharacteristicByHandle(handle);
345 * Adds a device listener
347 * @param listener the {@link BluetoothDeviceListener} to add
349 public final void addListener(BluetoothDeviceListener listener) {
350 getListeners().add(listener);
354 * Removes a device listener
356 * @param listener the {@link BluetoothDeviceListener} to remove
358 public final void removeListener(BluetoothDeviceListener listener) {
359 getListeners().remove(listener);
363 * Checks if this device has any listeners
365 * @return true if this device has listeners
367 public final boolean hasListeners() {
368 return !getListeners().isEmpty();
372 * Releases resources that this device is using.
375 protected abstract void dispose();
377 protected abstract Collection<BluetoothDeviceListener> getListeners();
380 * Notify the listeners of an event
382 * @param event the {@link BluetoothEventType} of this event
383 * @param args an array of arguments to pass to the callback
385 protected void notifyListeners(BluetoothEventType event, Object... args) {
386 for (BluetoothDeviceListener listener : getListeners()) {
390 listener.onScanRecordReceived((BluetoothScanNotification) args[0]);
392 case CONNECTION_STATE:
393 listener.onConnectionStateChange((BluetoothConnectionStatusNotification) args[0]);
395 case SERVICES_DISCOVERED:
396 listener.onServicesDiscovered();
398 case CHARACTERISTIC_READ_COMPLETE:
399 listener.onCharacteristicReadComplete((BluetoothCharacteristic) args[0],
400 (BluetoothCompletionStatus) args[1]);
402 case CHARACTERISTIC_WRITE_COMPLETE:
403 listener.onCharacteristicWriteComplete((BluetoothCharacteristic) args[0],
404 (BluetoothCompletionStatus) args[1]);
406 case CHARACTERISTIC_UPDATED:
407 listener.onCharacteristicUpdate((BluetoothCharacteristic) args[0]);
409 case DESCRIPTOR_UPDATED:
410 listener.onDescriptorUpdate((BluetoothDescriptor) args[0]);
412 case ADAPTER_CHANGED:
413 listener.onAdapterChanged((BluetoothAdapter) args[0]);
416 } catch (Exception e) {
417 logger.error("Failed to inform listener '{}': {}", listener, e.getMessage(), e);