2 * Copyright (c) 2010-2023 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.time.ZonedDateTime;
16 import java.util.Collection;
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;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
33 * The {@link BaseBluetoothDevice} implements parts of the BluetoothDevice functionality that is
34 * shared to all concrete BluetoothDevice implementations.
36 * @author Connor Petty - Initial Contribution
39 public abstract class BaseBluetoothDevice extends BluetoothDevice {
41 private final Logger logger = LoggerFactory.getLogger(BaseBluetoothDevice.class);
44 * Current connection state
46 protected ConnectionState connectionState = ConnectionState.DISCOVERING;
51 protected @Nullable Integer manufacturer = null;
56 * Uses the devices long name if known, otherwise the short name if known
58 protected @Nullable String name;
61 * List of supported services
63 protected final Map<UUID, BluetoothService> supportedServices = new ConcurrentHashMap<>();
68 protected @Nullable Integer rssi = null;
71 * Last reported transmitter power
73 protected @Nullable Integer txPower = null;
75 protected final transient ZonedDateTime createTime = ZonedDateTime.now();
78 * Last time when activity occurred on this device.
80 protected @Nullable ZonedDateTime lastSeenTime = null;
83 * The event listeners will be notified of device updates
85 private final Set<BluetoothDeviceListener> eventListeners = new CopyOnWriteArraySet<>();
87 private final Lock deviceLock = new ReentrantLock();
88 private final Condition connectionCondition = deviceLock.newCondition();
89 private final Condition serviceDiscoveryCondition = deviceLock.newCondition();
91 private volatile boolean servicesDiscovered = false;
94 * Construct a Bluetooth device taking the Bluetooth address
99 public BaseBluetoothDevice(BluetoothAdapter adapter, BluetoothAddress address) {
100 super(adapter, address);
104 * Returns the last time this device was active
106 * @return The last time this device was active
108 public @Nullable ZonedDateTime getLastSeenTime() {
113 * Updates the last activity timestamp for this device.
114 * Should be called whenever activity occurs on this device.
117 public void updateLastSeenTime() {
118 lastSeenTime = ZonedDateTime.now();
122 * Returns the name of the Bluetooth device.
124 * @return The devices name
127 public @Nullable String getName() {
132 * Sets the manufacturer id for the device
134 * @param manufacturer the manufacturer id
136 public void setManufacturerId(int manufacturer) {
137 this.manufacturer = manufacturer;
141 * Returns the manufacturer ID of the device
143 * @return an integer with manufacturer ID of the device, or null if not known
146 public @Nullable Integer getManufacturerId() {
151 * Returns a {@link BluetoothService} if the requested service is supported
153 * @return the {@link BluetoothService} or null if the service is not supported.
156 public @Nullable BluetoothService getServices(UUID uuid) {
157 return supportedServices.get(uuid);
161 * Returns a list of supported service UUIDs
163 * @return list of supported {@link BluetoothService}s.
166 public Collection<BluetoothService> getServices() {
167 return supportedServices.values();
171 * Sets the device transmit power
173 * @param txPower the current transmitter power in dBm
175 public void setTxPower(int txPower) {
176 this.txPower = txPower;
180 * Returns the last Transmit Power value or null if no transmit power has been received
182 * @return the last reported transmitter power value in dBm
185 public @Nullable Integer getTxPower() {
190 * Sets the current Receive Signal Strength Indicator (RSSI) value
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
195 public boolean setRssi(int rssi) {
196 boolean changed = (this.rssi == null || this.rssi != rssi);
203 * Returns the last Receive Signal Strength Indicator (RSSI) value or null if no RSSI has been received
205 * @return the last RSSI value in dBm
208 public @Nullable Integer getRssi() {
213 * Set the name of the device
215 * @param name a {@link String} defining the device name
217 public void setName(String name) {
222 * Check if the device supports the specified service
224 * @param uuid the service {@link UUID}
225 * @return true if the service is supported
228 public boolean supportsService(UUID uuid) {
229 return supportedServices.containsKey(uuid);
233 * Get the current connection state for this device
235 * @return the current {@link ConnectionState}
238 public ConnectionState getConnectionState() {
239 return connectionState;
243 * Adds a service to the device.
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
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);
259 protected Collection<BluetoothDeviceListener> getListeners() {
260 return eventListeners;
264 * Releases resources that this device is using.
268 protected void dispose() {
272 public boolean isServicesDiscovered() {
273 return servicesDiscovered;
277 public boolean awaitConnection(long timeout, TimeUnit unit) throws InterruptedException {
280 long nanosTimeout = unit.toNanos(timeout);
281 while (getConnectionState() != ConnectionState.CONNECTED) {
282 if (nanosTimeout <= 0L) {
285 nanosTimeout = connectionCondition.awaitNanos(nanosTimeout);
294 public boolean awaitServiceDiscovery(long timeout, TimeUnit unit) throws InterruptedException {
297 long nanosTimeout = unit.toNanos(timeout);
298 while (!servicesDiscovered) {
299 if (nanosTimeout <= 0L) {
302 nanosTimeout = serviceDiscoveryCondition.awaitNanos(nanosTimeout);
311 protected void notifyListeners(BluetoothEventType event, Object... args) {
314 case CHARACTERISTIC_UPDATED:
315 case DESCRIPTOR_UPDATED:
316 case SERVICES_DISCOVERED:
317 updateLastSeenTime();
323 case SERVICES_DISCOVERED:
326 servicesDiscovered = true;
327 serviceDiscoveryCondition.signal();
332 case CONNECTION_STATE:
335 connectionCondition.signal();
343 super.notifyListeners(event, args);
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));
358 builder.append(", name=");
359 builder.append(name);
360 builder.append(", rssi=");
361 builder.append(rssi);
363 return builder.toString();