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.roaming.internal;
15 import java.util.Collection;
16 import java.util.List;
18 import java.util.Objects;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.concurrent.CopyOnWriteArrayList;
21 import java.util.concurrent.atomic.AtomicReference;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.bluetooth.BluetoothAdapter;
26 import org.openhab.binding.bluetooth.BluetoothAddress;
27 import org.openhab.binding.bluetooth.BluetoothCharacteristic;
28 import org.openhab.binding.bluetooth.BluetoothDescriptor;
29 import org.openhab.binding.bluetooth.BluetoothDevice;
30 import org.openhab.binding.bluetooth.BluetoothDeviceListener;
31 import org.openhab.binding.bluetooth.DelegateBluetoothDevice;
32 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
33 import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
36 * The {@link RoamingBluetoothDevice} acts as a roaming device by delegating
37 * its operations to actual adapters.
39 * @author Connor Petty - Initial contribution
42 public class RoamingBluetoothDevice extends DelegateBluetoothDevice {
44 private final Map<BluetoothDevice, Listener> devices = new ConcurrentHashMap<>();
46 private final List<BluetoothDeviceListener> eventListeners = new CopyOnWriteArrayList<>();
48 private final AtomicReference<@Nullable BluetoothDevice> currentDelegateRef = new AtomicReference<>();
50 protected RoamingBluetoothDevice(RoamingBridgeHandler roamingAdapter, BluetoothAddress address) {
51 super(roamingAdapter, address);
54 public void addBluetoothDevice(BluetoothDevice device) {
55 device.addListener(Objects.requireNonNull(devices.computeIfAbsent(device, Listener::new)));
58 public void removeBluetoothDevice(BluetoothDevice device) {
59 BluetoothDeviceListener listener = devices.remove(device);
60 if (listener != null) {
61 device.removeListener(listener);
66 protected Collection<BluetoothDeviceListener> getListeners() {
67 return eventListeners;
71 @SuppressWarnings("PMD.CompareObjectsWithEquals")
72 protected @Nullable BluetoothDevice getDelegate() {
73 BluetoothDevice newDelegate = null;
74 int newRssi = Integer.MIN_VALUE;
75 for (BluetoothDevice device : devices.keySet()) {
76 ConnectionState state = device.getConnectionState();
77 if (state == ConnectionState.CONNECTING || state == ConnectionState.CONNECTED) {
81 Integer rssi = device.getRssi();
82 if (rssi != null && (newDelegate == null || rssi > newRssi)) {
87 BluetoothDevice oldDelegate = currentDelegateRef.getAndSet(newDelegate);
88 if (oldDelegate != newDelegate) { // using reference comparison is valid in this case
89 notifyListeners(BluetoothEventType.ADAPTER_CHANGED, getAdapter(newDelegate));
94 private BluetoothAdapter getAdapter(@Nullable BluetoothDevice delegate) {
95 if (delegate != null) {
96 return delegate.getAdapter();
98 // as a last resort we return our "actual" adapter
99 return super.getAdapter();
103 public BluetoothAdapter getAdapter() {
104 return getAdapter(currentDelegateRef.get());
107 @SuppressWarnings("PMD.CompareObjectsWithEquals")
108 private class Listener implements BluetoothDeviceListener {
110 private BluetoothDevice device;
112 public Listener(BluetoothDevice device) {
113 this.device = device;
117 public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
118 if (device == getDelegate()) {
119 notifyListeners(BluetoothEventType.SCAN_RECORD, scanNotification);
124 public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
125 if (device == getDelegate()) {
126 notifyListeners(BluetoothEventType.CONNECTION_STATE, connectionNotification);
131 public void onServicesDiscovered() {
132 device.getServices().forEach(RoamingBluetoothDevice.this::addService);
133 if (device == getDelegate()) {
134 notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
139 public void onCharacteristicUpdate(BluetoothCharacteristic characteristic, byte[] value) {
140 if (device == getDelegate()) {
141 notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic, value);
146 public void onDescriptorUpdate(BluetoothDescriptor bluetoothDescriptor, byte[] value) {
147 if (device == getDelegate()) {
148 notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, bluetoothDescriptor, value);
153 public void onAdapterChanged(BluetoothAdapter adapter) {
154 // do nothing since we are the ones that are supposed to trigger this