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 device.removeListener(devices.remove(device));
63 protected Collection<BluetoothDeviceListener> getListeners() {
64 return eventListeners;
68 protected @Nullable BluetoothDevice getDelegate() {
69 BluetoothDevice newDelegate = null;
70 int newRssi = Integer.MIN_VALUE;
71 for (BluetoothDevice device : devices.keySet()) {
72 ConnectionState state = device.getConnectionState();
73 if (state == ConnectionState.CONNECTING || state == ConnectionState.CONNECTED) {
77 Integer rssi = device.getRssi();
78 if (rssi != null && (newDelegate == null || rssi > newRssi)) {
83 BluetoothDevice oldDelegate = currentDelegateRef.getAndSet(newDelegate);
84 if (oldDelegate != newDelegate) { // using reference comparison is valid in this case
85 notifyListeners(BluetoothEventType.ADAPTER_CHANGED, getAdapter(newDelegate));
90 private BluetoothAdapter getAdapter(@Nullable BluetoothDevice delegate) {
91 if (delegate != null) {
92 return delegate.getAdapter();
94 // as a last resort we return our "actual" adapter
95 return super.getAdapter();
99 public BluetoothAdapter getAdapter() {
100 return getAdapter(currentDelegateRef.get());
103 private class Listener implements BluetoothDeviceListener {
105 private BluetoothDevice device;
107 public Listener(BluetoothDevice device) {
108 this.device = device;
112 public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
113 if (device == getDelegate()) {
114 notifyListeners(BluetoothEventType.SCAN_RECORD, scanNotification);
119 public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
120 if (device == getDelegate()) {
121 notifyListeners(BluetoothEventType.CONNECTION_STATE, connectionNotification);
126 public void onServicesDiscovered() {
127 device.getServices().forEach(RoamingBluetoothDevice.this::addService);
128 if (device == getDelegate()) {
129 notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
134 public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic,
135 BluetoothCompletionStatus status) {
136 if (device == getDelegate()) {
137 notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, status);
142 public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
143 BluetoothCompletionStatus status) {
144 if (device == getDelegate()) {
145 notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic);
150 public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
151 if (device == getDelegate()) {
152 notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
157 public void onDescriptorUpdate(BluetoothDescriptor bluetoothDescriptor) {
158 if (device == getDelegate()) {
159 notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, bluetoothDescriptor);
164 public void onAdapterChanged(BluetoothAdapter adapter) {
165 // do nothing since we are the ones that are supposed to trigger this