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
33 public abstract class BluetoothDevice {
35 private final Logger logger = LoggerFactory.getLogger(BluetoothDevice.class);
38 * Enumeration of Bluetooth connection states
41 public enum ConnectionState {
43 * Device is still being discovered and is not available for use.
47 * Device has been discovered. This is used for the initial notification that the device is available.
51 * Device is disconnected.
55 * A connection is in progress.
59 * The device is connected.
63 * A disconnection is in progress.
68 protected enum BluetoothEventType {
71 CHARACTERISTIC_READ_COMPLETE,
72 CHARACTERISTIC_WRITE_COMPLETE,
73 CHARACTERISTIC_UPDATED,
80 * The adapter the device is accessed through
82 protected final BluetoothAdapter adapter;
85 * Devices Bluetooth address
87 protected final BluetoothAddress address;
90 * Construct a Bluetooth device taking the Bluetooth address
95 public BluetoothDevice(BluetoothAdapter adapter, BluetoothAddress address) {
96 this.address = address;
97 this.adapter = adapter;
101 * Returns the name of the Bluetooth device.
103 * @return The devices name
105 public abstract @Nullable String getName();
108 * Returns the manufacturer ID of the device
110 * @return an integer with manufacturer ID of the device, or null if not known
112 public abstract @Nullable Integer getManufacturerId();
115 * Returns the last Transmit Power value or null if no transmit power has been received
117 * @return the last reported transmitter power value in dBm
119 public abstract @Nullable Integer getTxPower();
122 * Returns the last Receive Signal Strength Indicator (RSSI) value or null if no RSSI has been received
124 * @return the last RSSI value in dBm
126 public abstract @Nullable Integer getRssi();
129 * Returns the physical address of the device.
131 * @return The physical address of the device
133 public BluetoothAddress getAddress() {
138 * Returns the adapter through which the device is accessed
140 * @return The adapter through which the device is accessed
142 public BluetoothAdapter getAdapter() {
147 * Returns a {@link BluetoothService} if the requested service is supported
149 * @return the {@link BluetoothService} or null if the service is not supported.
151 public abstract @Nullable BluetoothService getServices(UUID uuid);
154 * Returns a list of supported service UUIDs
156 * @return list of supported {@link BluetoothService}s.
158 public abstract Collection<BluetoothService> getServices();
161 * Check if the device supports the specified service
163 * @param uuid the service {@link UUID}
164 * @return true if the service is supported
166 public abstract boolean supportsService(UUID uuid);
169 * Get the current connection state for this device
171 * @return the current {@link ConnectionState}
173 public abstract ConnectionState getConnectionState();
176 * Connects to a device. This is an asynchronous method. Once the connection state is updated, the
177 * {@link BluetoothDeviceListener.onConnectionState} method will be called with the connection state.
179 * If the device is already connected, this will return false.
181 * @return true if the connection process is started successfully
183 public abstract boolean connect();
186 * Disconnects from a device. Once the connection state is updated, the
187 * {@link BluetoothDeviceListener.onConnectionState}
188 * method will be called with the connection state.
190 * If the device is not currently connected, this will return false.
192 * @return true if the disconnection process is started successfully
194 public abstract boolean disconnect();
197 * Starts a discovery on a device. This will iterate through all services and characteristics to build up a view of
200 * This method should be called before attempting to read or write characteristics.
202 * @return true if the discovery process is started successfully
204 public abstract boolean discoverServices();
207 * Gets a Bluetooth characteristic if it is known.
209 * Note that this method will not search for a characteristic in the remote device if it is not known.
210 * You must have previously connected to the device so that the device services and characteristics can
213 * @param uuid the {@link UUID} of the characteristic to return
214 * @return the {@link BluetoothCharacteristic} or null if the characteristic is not found in the device
216 public @Nullable BluetoothCharacteristic getCharacteristic(UUID uuid) {
217 for (BluetoothService service : getServices()) {
218 if (service.providesCharacteristic(uuid)) {
219 return service.getCharacteristic(uuid);
226 * Reads a characteristic. Only a single read or write operation can be requested at once. Attempting to perform an
227 * operation when one is already in progress will result in subsequent calls returning false.
229 * This is an asynchronous method. Once the read is complete
230 * {@link BluetoothDeviceListener.onCharacteristicReadComplete}
231 * method will be called with the completion state.
233 * Note that {@link BluetoothDeviceListener.onCharacteristicUpdate} will be called when the read value is received.
235 * @param characteristic the {@link BluetoothCharacteristic} to read.
236 * @return true if the characteristic read is started successfully
238 public abstract boolean readCharacteristic(BluetoothCharacteristic characteristic);
241 * Writes a characteristic. Only a single read or write operation can be requested at once. Attempting to perform an
242 * operation when one is already in progress will result in subsequent calls returning false.
244 * This is an asynchronous method. Once the write is complete
245 * {@link BluetoothDeviceListener.onCharacteristicWriteComplete} method will be called with the completion state.
247 * @param characteristic the {@link BluetoothCharacteristic} to read.
248 * @return true if the characteristic write is started successfully
250 public abstract boolean writeCharacteristic(BluetoothCharacteristic characteristic);
253 * Enables notifications for a characteristic. Only a single read or write operation can be requested at once.
254 * Attempting to perform an operation when one is already in progress will result in subsequent calls returning
257 * Notifications result in CHARACTERISTIC_UPDATED events to the listeners.
259 * @param characteristic the {@link BluetoothCharacteristic} to receive notifications for.
260 * @return true if the characteristic notification is started successfully
262 public abstract boolean enableNotifications(BluetoothCharacteristic characteristic);
265 * Disables notifications for a characteristic. Only a single read or write operation can be requested at once.
266 * Attempting to perform an operation when one is already in progress will result in subsequent calls returning
269 * @param characteristic the {@link BluetoothCharacteristic} to disable notifications for.
270 * @return true if the characteristic notification is stopped successfully
272 public abstract boolean disableNotifications(BluetoothCharacteristic characteristic);
275 * Enables notifications for a descriptor. 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 * Notifications result in DESCRIPTOR_UPDATED events to the listeners.
281 * @param descriptor the {@link BluetoothDescriptor} to receive notifications for.
282 * @return true if the descriptor notification is started successfully
284 public abstract boolean enableNotifications(BluetoothDescriptor descriptor);
287 * Disables notifications for a descriptor. Only a single read or write operation can be requested at once.
288 * Attempting to perform an operation when one is already in progress will result in subsequent calls returning
291 * @param descriptor the {@link BluetoothDescriptor} to disable notifications for.
292 * @return true if the descriptor notification is stopped successfully
294 public abstract boolean disableNotifications(BluetoothDescriptor descriptor);
297 * Adds a service to the device.
299 * @param service the new {@link BluetoothService} to add
300 * @return true if the service was added or false if the service was already supported
302 protected abstract boolean addService(BluetoothService service);
305 * Gets a service based on the handle.
306 * This will return a service if the handle falls within the start and end handles for the service.
308 * @param handle the handle for the service
309 * @return the {@link BluetoothService} or null if the service was not found
311 protected @Nullable BluetoothService getServiceByHandle(int handle) {
312 for (BluetoothService service : getServices()) {
313 if (service.getHandleStart() <= handle && service.getHandleEnd() >= handle) {
321 * Gets a characteristic based on the handle.
323 * @param handle the handle for the characteristic
324 * @return the {@link BluetoothCharacteristic} or null if the characteristic was not found
326 protected @Nullable BluetoothCharacteristic getCharacteristicByHandle(int handle) {
327 BluetoothService service = getServiceByHandle(handle);
328 if (service != null) {
329 return service.getCharacteristicByHandle(handle);
335 * Adds a device listener
337 * @param listener the {@link BluetoothDeviceListener} to add
339 public final void addListener(BluetoothDeviceListener listener) {
340 getListeners().add(listener);
344 * Removes a device listener
346 * @param listener the {@link BluetoothDeviceListener} to remove
348 public final void removeListener(BluetoothDeviceListener listener) {
349 getListeners().remove(listener);
353 * Checks if this device has any listeners
355 * @return true if this device has listeners
357 public final boolean hasListeners() {
358 return !getListeners().isEmpty();
362 * Releases resources that this device is using.
365 protected abstract void dispose();
367 protected abstract Collection<BluetoothDeviceListener> getListeners();
370 * Notify the listeners of an event
372 * @param event the {@link BluetoothEventType} of this event
373 * @param args an array of arguments to pass to the callback
375 protected void notifyListeners(BluetoothEventType event, Object... args) {
376 for (BluetoothDeviceListener listener : getListeners()) {
380 listener.onScanRecordReceived((BluetoothScanNotification) args[0]);
382 case CONNECTION_STATE:
383 listener.onConnectionStateChange((BluetoothConnectionStatusNotification) args[0]);
385 case SERVICES_DISCOVERED:
386 listener.onServicesDiscovered();
388 case CHARACTERISTIC_READ_COMPLETE:
389 listener.onCharacteristicReadComplete((BluetoothCharacteristic) args[0],
390 (BluetoothCompletionStatus) args[1]);
392 case CHARACTERISTIC_WRITE_COMPLETE:
393 listener.onCharacteristicWriteComplete((BluetoothCharacteristic) args[0],
394 (BluetoothCompletionStatus) args[1]);
396 case CHARACTERISTIC_UPDATED:
397 listener.onCharacteristicUpdate((BluetoothCharacteristic) args[0]);
399 case DESCRIPTOR_UPDATED:
400 listener.onDescriptorUpdate((BluetoothDescriptor) args[0]);
402 case ADAPTER_CHANGED:
403 listener.onAdapterChanged((BluetoothAdapter) args[0]);
406 } catch (Exception e) {
407 logger.error("Failed to inform listener '{}': {}", listener, e.getMessage(), e);