2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bluetooth.bluez.internal;
15 import java.util.HashMap;
16 import java.util.List;
18 import java.util.concurrent.Future;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
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;
41 import com.github.hypfvieh.bluetooth.wrapper.BluetoothAdapter;
42 import com.github.hypfvieh.bluetooth.wrapper.BluetoothDevice;
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.
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
57 public class BlueZBridgeHandler extends AbstractBluetoothBridgeHandler<BlueZBluetoothDevice>
58 implements BlueZEventListener {
60 private final Logger logger = LoggerFactory.getLogger(BlueZBridgeHandler.class);
62 // ADAPTER from BlueZ-DBus Library
63 private @Nullable BluetoothAdapter adapter;
66 private @Nullable BluetoothAddress adapterAddress;
68 private @Nullable ScheduledFuture<?> discoveryJob;
70 private final DeviceManagerFactory deviceManagerFactory;
75 * @param bridge the bridge definition for this handler
77 public BlueZBridgeHandler(Bridge bridge, DeviceManagerFactory deviceManagerFactory) {
79 this.deviceManagerFactory = deviceManagerFactory;
83 public void initialize() {
87 final BlueZAdapterConfiguration configuration = getConfigAs(BlueZAdapterConfiguration.class);
88 String addr = configuration.address;
90 this.adapterAddress = new BluetoothAddress(addr.toUpperCase());
92 // If configuration does not contain adapter address to use, exit with error.
93 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "address not set");
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);
104 public void dispose() {
105 deviceManagerFactory.getPropertiesChangedHandler().removeListener(this);
106 logger.debug("Termination of DBus BlueZ handler");
108 Future<?> job = discoveryJob;
114 BluetoothAdapter localAdatper = this.adapter;
115 if (localAdatper != null) {
116 localAdatper.stopDiscovery();
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);
131 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No adapter address provided");
135 if (localAdapter == null) {
136 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
137 "Native adapter could not be found for address '" + adapterAddress + "'");
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...");
149 Map<String, Variant<?>> filter = new HashMap<>();
150 filter.put("DuplicateData", new Variant<>(true));
152 adapter.setDiscoveryFilter(filter);
153 } catch (BluezInvalidArgumentsException | BluezFailedException | BluezNotSupportedException
154 | BluezNotReadyException e) {
155 throw new RuntimeException(e);
158 // now lets make sure that discovery is turned on
159 if (!localAdapter.startDiscovery()) {
160 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Trying to start discovery");
166 private void initializeAndRefreshDevices() {
167 logger.debug("initializeAndRefreshDevice()");
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.");
178 BluetoothAdapter adapter = prepareAdapter(deviceManager);
179 if (adapter == null) {
180 // adapter isn't prepared yet
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..
192 BlueZBluetoothDevice device = getDevice(new BluetoothAddress(bluezDevice.getAddress()));
193 device.updateBlueZDevice(bluezDevice);
194 deviceDiscovered(device);
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());
206 public @Nullable BluetoothAddress getAddress() {
207 return adapterAddress;
211 protected BlueZBluetoothDevice createDevice(BluetoothAddress address) {
212 logger.debug("createDevice {}", address);
213 return new BlueZBluetoothDevice(this, address);
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
224 String localName = localAdapter.getDeviceName();
226 if (!adapterName.equals(localName)) {
227 // does not concern this adapter
231 BluetoothAddress address = event.getDevice();
233 if (address != null) {
234 // now lets forward the event to the corresponding bluetooth device
235 BlueZBluetoothDevice device = getDevice(address);
236 event.dispatch(device);
241 public void onDiscoveringChanged(AdapterDiscoveringChangedEvent event) {
242 // do nothing for now
246 public void onPoweredChange(AdapterPoweredChangedEvent event) {
247 // do nothing for now