]> git.basschouten.com Git - openhab-addons.git/blob
b1bacfbf431cd03fc41292c06eb7272e92365b9a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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         if (discovery != null && discovery.toString().equalsIgnoreCase("false")) {
140             return false;
141         }
142         return true;
143     }
144
145     @Override
146     @SuppressWarnings("PMD.CompareObjectsWithEquals")
147     public void addBluetoothAdapter(BluetoothAdapter adapter) {
148         if (adapter == this) {
149             return;
150         }
151
152         this.adapters.add(adapter);
153
154         if (isRoamingMember(adapter)) {
155             synchronized (devices) {
156                 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
157                     roamingDevice.addBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
158                 }
159             }
160         }
161
162         if (getThing().getStatus() == ThingStatus.OFFLINE) {
163             updateStatus();
164         }
165     }
166
167     @Override
168     @SuppressWarnings("PMD.CompareObjectsWithEquals")
169     public void removeBluetoothAdapter(BluetoothAdapter adapter) {
170         if (adapter == this) {
171             return;
172         }
173         this.adapters.remove(adapter);
174
175         if (isRoamingMember(adapter)) {
176             synchronized (devices) {
177                 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
178                     roamingDevice.removeBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
179                 }
180             }
181         }
182
183         if (getThing().getStatus() == ThingStatus.ONLINE) {
184             updateStatus();
185         }
186     }
187
188     @Override
189     public void handleCommand(ChannelUID channelUID, Command command) {
190     }
191
192     @Override
193     public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
194         // we don't use this
195     }
196
197     @Override
198     public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
199         // we don't use this
200     }
201
202     @Override
203     public void scanStart() {
204         // does nothing
205     }
206
207     @Override
208     public void scanStop() {
209         // does nothing
210     }
211
212     @Override
213     public @Nullable BluetoothAddress getAddress() {
214         // roaming adapters don't have bluetooth addresses
215         return null;
216     }
217
218     @Override
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)));
224
225             adapters.stream().filter(this::isRoamingMember)
226                     .forEach(adapter -> roamingDevice.addBluetoothDevice(adapter.getDevice(address)));
227
228             return roamingDevice;
229         }
230     }
231
232     @Override
233     public boolean hasHandlerForDevice(BluetoothAddress address) {
234         String addrStr = address.toString();
235         /*
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.
239          */
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;
244             }
245         }
246         return false;
247     }
248 }