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