]> git.basschouten.com Git - openhab-addons.git/blob
e0122ac082be7cb06b82bcfe73e4034f5a4d8655
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth.roaming.internal;
14
15 import java.util.HashMap;
16 import java.util.Map;
17 import java.util.Objects;
18 import java.util.Set;
19 import java.util.concurrent.CopyOnWriteArraySet;
20 import java.util.stream.Stream;
21
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;
36
37 /**
38  * The {@link RoamingBridgeHandler} is responsible for handling commands, which are
39  * sent to one of the channels.
40  *
41  * @author Connor Petty - Initial contribution
42  */
43 @NonNullByDefault
44 public class RoamingBridgeHandler extends BaseBridgeHandler implements RoamingBluetoothAdapter {
45
46     private final Set<BluetoothAdapter> adapters = new CopyOnWriteArraySet<>();
47
48     /*
49      * Note: this will only populate from handlers calling getDevice(BluetoothAddress), so we don't need
50      * to do periodic cleanup.
51      */
52     private Map<BluetoothAddress, RoamingBluetoothDevice> devices = new HashMap<>();
53     private ThingUID[] groupUIDs = new ThingUID[0];
54
55     public RoamingBridgeHandler(Bridge bridge) {
56         super(bridge);
57     }
58
59     @Override
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];
64         } else {
65             String groupIds = (String) value;
66             groupUIDs = Stream.of(groupIds.split(",")).map(ThingUID::new).toArray(ThingUID[]::new);
67         }
68
69         if (adapters.stream().map(BluetoothAdapter::getUID).anyMatch(this::isGroupMember)) {
70             updateStatus(ThingStatus.ONLINE);
71         } else {
72             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
73                     "No Physical Bluetooth adapters found");
74         }
75     }
76
77     private void updateStatus() {
78         if (adapters.stream().anyMatch(this::isRoamingMember)) {
79             updateStatus(ThingStatus.ONLINE);
80         } else {
81             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
82                     "No Physical Bluetooth adapters found");
83         }
84     }
85
86     @Override
87     public void dispose() {
88         // nothing that needs to be done here.
89         // Listener cleanup will be performed by the discovery participant anyway.
90     }
91
92     @Override
93     public ThingUID getUID() {
94         return getThing().getUID();
95     }
96
97     @Override
98     public @Nullable String getLocation() {
99         return getThing().getLocation();
100     }
101
102     @Override
103     public @Nullable String getLabel() {
104         return getThing().getLabel();
105     }
106
107     private boolean isRoamingMember(BluetoothAdapter adapter) {
108         return isRoamingMember(adapter.getUID());
109     }
110
111     @Override
112     public boolean isRoamingMember(ThingUID adapterUID) {
113         if (!isInitialized()) {
114             // an unitialized roaming adapter has no members
115             return false;
116         }
117         return isGroupMember(adapterUID);
118     }
119
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.
123             return true;
124         }
125         for (ThingUID uid : groupUIDs) {
126             if (adapterUID.equals(uid)) {
127                 return true;
128             }
129         }
130         return false;
131     }
132
133     @Override
134     public boolean isDiscoveryEnabled() {
135         if (getThing().getStatus() != ThingStatus.ONLINE) {
136             return false;
137         }
138         Object discovery = getConfig().get(BluetoothBindingConstants.CONFIGURATION_DISCOVERY);
139         return !(discovery != null && "false".equalsIgnoreCase(discovery.toString()));
140     }
141
142     @Override
143     @SuppressWarnings("PMD.CompareObjectsWithEquals")
144     public void addBluetoothAdapter(BluetoothAdapter adapter) {
145         if (adapter == this) {
146             return;
147         }
148
149         this.adapters.add(adapter);
150
151         if (isRoamingMember(adapter)) {
152             synchronized (devices) {
153                 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
154                     roamingDevice.addBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
155                 }
156             }
157         }
158
159         if (getThing().getStatus() == ThingStatus.OFFLINE) {
160             updateStatus();
161         }
162     }
163
164     @Override
165     @SuppressWarnings("PMD.CompareObjectsWithEquals")
166     public void removeBluetoothAdapter(BluetoothAdapter adapter) {
167         if (adapter == this) {
168             return;
169         }
170         this.adapters.remove(adapter);
171
172         if (isRoamingMember(adapter)) {
173             synchronized (devices) {
174                 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
175                     roamingDevice.removeBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
176                 }
177             }
178         }
179
180         if (getThing().getStatus() == ThingStatus.ONLINE) {
181             updateStatus();
182         }
183     }
184
185     @Override
186     public void handleCommand(ChannelUID channelUID, Command command) {
187     }
188
189     @Override
190     public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
191         // we don't use this
192     }
193
194     @Override
195     public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
196         // we don't use this
197     }
198
199     @Override
200     public void scanStart() {
201         // does nothing
202     }
203
204     @Override
205     public void scanStop() {
206         // does nothing
207     }
208
209     @Override
210     public @Nullable BluetoothAddress getAddress() {
211         // roaming adapters don't have bluetooth addresses
212         return null;
213     }
214
215     @Override
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)));
221
222             adapters.stream().filter(this::isRoamingMember)
223                     .forEach(adapter -> roamingDevice.addBluetoothDevice(adapter.getDevice(address)));
224
225             return roamingDevice;
226         }
227     }
228
229     @Override
230     public boolean hasHandlerForDevice(BluetoothAddress address) {
231         String addrStr = address.toString();
232         /*
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.
236          */
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;
241             }
242         }
243         return false;
244     }
245 }