2 * Copyright (c) 2010-2022 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.HashMap;
17 import java.util.Objects;
19 import java.util.concurrent.CopyOnWriteArraySet;
20 import java.util.stream.Stream;
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.BluetoothBindingConstants;
27 import org.openhab.binding.bluetooth.BluetoothDiscoveryListener;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.ThingUID;
34 import org.openhab.core.thing.binding.BaseBridgeHandler;
35 import org.openhab.core.types.Command;
38 * The {@link RoamingBridgeHandler} is responsible for handling commands, which are
39 * sent to one of the channels.
41 * @author Connor Petty - Initial contribution
44 public class RoamingBridgeHandler extends BaseBridgeHandler implements RoamingBluetoothAdapter {
46 private final Set<BluetoothAdapter> adapters = new CopyOnWriteArraySet<>();
49 * Note: this will only populate from handlers calling getDevice(BluetoothAddress), so we don't need
50 * to do periodic cleanup.
52 private Map<BluetoothAddress, RoamingBluetoothDevice> devices = new HashMap<>();
53 private ThingUID[] groupUIDs = new ThingUID[0];
55 public RoamingBridgeHandler(Bridge bridge) {
60 public void initialize() {
61 Object value = getConfig().get(RoamingBindingConstants.CONFIGURATION_GROUP_ADAPTER_UIDS);
62 if (value == null || !(value instanceof String) || "".equals(value)) {
63 groupUIDs = new ThingUID[0];
65 String groupIds = (String) value;
66 groupUIDs = Stream.of(groupIds.split(",")).map(ThingUID::new).toArray(ThingUID[]::new);
69 if (adapters.stream().map(BluetoothAdapter::getUID).anyMatch(this::isGroupMember)) {
70 updateStatus(ThingStatus.ONLINE);
72 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
73 "No Physical Bluetooth adapters found");
77 private void updateStatus() {
78 if (adapters.stream().anyMatch(this::isRoamingMember)) {
79 updateStatus(ThingStatus.ONLINE);
81 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
82 "No Physical Bluetooth adapters found");
87 public void dispose() {
88 // nothing that needs to be done here.
89 // Listener cleanup will be performed by the discovery participant anyway.
93 public ThingUID getUID() {
94 return getThing().getUID();
98 public @Nullable String getLocation() {
99 return getThing().getLocation();
103 public @Nullable String getLabel() {
104 return getThing().getLabel();
107 private boolean isRoamingMember(BluetoothAdapter adapter) {
108 return isRoamingMember(adapter.getUID());
112 public boolean isRoamingMember(ThingUID adapterUID) {
113 if (!isInitialized()) {
114 // an unitialized roaming adapter has no members
117 return isGroupMember(adapterUID);
120 private boolean isGroupMember(ThingUID adapterUID) {
121 if (groupUIDs.length == 0) {
122 // if there are no members of the group then it is treated as all adapters are members.
125 for (ThingUID uid : groupUIDs) {
126 if (adapterUID.equals(uid)) {
134 public boolean isDiscoveryEnabled() {
135 if (getThing().getStatus() != ThingStatus.ONLINE) {
138 Object discovery = getConfig().get(BluetoothBindingConstants.CONFIGURATION_DISCOVERY);
139 if (discovery != null && discovery.toString().equalsIgnoreCase("false")) {
146 @SuppressWarnings("PMD.CompareObjectsWithEquals")
147 public void addBluetoothAdapter(BluetoothAdapter adapter) {
148 if (adapter == this) {
152 this.adapters.add(adapter);
154 if (isRoamingMember(adapter)) {
155 synchronized (devices) {
156 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
157 roamingDevice.addBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
162 if (getThing().getStatus() == ThingStatus.OFFLINE) {
168 @SuppressWarnings("PMD.CompareObjectsWithEquals")
169 public void removeBluetoothAdapter(BluetoothAdapter adapter) {
170 if (adapter == this) {
173 this.adapters.remove(adapter);
175 if (isRoamingMember(adapter)) {
176 synchronized (devices) {
177 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
178 roamingDevice.removeBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
183 if (getThing().getStatus() == ThingStatus.ONLINE) {
189 public void handleCommand(ChannelUID channelUID, Command command) {
193 public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
198 public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
203 public void scanStart() {
208 public void scanStop() {
213 public @Nullable BluetoothAddress getAddress() {
214 // roaming adapters don't have bluetooth addresses
219 public RoamingBluetoothDevice getDevice(BluetoothAddress address) {
220 // this will only get called by a bluetooth device handler
221 synchronized (devices) {
222 RoamingBluetoothDevice roamingDevice = Objects
223 .requireNonNull(devices.computeIfAbsent(address, addr -> new RoamingBluetoothDevice(this, addr)));
225 adapters.stream().filter(this::isRoamingMember)
226 .forEach(adapter -> roamingDevice.addBluetoothDevice(adapter.getDevice(address)));
228 return roamingDevice;
233 public boolean hasHandlerForDevice(BluetoothAddress address) {
234 String addrStr = address.toString();
236 * This type of search is inefficient and won't scale as the number of bluetooth Thing children increases on
237 * this bridge. But implementing a more efficient search would require a bit more overhead.
238 * Luckily though, it is reasonable to assume that the number of Thing children will remain small.
240 for (Thing childThing : getThing().getThings()) {
241 Object childAddr = childThing.getConfiguration().get(BluetoothBindingConstants.CONFIGURATION_ADDRESS);
242 if (addrStr.equals(childAddr)) {
243 return childThing.getHandler() != null;