2 * Copyright (c) 2010-2020 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.handler;
15 import java.util.List;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
22 import org.openhab.binding.bluetooth.BluetoothAddress;
23 import org.openhab.binding.bluetooth.bluez.BlueZBluetoothDevice;
24 import org.openhab.core.thing.Bridge;
25 import org.openhab.core.thing.ThingStatus;
26 import org.openhab.core.thing.ThingStatusDetail;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import tinyb.BluetoothException;
31 import tinyb.BluetoothManager;
34 * The {@link BlueZBridgeHandler} is responsible for talking to the BlueZ stack.
35 * It provides a private interface for {@link BlueZBluetoothDevice}s to access the stack and provides top
36 * level adaptor functionality for scanning and arbitration.
38 * @author Kai Kreuzer - Initial contribution and API
39 * @author Hilbrand Bouwkamp - Simplified calling scan and better handling manual scanning
40 * @author Connor Petty - Simplified device scan logic
43 public class BlueZBridgeHandler extends AbstractBluetoothBridgeHandler<BlueZBluetoothDevice> {
45 private final Logger logger = LoggerFactory.getLogger(BlueZBridgeHandler.class);
47 private @NonNullByDefault({}) tinyb.BluetoothAdapter adapter;
50 private @NonNullByDefault({}) BluetoothAddress adapterAddress;
52 private @NonNullByDefault({}) ScheduledFuture<?> discoveryJob;
57 * @param bridge the bridge definition for this handler
59 public BlueZBridgeHandler(Bridge bridge) {
64 public void initialize() {
66 BluetoothManager manager;
68 manager = BluetoothManager.getBluetoothManager();
69 if (manager == null) {
70 throw new IllegalStateException("Received null BlueZ manager");
72 } catch (UnsatisfiedLinkError e) {
73 throw new IllegalStateException("BlueZ JNI connection cannot be established.", e);
74 } catch (RuntimeException e) {
75 // we do not get anything more specific from TinyB here
76 if (e.getMessage() != null && e.getMessage().contains("AccessDenied")) {
77 throw new IllegalStateException(
78 "Cannot access BlueZ stack due to permission problems. Make sure that your OS user is part of the 'bluetooth' group of BlueZ.");
80 throw new IllegalStateException("Cannot access BlueZ layer.", e);
84 final BlueZAdapterConfiguration configuration = getConfigAs(BlueZAdapterConfiguration.class);
85 if (configuration.address != null) {
86 adapterAddress = new BluetoothAddress(configuration.address);
88 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "address not set");
92 logger.debug("Creating BlueZ adapter with address '{}'", adapterAddress);
94 for (tinyb.BluetoothAdapter adapter : manager.getAdapters()) {
95 if (adapter == null) {
96 logger.warn("got null adapter from bluetooth manager");
99 if (adapter.getAddress().equals(adapterAddress.toString())) {
100 this.adapter = adapter;
101 discoveryJob = scheduler.scheduleWithFixedDelay(this::refreshDevices, 0, 10, TimeUnit.SECONDS);
105 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No adapter for this address found.");
108 private void startDiscovery() {
109 // we need to make sure the adapter is powered first
110 if (!adapter.getPowered()) {
111 adapter.setPowered(true);
113 if (!adapter.getDiscovering()) {
114 adapter.setRssiDiscoveryFilter(-96);
115 adapter.startDiscovery();
119 private void refreshDevices() {
121 logger.debug("Refreshing Bluetooth device list...");
122 List<tinyb.BluetoothDevice> tinybDevices = adapter.getDevices();
123 logger.debug("Found {} Bluetooth devices.", tinybDevices.size());
124 for (tinyb.BluetoothDevice tinybDevice : tinybDevices) {
125 BlueZBluetoothDevice device = getDevice(new BluetoothAddress(tinybDevice.getAddress()));
126 device.updateTinybDevice(tinybDevice);
127 deviceDiscovered(device);
129 // For whatever reason, bluez will sometimes turn off scanning. So we just make sure it keeps running.
131 } catch (BluetoothException ex) {
132 String message = ex.getMessage();
133 if (message != null) {
134 if (message.contains("Operation already in progress")) {
135 // we shouldn't go offline in this case
138 int idx = message.lastIndexOf(':');
140 message = message.substring(idx).trim();
143 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
146 updateStatus(ThingStatus.ONLINE);
150 public @Nullable BluetoothAddress getAddress() {
151 return adapterAddress;
155 protected BlueZBluetoothDevice createDevice(BluetoothAddress address) {
156 BlueZBluetoothDevice device = new BlueZBluetoothDevice(this, address);
162 public void dispose() {
163 if (discoveryJob != null) {
164 discoveryJob.cancel(true);
167 if (adapter != null && adapter.getDiscovering()) {
168 adapter.stopDiscovery();