]> git.basschouten.com Git - openhab-addons.git/blob
4172c4f3c8d7fec42c3b7a32ac90ff17eb0a901b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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     public void addBluetoothAdapter(BluetoothAdapter adapter) {
147         if (adapter == this) {
148             return;
149         }
150
151         this.adapters.add(adapter);
152
153         if (isRoamingMember(adapter)) {
154             synchronized (devices) {
155                 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
156                     roamingDevice.addBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
157                 }
158             }
159         }
160
161         if (getThing().getStatus() == ThingStatus.OFFLINE) {
162             updateStatus();
163         }
164     }
165
166     @Override
167     public void removeBluetoothAdapter(BluetoothAdapter adapter) {
168         if (adapter == this) {
169             return;
170         }
171         this.adapters.remove(adapter);
172
173         if (isRoamingMember(adapter)) {
174             synchronized (devices) {
175                 for (RoamingBluetoothDevice roamingDevice : devices.values()) {
176                     roamingDevice.removeBluetoothDevice(adapter.getDevice(roamingDevice.getAddress()));
177                 }
178             }
179         }
180
181         if (getThing().getStatus() == ThingStatus.ONLINE) {
182             updateStatus();
183         }
184     }
185
186     @Override
187     public void handleCommand(ChannelUID channelUID, Command command) {
188     }
189
190     @Override
191     public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
192         // we don't use this
193     }
194
195     @Override
196     public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
197         // we don't use this
198     }
199
200     @Override
201     public void scanStart() {
202         // does nothing
203     }
204
205     @Override
206     public void scanStop() {
207         // does nothing
208     }
209
210     @Override
211     public @Nullable BluetoothAddress getAddress() {
212         // roaming adapters don't have bluetooth addresses
213         return null;
214     }
215
216     @Override
217     public RoamingBluetoothDevice getDevice(BluetoothAddress address) {
218         // this will only get called by a bluetooth device handler
219         synchronized (devices) {
220             RoamingBluetoothDevice roamingDevice = Objects
221                     .requireNonNull(devices.computeIfAbsent(address, addr -> new RoamingBluetoothDevice(this, addr)));
222
223             adapters.stream().filter(this::isRoamingMember)
224                     .forEach(adapter -> roamingDevice.addBluetoothDevice(adapter.getDevice(address)));
225
226             return roamingDevice;
227         }
228     }
229
230     @Override
231     public boolean hasHandlerForDevice(BluetoothAddress address) {
232         String addrStr = address.toString();
233         /*
234          * This type of search is inefficient and won't scale as the number of bluetooth Thing children increases on
235          * this bridge. But implementing a more efficient search would require a bit more overhead.
236          * Luckily though, it is reasonable to assume that the number of Thing children will remain small.
237          */
238         for (Thing childThing : getThing().getThings()) {
239             Object childAddr = childThing.getConfiguration().get(BluetoothBindingConstants.CONFIGURATION_ADDRESS);
240             if (addrStr.equals(childAddr)) {
241                 return childThing.getHandler() != null;
242             }
243         }
244         return false;
245     }
246 }