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.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.BluetoothCompletionStatus;
29 import org.openhab.binding.bluetooth.BluetoothDescriptor;
30 import org.openhab.binding.bluetooth.BluetoothDevice;
31 import org.openhab.binding.bluetooth.BluetoothDeviceListener;
32 import org.openhab.binding.bluetooth.DelegateBluetoothDevice;
33 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
34 import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
37 * The {@link RoamingBluetoothDevice} acts as a roaming device by delegating
38 * its operations to actual adapters.
40 * @author Connor Petty - Initial contribution
43 public class RoamingBluetoothDevice extends DelegateBluetoothDevice {
45 private final Map<BluetoothDevice, Listener> devices = new ConcurrentHashMap<>();
47 private final List<BluetoothDeviceListener> eventListeners = new CopyOnWriteArrayList<>();
49 private final AtomicReference<@Nullable BluetoothDevice> currentDelegateRef = new AtomicReference<>();
51 protected RoamingBluetoothDevice(RoamingBridgeHandler roamingAdapter, BluetoothAddress address) {
52 super(roamingAdapter, address);
55 public void addBluetoothDevice(BluetoothDevice device) {
56 device.addListener(Objects.requireNonNull(devices.computeIfAbsent(device, Listener::new)));
59 public void removeBluetoothDevice(BluetoothDevice device) {
60 BluetoothDeviceListener listener = devices.remove(device);
61 if (listener != null) {
62 device.removeListener(listener);
67 protected Collection<BluetoothDeviceListener> getListeners() {
68 return eventListeners;
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 private class Listener implements BluetoothDeviceListener {
109 private BluetoothDevice device;
111 public Listener(BluetoothDevice device) {
112 this.device = device;
116 public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
117 if (device == getDelegate()) {
118 notifyListeners(BluetoothEventType.SCAN_RECORD, scanNotification);
123 public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
124 if (device == getDelegate()) {
125 notifyListeners(BluetoothEventType.CONNECTION_STATE, connectionNotification);
130 public void onServicesDiscovered() {
131 device.getServices().forEach(RoamingBluetoothDevice.this::addService);
132 if (device == getDelegate()) {
133 notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
138 public void onCharacteristicReadComplete(BluetoothCharacteristic characteristic,
139 BluetoothCompletionStatus status) {
140 if (device == getDelegate()) {
141 notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, status);
146 public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
147 BluetoothCompletionStatus status) {
148 if (device == getDelegate()) {
149 notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic);
154 public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
155 if (device == getDelegate()) {
156 notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
161 public void onDescriptorUpdate(BluetoothDescriptor bluetoothDescriptor) {
162 if (device == getDelegate()) {
163 notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, bluetoothDescriptor);
168 public void onAdapterChanged(BluetoothAdapter adapter) {
169 // do nothing since we are the ones that are supposed to trigger this