]> git.basschouten.com Git - openhab-addons.git/blob
25adffdc92c003bb2d9e645abb4bea4b6a02c5b6
[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.bluez.internal;
14
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.concurrent.Future;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.bluez.exceptions.BluezFailedException;
23 import org.bluez.exceptions.BluezInvalidArgumentsException;
24 import org.bluez.exceptions.BluezNotReadyException;
25 import org.bluez.exceptions.BluezNotSupportedException;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.freedesktop.dbus.types.Variant;
29 import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
30 import org.openhab.binding.bluetooth.BluetoothAddress;
31 import org.openhab.binding.bluetooth.bluez.internal.events.AdapterDiscoveringChangedEvent;
32 import org.openhab.binding.bluetooth.bluez.internal.events.AdapterPoweredChangedEvent;
33 import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEvent;
34 import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEventListener;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import com.github.hypfvieh.bluetooth.wrapper.BluetoothAdapter;
42 import com.github.hypfvieh.bluetooth.wrapper.BluetoothDevice;
43
44 /**
45  * The {@link BlueZBridgeHandler} is responsible for talking to the BlueZ stack, using DBus Unix Socket.
46  * This Binding does not use any JNI.
47  * It provides a private interface for {@link BlueZBluetoothDevice}s to access the stack and provides top
48  * level adaptor functionality for scanning and arbitration.
49  *
50  * @author Kai Kreuzer - Initial contribution and API
51  * @author Hilbrand Bouwkamp - Simplified calling scan and better handling manual scanning
52  * @author Connor Petty - Simplified device scan logic
53  * @author Benjamin Lafois - Replaced tinyB with bluezDbus
54  *
55  */
56 @NonNullByDefault
57 public class BlueZBridgeHandler extends AbstractBluetoothBridgeHandler<BlueZBluetoothDevice>
58         implements BlueZEventListener {
59
60     private final Logger logger = LoggerFactory.getLogger(BlueZBridgeHandler.class);
61
62     // ADAPTER from BlueZ-DBus Library
63     private @Nullable BluetoothAdapter adapter;
64
65     // Our BT address
66     private @Nullable BluetoothAddress adapterAddress;
67
68     private @Nullable ScheduledFuture<?> discoveryJob;
69
70     private final DeviceManagerFactory deviceManagerFactory;
71
72     /**
73      * Constructor
74      *
75      * @param bridge the bridge definition for this handler
76      */
77     public BlueZBridgeHandler(Bridge bridge, DeviceManagerFactory deviceManagerFactory) {
78         super(bridge);
79         this.deviceManagerFactory = deviceManagerFactory;
80     }
81
82     @Override
83     public void initialize() {
84         super.initialize();
85
86         // Load configuration
87         final BlueZAdapterConfiguration configuration = getConfigAs(BlueZAdapterConfiguration.class);
88         String addr = configuration.address;
89         if (addr != null) {
90             this.adapterAddress = new BluetoothAddress(addr.toUpperCase());
91         } else {
92             // If configuration does not contain adapter address to use, exit with error.
93             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "address not set");
94             return;
95         }
96
97         logger.debug("Creating BlueZ adapter with address '{}'", adapterAddress);
98         updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Initializing");
99         deviceManagerFactory.getPropertiesChangedHandler().addListener(this);
100         discoveryJob = scheduler.scheduleWithFixedDelay(this::initializeAndRefreshDevices, 5, 10, TimeUnit.SECONDS);
101     }
102
103     @Override
104     public void dispose() {
105         deviceManagerFactory.getPropertiesChangedHandler().removeListener(this);
106         logger.debug("Termination of DBus BlueZ handler");
107
108         Future<?> job = discoveryJob;
109         if (job != null) {
110             job.cancel(false);
111             discoveryJob = null;
112         }
113
114         BluetoothAdapter localAdatper = this.adapter;
115         if (localAdatper != null) {
116             localAdatper.stopDiscovery();
117             this.adapter = null;
118         }
119
120         super.dispose();
121     }
122
123     private @Nullable BluetoothAdapter prepareAdapter(DeviceManagerWrapper deviceManager) {
124         // next lets check if we can find our adapter in the manager.
125         BluetoothAdapter localAdapter = adapter;
126         if (localAdapter == null) {
127             BluetoothAddress localAddress = adapterAddress;
128             if (localAddress != null) {
129                 localAdapter = adapter = deviceManager.getAdapter(localAddress);
130             } else {
131                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No adapter address provided");
132                 return null;
133             }
134         }
135         if (localAdapter == null) {
136             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
137                     "Native adapter could not be found for address '" + adapterAddress + "'");
138             return null;
139         }
140         // now lets confirm that the adapter is powered
141         if (!localAdapter.isPowered()) {
142             localAdapter.setPowered(true);
143             // give the device some time to power on
144             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
145                     "Adapter is not powered, attempting to turn on...");
146             return null;
147         }
148
149         Map<String, Variant<?>> filter = new HashMap<>();
150         filter.put("DuplicateData", new Variant<>(true));
151         try {
152             adapter.setDiscoveryFilter(filter);
153         } catch (BluezInvalidArgumentsException | BluezFailedException | BluezNotSupportedException
154                 | BluezNotReadyException e) {
155             throw new RuntimeException(e);
156         }
157
158         // now lets make sure that discovery is turned on
159         if (!localAdapter.startDiscovery()) {
160             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Trying to start discovery");
161             return null;
162         }
163         return localAdapter;
164     }
165
166     private void initializeAndRefreshDevices() {
167         logger.debug("initializeAndRefreshDevice()");
168
169         try {
170             // first check if the device manager is ready
171             DeviceManagerWrapper deviceManager = deviceManagerFactory.getDeviceManager();
172             if (deviceManager == null) {
173                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
174                         "Bluez DeviceManager not available yet.");
175                 return;
176             }
177
178             BluetoothAdapter adapter = prepareAdapter(deviceManager);
179             if (adapter == null) {
180                 // adapter isn't prepared yet
181                 return;
182             }
183
184             // now lets refresh devices
185             List<BluetoothDevice> bluezDevices = deviceManager.getDevices(adapter);
186             logger.debug("Found {} Bluetooth devices.", bluezDevices.size());
187             for (BluetoothDevice bluezDevice : bluezDevices) {
188                 if (bluezDevice.getAddress() == null) {
189                     // For some reasons, sometimes the address is null..
190                     continue;
191                 }
192                 BlueZBluetoothDevice device = getDevice(new BluetoothAddress(bluezDevice.getAddress()));
193                 device.updateBlueZDevice(bluezDevice);
194                 deviceDiscovered(device);
195             }
196             updateStatus(ThingStatus.ONLINE);
197         } catch (Exception ex) {
198             // don't know what kind of exception the bluez library might throw at us so lets catch them here so our
199             // scheduler loop doesn't get terminated
200             logger.warn("Unknown exception", ex);
201             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
202         }
203     }
204
205     @Override
206     public @Nullable BluetoothAddress getAddress() {
207         return adapterAddress;
208     }
209
210     @Override
211     protected BlueZBluetoothDevice createDevice(BluetoothAddress address) {
212         logger.debug("createDevice {}", address);
213         return new BlueZBluetoothDevice(this, address);
214     }
215
216     @Override
217     public void onDBusBlueZEvent(BlueZEvent event) {
218         BluetoothAdapter localAdapter = this.adapter;
219         String adapterName = event.getAdapterName();
220         if (adapterName == null || localAdapter == null) {
221             // We cannot be sure that this event concerns this adapter.. So ignore message
222             return;
223         }
224         String localName = localAdapter.getDeviceName();
225
226         if (!adapterName.equals(localName)) {
227             // does not concern this adapter
228             return;
229         }
230
231         BluetoothAddress address = event.getDevice();
232
233         if (address != null) {
234             // now lets forward the event to the corresponding bluetooth device
235             BlueZBluetoothDevice device = getDevice(address);
236             event.dispatch(device);
237         }
238     }
239
240     @Override
241     public void onDiscoveringChanged(AdapterDiscoveringChangedEvent event) {
242         // do nothing for now
243     }
244
245     @Override
246     public void onPoweredChange(AdapterPoweredChangedEvent event) {
247         // do nothing for now
248     }
249 }