2 * Copyright (c) 2010-2020 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.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.CopyOnWriteArrayList;
20 import java.util.concurrent.atomic.AtomicReference;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.bluetooth.BluetoothAdapter;
25 import org.openhab.binding.bluetooth.BluetoothAddress;
26 import org.openhab.binding.bluetooth.BluetoothCharacteristic;
27 import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
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(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 protected @Nullable BluetoothDevice getDelegate() {
72 BluetoothDevice newDelegate = null;
73 int newRssi = Integer.MIN_VALUE;
74 for (BluetoothDevice device : devices.keySet()) {
75 ConnectionState state = device.getConnectionState();
76 if (state == ConnectionState.CONNECTING || state == ConnectionState.CONNECTED) {
80 Integer rssi = device.getRssi();
81 if (rssi != null && (newDelegate == null || rssi > newRssi)) {
86 BluetoothDevice oldDelegate = currentDelegateRef.getAndSet(newDelegate);
87 if (oldDelegate != newDelegate) { // using reference comparison is valid in this case
88 notifyListeners(BluetoothEventType.ADAPTER_CHANGED, getAdapter(newDelegate));
93 private BluetoothAdapter getAdapter(@Nullable BluetoothDevice delegate) {
94 if (delegate != null) {
95 return delegate.getAdapter();
97 // as a last resort we return our "actual" adapter
98 return super.getAdapter();
102 public BluetoothAdapter getAdapter() {
103 return getAdapter(currentDelegateRef.get());
106 private class Listener implements BluetoothDeviceListener {
108 private BluetoothDevice device;
110 public Listener(BluetoothDevice device) {
111 this.device = device;
115 public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
116 if (device == getDelegate()) {
117 notifyListeners(BluetoothEventType.SCAN_RECORD, scanNotification);
122 public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
123 if (device == getDelegate()) {
124 notifyListeners(BluetoothEventType.CONNECTION_STATE, connectionNotification);
129 public void onServicesDiscovered() {
130 device.getServices().forEach(RoamingBluetoothDevice.this::addService);
131 if (device == getDelegate()) {
132 notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
137 public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic,
138 BluetoothCompletionStatus status) {
139 if (device == getDelegate()) {
140 notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, status);
145 public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
146 BluetoothCompletionStatus status) {
147 if (device == getDelegate()) {
148 notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic);
153 public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
154 if (device == getDelegate()) {
155 notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
160 public void onDescriptorUpdate(BluetoothDescriptor bluetoothDescriptor) {
161 if (device == getDelegate()) {
162 notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, bluetoothDescriptor);
167 public void onAdapterChanged(BluetoothAdapter adapter) {
168 // do nothing since we are the ones that are supposed to trigger this