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.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 return !(discovery != null && "false".equalsIgnoreCase(discovery.toString()));
143 @SuppressWarnings("PMD.CompareObjectsWithEquals")
144 public void addBluetoothAdapter(BluetoothAdapter adapter) {
145 if (adapter == this) {
149 this.adapters.add(adapter);
151 if (isRoamingMember(adapter)) {
152 synchronized (devices) {
153 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
154 roamingDevice.addBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
159 if (getThing().getStatus() == ThingStatus.OFFLINE) {
165 @SuppressWarnings("PMD.CompareObjectsWithEquals")
166 public void removeBluetoothAdapter(BluetoothAdapter adapter) {
167 if (adapter == this) {
170 this.adapters.remove(adapter);
172 if (isRoamingMember(adapter)) {
173 synchronized (devices) {
174 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
175 roamingDevice.removeBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
180 if (getThing().getStatus() == ThingStatus.ONLINE) {
186 public void handleCommand(ChannelUID channelUID, Command command) {
190 public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
195 public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
200 public void scanStart() {
205 public void scanStop() {
210 public @Nullable BluetoothAddress getAddress() {
211 // roaming adapters don't have bluetooth addresses
216 public RoamingBluetoothDevice getDevice(BluetoothAddress address) {
217 // this will only get called by a bluetooth device handler
218 synchronized (devices) {
219 RoamingBluetoothDevice roamingDevice = Objects
220 .requireNonNull(devices.computeIfAbsent(address, addr -> new RoamingBluetoothDevice(this, addr)));
222 adapters.stream().filter(this::isRoamingMember)
223 .forEach(adapter -> roamingDevice.addBluetoothDevice(adapter.getDevice(address)));
225 return roamingDevice;
230 public boolean hasHandlerForDevice(BluetoothAddress address) {
231 String addrStr = address.toString();
233 * This type of search is inefficient and won't scale as the number of bluetooth Thing children increases on
234 * this bridge. But implementing a more efficient search would require a bit more overhead.
235 * Luckily though, it is reasonable to assume that the number of Thing children will remain small.
237 for (Thing childThing : getThing().getThings()) {
238 Object childAddr = childThing.getConfiguration().get(BluetoothBindingConstants.CONFIGURATION_ADDRESS);
239 if (addrStr.equals(childAddr)) {
240 return childThing.getHandler() != null;