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