]> git.basschouten.com Git - openhab-addons.git/blob
34a1d81ebc55d09988cb092725b44ffa65bf98b3
[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.bluez.handler;
14
15 import java.util.List;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
18
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;
29
30 import tinyb.BluetoothException;
31 import tinyb.BluetoothManager;
32
33 /**
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.
37  *
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
41  */
42 @NonNullByDefault
43 public class BlueZBridgeHandler extends AbstractBluetoothBridgeHandler<BlueZBluetoothDevice> {
44
45     private final Logger logger = LoggerFactory.getLogger(BlueZBridgeHandler.class);
46
47     private @NonNullByDefault({}) tinyb.BluetoothAdapter adapter;
48
49     // Our BT address
50     private @NonNullByDefault({}) BluetoothAddress adapterAddress;
51
52     private @NonNullByDefault({}) ScheduledFuture<?> discoveryJob;
53
54     /**
55      * Constructor
56      *
57      * @param bridge the bridge definition for this handler
58      */
59     public BlueZBridgeHandler(Bridge bridge) {
60         super(bridge);
61     }
62
63     @Override
64     public void initialize() {
65         super.initialize();
66         BluetoothManager manager;
67         try {
68             manager = BluetoothManager.getBluetoothManager();
69             if (manager == null) {
70                 throw new IllegalStateException("Received null BlueZ manager");
71             }
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.");
79             } else {
80                 throw new IllegalStateException("Cannot access BlueZ layer.", e);
81             }
82         }
83
84         final BlueZAdapterConfiguration configuration = getConfigAs(BlueZAdapterConfiguration.class);
85         if (configuration.address != null) {
86             adapterAddress = new BluetoothAddress(configuration.address);
87         } else {
88             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "address not set");
89             return;
90         }
91
92         logger.debug("Creating BlueZ adapter with address '{}'", adapterAddress);
93
94         for (tinyb.BluetoothAdapter adapter : manager.getAdapters()) {
95             if (adapter == null) {
96                 logger.warn("got null adapter from bluetooth manager");
97                 continue;
98             }
99             if (adapter.getAddress().equals(adapterAddress.toString())) {
100                 this.adapter = adapter;
101                 discoveryJob = scheduler.scheduleWithFixedDelay(this::refreshDevices, 0, 10, TimeUnit.SECONDS);
102                 return;
103             }
104         }
105         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No adapter for this address found.");
106     }
107
108     private void startDiscovery() {
109         // we need to make sure the adapter is powered first
110         if (!adapter.getPowered()) {
111             adapter.setPowered(true);
112         }
113         if (!adapter.getDiscovering()) {
114             adapter.setRssiDiscoveryFilter(-96);
115             adapter.startDiscovery();
116         }
117     }
118
119     private void refreshDevices() {
120         refreshTry: try {
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);
128             }
129             // For whatever reason, bluez will sometimes turn off scanning. So we just make sure it keeps running.
130             startDiscovery();
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
136                     break refreshTry;
137                 }
138                 int idx = message.lastIndexOf(':');
139                 if (idx != -1) {
140                     message = message.substring(idx).trim();
141                 }
142             }
143             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
144             return;
145         }
146         updateStatus(ThingStatus.ONLINE);
147     }
148
149     @Override
150     public @Nullable BluetoothAddress getAddress() {
151         return adapterAddress;
152     }
153
154     @Override
155     protected BlueZBluetoothDevice createDevice(BluetoothAddress address) {
156         BlueZBluetoothDevice device = new BlueZBluetoothDevice(this, address);
157         device.initialize();
158         return device;
159     }
160
161     @Override
162     public void dispose() {
163         if (discoveryJob != null) {
164             discoveryJob.cancel(true);
165             discoveryJob = null;
166         }
167         if (adapter != null && adapter.getDiscovering()) {
168             adapter.stopDiscovery();
169         }
170         super.dispose();
171     }
172 }