]> git.basschouten.com Git - openhab-addons.git/blob
ba1186829f12bf3749b57367ee2861db78bdc7d5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth;
14
15 import java.util.Collection;
16 import java.util.UUID;
17
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;
24
25 /**
26  * The {@link BluetoothDevice} class provides a base implementation of a Bluetooth Low Energy device
27  *
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
32  */
33 @NonNullByDefault
34 public abstract class BluetoothDevice {
35
36     private final Logger logger = LoggerFactory.getLogger(BluetoothDevice.class);
37
38     /**
39      * Enumeration of Bluetooth connection states
40      *
41      */
42     public enum ConnectionState {
43         /**
44          * Device is still being discovered and is not available for use.
45          */
46         DISCOVERING,
47         /**
48          * Device has been discovered. This is used for the initial notification that the device is available.
49          */
50         DISCOVERED,
51         /**
52          * Device is disconnected.
53          */
54         DISCONNECTED,
55         /**
56          * A connection is in progress.
57          */
58         CONNECTING,
59         /**
60          * The device is connected.
61          */
62         CONNECTED,
63         /**
64          * A disconnection is in progress.
65          */
66         DISCONNECTING
67     }
68
69     protected enum BluetoothEventType {
70         CONNECTION_STATE,
71         SCAN_RECORD,
72         CHARACTERISTIC_READ_COMPLETE,
73         CHARACTERISTIC_WRITE_COMPLETE,
74         CHARACTERISTIC_UPDATED,
75         DESCRIPTOR_UPDATED,
76         SERVICES_DISCOVERED,
77         ADAPTER_CHANGED
78     }
79
80     /**
81      * The adapter the device is accessed through
82      */
83     protected final BluetoothAdapter adapter;
84
85     /**
86      * Devices Bluetooth address
87      */
88     protected final BluetoothAddress address;
89
90     /**
91      * Construct a Bluetooth device taking the Bluetooth address
92      *
93      * @param adapter
94      * @param sender
95      */
96     public BluetoothDevice(BluetoothAdapter adapter, BluetoothAddress address) {
97         this.address = address;
98         this.adapter = adapter;
99     }
100
101     /**
102      * Returns the name of the Bluetooth device.
103      *
104      * @return The devices name
105      */
106     public abstract @Nullable String getName();
107
108     /**
109      * Returns the manufacturer ID of the device
110      *
111      * @return an integer with manufacturer ID of the device, or null if not known
112      */
113     public abstract @Nullable Integer getManufacturerId();
114
115     /**
116      * Returns the last Transmit Power value or null if no transmit power has been received
117      *
118      * @return the last reported transmitter power value in dBm
119      */
120     public abstract @Nullable Integer getTxPower();
121
122     /**
123      * Returns the last Receive Signal Strength Indicator (RSSI) value or null if no RSSI has been received
124      *
125      * @return the last RSSI value in dBm
126      */
127     public abstract @Nullable Integer getRssi();
128
129     /**
130      * Returns the physical address of the device.
131      *
132      * @return The physical address of the device
133      */
134     public BluetoothAddress getAddress() {
135         return address;
136     }
137
138     /**
139      * Returns the adapter through which the device is accessed
140      *
141      * @return The adapter through which the device is accessed
142      */
143     public BluetoothAdapter getAdapter() {
144         return adapter;
145     }
146
147     /**
148      * Returns a {@link BluetoothService} if the requested service is supported
149      *
150      * @return the {@link BluetoothService} or null if the service is not supported.
151      */
152     public abstract @Nullable BluetoothService getServices(UUID uuid);
153
154     /**
155      * Returns a list of supported service UUIDs
156      *
157      * @return list of supported {@link BluetoothService}s.
158      */
159     public abstract Collection<BluetoothService> getServices();
160
161     /**
162      * Check if the device supports the specified service
163      *
164      * @param uuid the service {@link UUID}
165      * @return true if the service is supported
166      */
167     public abstract boolean supportsService(UUID uuid);
168
169     /**
170      * Get the current connection state for this device
171      *
172      * @return the current {@link ConnectionState}
173      */
174     public abstract ConnectionState getConnectionState();
175
176     /**
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.
179      * <p>
180      * If the device is already connected, this will return false.
181      *
182      * @return true if the connection process is started successfully
183      */
184     public abstract boolean connect();
185
186     /**
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.
190      * <p>
191      * If the device is not currently connected, this will return false.
192      *
193      * @return true if the disconnection process is started successfully
194      */
195     public abstract boolean disconnect();
196
197     /**
198      * Starts a discovery on a device. This will iterate through all services and characteristics to build up a view of
199      * the device.
200      * <p>
201      * This method should be called before attempting to read or write characteristics.
202      *
203      * @return true if the discovery process is started successfully
204      */
205     public abstract boolean discoverServices();
206
207     /**
208      * Gets a Bluetooth characteristic if it is known.
209      * <p>
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
212      * be retrieved.
213      *
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
216      */
217     public @Nullable BluetoothCharacteristic getCharacteristic(UUID uuid) {
218         for (BluetoothService service : getServices()) {
219             if (service.providesCharacteristic(uuid)) {
220                 return service.getCharacteristic(uuid);
221             }
222         }
223         return null;
224     }
225
226     /**
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.
229      * <p>
230      * This is an asynchronous method. Once the read is complete
231      * {@link BluetoothDeviceListener.onCharacteristicReadComplete}
232      * method will be called with the completion state.
233      * <p>
234      * Note that {@link BluetoothDeviceListener.onCharacteristicUpdate} will be called when the read value is received.
235      *
236      * @param characteristic the {@link BluetoothCharacteristic} to read.
237      * @return true if the characteristic read is started successfully
238      */
239     public abstract boolean readCharacteristic(BluetoothCharacteristic characteristic);
240
241     /**
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.
244      * <p>
245      * This is an asynchronous method. Once the write is complete
246      * {@link BluetoothDeviceListener.onCharacteristicWriteComplete} method will be called with the completion state.
247      *
248      * @param characteristic the {@link BluetoothCharacteristic} to read.
249      * @return true if the characteristic write is started successfully
250      */
251     public abstract boolean writeCharacteristic(BluetoothCharacteristic characteristic);
252
253     /**
254      * Returns if notification is enabled for the given characteristic.
255      *
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.
259      */
260     public abstract boolean isNotifying(BluetoothCharacteristic characteristic);
261
262     /**
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
265      * false.
266      * <p>
267      * Notifications result in CHARACTERISTIC_UPDATED events to the listeners.
268      *
269      * @param characteristic the {@link BluetoothCharacteristic} to receive notifications for.
270      * @return true if the characteristic notification is started successfully
271      */
272     public abstract boolean enableNotifications(BluetoothCharacteristic characteristic);
273
274     /**
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
277      * false.
278      *
279      * @param characteristic the {@link BluetoothCharacteristic} to disable notifications for.
280      * @return true if the characteristic notification is stopped successfully
281      */
282     public abstract boolean disableNotifications(BluetoothCharacteristic characteristic);
283
284     /**
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
287      * false.
288      * <p>
289      * Notifications result in DESCRIPTOR_UPDATED events to the listeners.
290      *
291      * @param descriptor the {@link BluetoothDescriptor} to receive notifications for.
292      * @return true if the descriptor notification is started successfully
293      */
294     public abstract boolean enableNotifications(BluetoothDescriptor descriptor);
295
296     /**
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
299      * false.
300      *
301      * @param descriptor the {@link BluetoothDescriptor} to disable notifications for.
302      * @return true if the descriptor notification is stopped successfully
303      */
304     public abstract boolean disableNotifications(BluetoothDescriptor descriptor);
305
306     /**
307      * Adds a service to the device.
308      *
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
311      */
312     protected abstract boolean addService(BluetoothService service);
313
314     /**
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.
317      *
318      * @param handle the handle for the service
319      * @return the {@link BluetoothService} or null if the service was not found
320      */
321     protected @Nullable BluetoothService getServiceByHandle(int handle) {
322         for (BluetoothService service : getServices()) {
323             if (service.getHandleStart() <= handle && service.getHandleEnd() >= handle) {
324                 return service;
325             }
326         }
327         return null;
328     }
329
330     /**
331      * Gets a characteristic based on the handle.
332      *
333      * @param handle the handle for the characteristic
334      * @return the {@link BluetoothCharacteristic} or null if the characteristic was not found
335      */
336     protected @Nullable BluetoothCharacteristic getCharacteristicByHandle(int handle) {
337         BluetoothService service = getServiceByHandle(handle);
338         if (service != null) {
339             return service.getCharacteristicByHandle(handle);
340         }
341         return null;
342     }
343
344     /**
345      * Adds a device listener
346      *
347      * @param listener the {@link BluetoothDeviceListener} to add
348      */
349     public final void addListener(BluetoothDeviceListener listener) {
350         getListeners().add(listener);
351     }
352
353     /**
354      * Removes a device listener
355      *
356      * @param listener the {@link BluetoothDeviceListener} to remove
357      */
358     public final void removeListener(BluetoothDeviceListener listener) {
359         getListeners().remove(listener);
360     }
361
362     /**
363      * Checks if this device has any listeners
364      *
365      * @return true if this device has listeners
366      */
367     public final boolean hasListeners() {
368         return !getListeners().isEmpty();
369     }
370
371     /**
372      * Releases resources that this device is using.
373      *
374      */
375     protected abstract void dispose();
376
377     protected abstract Collection<BluetoothDeviceListener> getListeners();
378
379     /**
380      * Notify the listeners of an event
381      *
382      * @param event the {@link BluetoothEventType} of this event
383      * @param args an array of arguments to pass to the callback
384      */
385     protected void notifyListeners(BluetoothEventType event, Object... args) {
386         for (BluetoothDeviceListener listener : getListeners()) {
387             try {
388                 switch (event) {
389                     case SCAN_RECORD:
390                         listener.onScanRecordReceived((BluetoothScanNotification) args[0]);
391                         break;
392                     case CONNECTION_STATE:
393                         listener.onConnectionStateChange((BluetoothConnectionStatusNotification) args[0]);
394                         break;
395                     case SERVICES_DISCOVERED:
396                         listener.onServicesDiscovered();
397                         break;
398                     case CHARACTERISTIC_READ_COMPLETE:
399                         listener.onCharacteristicReadComplete((BluetoothCharacteristic) args[0],
400                                 (BluetoothCompletionStatus) args[1]);
401                         break;
402                     case CHARACTERISTIC_WRITE_COMPLETE:
403                         listener.onCharacteristicWriteComplete((BluetoothCharacteristic) args[0],
404                                 (BluetoothCompletionStatus) args[1]);
405                         break;
406                     case CHARACTERISTIC_UPDATED:
407                         listener.onCharacteristicUpdate((BluetoothCharacteristic) args[0]);
408                         break;
409                     case DESCRIPTOR_UPDATED:
410                         listener.onDescriptorUpdate((BluetoothDescriptor) args[0]);
411                         break;
412                     case ADAPTER_CHANGED:
413                         listener.onAdapterChanged((BluetoothAdapter) args[0]);
414                         break;
415                 }
416             } catch (Exception e) {
417                 logger.error("Failed to inform listener '{}': {}", listener, e.getMessage(), e);
418             }
419         }
420     }
421 }