]> git.basschouten.com Git - openhab-addons.git/blob
1d18a53ec1dfc8987da95bbd284576d5008cee5e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.time.ZonedDateTime;
16 import java.util.Collection;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.UUID;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.CopyOnWriteArraySet;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.locks.Condition;
24 import java.util.concurrent.locks.Lock;
25 import java.util.concurrent.locks.ReentrantLock;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * The {@link BaseBluetoothDevice} implements parts of the BluetoothDevice functionality that is
34  * shared to all concrete BluetoothDevice implementations.
35  *
36  * @author Connor Petty - Initial Contribution
37  */
38 @NonNullByDefault
39 public abstract class BaseBluetoothDevice extends BluetoothDevice {
40
41     private final Logger logger = LoggerFactory.getLogger(BaseBluetoothDevice.class);
42
43     /**
44      * Current connection state
45      */
46     protected ConnectionState connectionState = ConnectionState.DISCOVERING;
47
48     /**
49      * Manufacturer id
50      */
51     protected @Nullable Integer manufacturer = null;
52
53     /**
54      * Device name.
55      * <p>
56      * Uses the devices long name if known, otherwise the short name if known
57      */
58     protected @Nullable String name;
59
60     /**
61      * List of supported services
62      */
63     protected final Map<UUID, BluetoothService> supportedServices = new ConcurrentHashMap<>();
64
65     /**
66      * Last known RSSI
67      */
68     protected @Nullable Integer rssi = null;
69
70     /**
71      * Last reported transmitter power
72      */
73     protected @Nullable Integer txPower = null;
74
75     protected final transient ZonedDateTime createTime = ZonedDateTime.now();
76
77     /**
78      * Last time when activity occurred on this device.
79      */
80     protected @Nullable ZonedDateTime lastSeenTime = null;
81
82     /**
83      * The event listeners will be notified of device updates
84      */
85     private final Set<BluetoothDeviceListener> eventListeners = new CopyOnWriteArraySet<>();
86
87     private final Lock deviceLock = new ReentrantLock();
88     private final Condition connectionCondition = deviceLock.newCondition();
89     private final Condition serviceDiscoveryCondition = deviceLock.newCondition();
90
91     private volatile boolean servicesDiscovered = false;
92
93     /**
94      * Construct a Bluetooth device taking the Bluetooth address
95      *
96      * @param adapter
97      * @param address
98      */
99     public BaseBluetoothDevice(BluetoothAdapter adapter, BluetoothAddress address) {
100         super(adapter, address);
101     }
102
103     /**
104      * Returns the last time this device was active
105      *
106      * @return The last time this device was active
107      */
108     public @Nullable ZonedDateTime getLastSeenTime() {
109         return lastSeenTime;
110     }
111
112     /**
113      * Updates the last activity timestamp for this device.
114      * Should be called whenever activity occurs on this device.
115      *
116      */
117     public void updateLastSeenTime() {
118         lastSeenTime = ZonedDateTime.now();
119     }
120
121     /**
122      * Returns the name of the Bluetooth device.
123      *
124      * @return The devices name
125      */
126     @Override
127     public @Nullable String getName() {
128         return name;
129     }
130
131     /**
132      * Sets the manufacturer id for the device
133      *
134      * @param manufacturer the manufacturer id
135      */
136     public void setManufacturerId(int manufacturer) {
137         this.manufacturer = manufacturer;
138     }
139
140     /**
141      * Returns the manufacturer ID of the device
142      *
143      * @return an integer with manufacturer ID of the device, or null if not known
144      */
145     @Override
146     public @Nullable Integer getManufacturerId() {
147         return manufacturer;
148     }
149
150     /**
151      * Returns a {@link BluetoothService} if the requested service is supported
152      *
153      * @return the {@link BluetoothService} or null if the service is not supported.
154      */
155     @Override
156     public @Nullable BluetoothService getServices(UUID uuid) {
157         return supportedServices.get(uuid);
158     }
159
160     /**
161      * Returns a list of supported service UUIDs
162      *
163      * @return list of supported {@link BluetoothService}s.
164      */
165     @Override
166     public Collection<BluetoothService> getServices() {
167         return supportedServices.values();
168     }
169
170     /**
171      * Sets the device transmit power
172      *
173      * @param txPower the current transmitter power in dBm
174      */
175     public void setTxPower(int txPower) {
176         this.txPower = txPower;
177     }
178
179     /**
180      * Returns the last Transmit Power value or null if no transmit power has been received
181      *
182      * @return the last reported transmitter power value in dBm
183      */
184     @Override
185     public @Nullable Integer getTxPower() {
186         return txPower;
187     }
188
189     /**
190      * Sets the current Receive Signal Strength Indicator (RSSI) value
191      *
192      * @param rssi the current RSSI value in dBm
193      * @return true if the RSSI has changed, false if it was the same as previous
194      */
195     public boolean setRssi(int rssi) {
196         boolean changed = (this.rssi == null || this.rssi != rssi);
197         this.rssi = rssi;
198
199         return changed;
200     }
201
202     /**
203      * Returns the last Receive Signal Strength Indicator (RSSI) value or null if no RSSI has been received
204      *
205      * @return the last RSSI value in dBm
206      */
207     @Override
208     public @Nullable Integer getRssi() {
209         return rssi;
210     }
211
212     /**
213      * Set the name of the device
214      *
215      * @param name a {@link String} defining the device name
216      */
217     public void setName(String name) {
218         this.name = name;
219     }
220
221     /**
222      * Check if the device supports the specified service
223      *
224      * @param uuid the service {@link UUID}
225      * @return true if the service is supported
226      */
227     @Override
228     public boolean supportsService(UUID uuid) {
229         return supportedServices.containsKey(uuid);
230     }
231
232     /**
233      * Get the current connection state for this device
234      *
235      * @return the current {@link ConnectionState}
236      */
237     @Override
238     public ConnectionState getConnectionState() {
239         return connectionState;
240     }
241
242     /**
243      * Adds a service to the device.
244      *
245      * @param service the new {@link BluetoothService} to add
246      * @return true if the service was added or false if the service was already supported
247      */
248     @Override
249     protected boolean addService(BluetoothService service) {
250         BluetoothService oldValue = supportedServices.putIfAbsent(service.getUuid(), service);
251         if (oldValue == null) {
252             logger.trace("Adding new service to device {}: {}", address, service);
253             return true;
254         }
255         return false;
256     }
257
258     @Override
259     protected Collection<BluetoothDeviceListener> getListeners() {
260         return eventListeners;
261     }
262
263     /**
264      * Releases resources that this device is using.
265      *
266      */
267     @Override
268     protected void dispose() {
269     }
270
271     @Override
272     public boolean isServicesDiscovered() {
273         return servicesDiscovered;
274     }
275
276     @Override
277     public boolean awaitConnection(long timeout, TimeUnit unit) throws InterruptedException {
278         deviceLock.lock();
279         try {
280             long nanosTimeout = unit.toNanos(timeout);
281             while (getConnectionState() != ConnectionState.CONNECTED) {
282                 if (nanosTimeout <= 0L) {
283                     return false;
284                 }
285                 nanosTimeout = connectionCondition.awaitNanos(nanosTimeout);
286             }
287         } finally {
288             deviceLock.unlock();
289         }
290         return true;
291     }
292
293     @Override
294     public boolean awaitServiceDiscovery(long timeout, TimeUnit unit) throws InterruptedException {
295         deviceLock.lock();
296         try {
297             long nanosTimeout = unit.toNanos(timeout);
298             while (!servicesDiscovered) {
299                 if (nanosTimeout <= 0L) {
300                     return false;
301                 }
302                 nanosTimeout = serviceDiscoveryCondition.awaitNanos(nanosTimeout);
303             }
304         } finally {
305             deviceLock.unlock();
306         }
307         return true;
308     }
309
310     @Override
311     protected void notifyListeners(BluetoothEventType event, Object... args) {
312         switch (event) {
313             case SCAN_RECORD:
314             case CHARACTERISTIC_UPDATED:
315             case DESCRIPTOR_UPDATED:
316             case SERVICES_DISCOVERED:
317                 updateLastSeenTime();
318                 break;
319             default:
320                 break;
321         }
322         switch (event) {
323             case SERVICES_DISCOVERED:
324                 deviceLock.lock();
325                 try {
326                     servicesDiscovered = true;
327                     serviceDiscoveryCondition.signal();
328                 } finally {
329                     deviceLock.unlock();
330                 }
331                 break;
332             case CONNECTION_STATE:
333                 deviceLock.lock();
334                 try {
335                     connectionCondition.signal();
336                 } finally {
337                     deviceLock.unlock();
338                 }
339                 break;
340             default:
341                 break;
342         }
343         super.notifyListeners(event, args);
344     }
345
346     @Override
347     public String toString() {
348         StringBuilder builder = new StringBuilder();
349         builder.append("BluetoothDevice [address=");
350         builder.append(address);
351         builder.append(", manufacturer=");
352         builder.append(manufacturer);
353         if (BluetoothCompanyIdentifiers.get(manufacturer) != null) {
354             builder.append(" (");
355             builder.append(BluetoothCompanyIdentifiers.get(manufacturer));
356             builder.append(')');
357         }
358         builder.append(", name=");
359         builder.append(name);
360         builder.append(", rssi=");
361         builder.append(rssi);
362         builder.append(']');
363         return builder.toString();
364     }
365 }