diff --git a/bundles/org.openhab.binding.bluetooth.bluez/NOTICE b/bundles/org.openhab.binding.bluetooth.bluez/NOTICE
index 9de213699..0f7ef793e 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/NOTICE
+++ b/bundles/org.openhab.binding.bluetooth.bluez/NOTICE
@@ -14,33 +14,33 @@ https://github.com/openhab/openhab-addons
== Third-party Content
-TinyB Version: 0.5.1
+BlueZ-DBus Version: 0.1.3
* License: MIT License
-* Project: https://github.com/intel-iot-devkit/tinyb
-* Source: https://github.com/intel-iot-devkit/tinyb/tree/v0.5.1
+* Project: https://github.com/hypfvieh/bluez-dbus
+* Source: https://github.com/hypfvieh/bluez-dbus
== Third-party license(s)
=== MIT License
-The MIT License (MIT)
-Copyright © 2015-2016 Intel Corporation
+MIT License
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
+Copyright (c) 2017 David M.
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/README.md b/bundles/org.openhab.binding.bluetooth.bluez/README.md
index 5135e9075..4d7124cc2 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/README.md
+++ b/bundles/org.openhab.binding.bluetooth.bluez/README.md
@@ -1,6 +1,6 @@
# Bluetooth BlueZ Adapter
-This extension supports Bluetooth access via BlueZ on Linux (ARMv6hf).
+This extension supports Bluetooth access via BlueZ and DBus on Linux. This is architecture agnostic and uses Unix Sockets.
# Setup
@@ -44,14 +44,15 @@ It defines the following bridge type:
|----------------|---------------------------------------------------------------------------|
| bluez | A Bluetooth adapter that is supported by BlueZ |
-
## Discovery
If BlueZ is enabled and can be accessed, all available adapters are automatically discovered.
+
## Bridge Configuration
The bluez bridge requires the configuration parameter `address`, which corresponds to the Bluetooth address of the adapter (in format "XX:XX:XX:XX:XX:XX").
+
Additionally, the parameter `backgroundDiscovery` can be set to true/false.When set to true, any Bluetooth device of which broadcasts are received is added to the Inbox.
## Example
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/pom.xml b/bundles/org.openhab.binding.bluetooth.bluez/pom.xml
index 152438e69..f25de595b 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/pom.xml
+++ b/bundles/org.openhab.binding.bluetooth.bluez/pom.xml
@@ -15,18 +15,21 @@
openHAB Add-ons :: Bundles :: BlueZ Bluetooth Adapter
+
org.openhab.addons.bundles
org.openhab.binding.bluetooth
${project.version}
provided
+
- org.openhab.osgiify
- intel-iot-devkit.tinyb
- 0.5.1
- compile
+ com.github.hypfvieh
+ bluez-dbus-osgi
+ 0.1.3
+ provided
+
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/feature/feature.xml b/bundles/org.openhab.binding.bluetooth.bluez/src/main/feature/feature.xml
index 1119f1727..32d9d2766 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/feature/feature.xml
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/feature/feature.xml
@@ -4,7 +4,8 @@
openhab-runtime-base
- openhab-transport-serial
+
+ mvn:com.github.hypfvieh/bluez-dbus-osgi/0.1.3
mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth/${project.version}
mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.bluez/${project.version}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZBluetoothDevice.java
deleted file mode 100644
index 0f099bde4..000000000
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZBluetoothDevice.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.bluetooth.bluez;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import org.openhab.binding.bluetooth.BaseBluetoothDevice;
-import org.openhab.binding.bluetooth.BluetoothAddress;
-import org.openhab.binding.bluetooth.BluetoothCharacteristic;
-import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
-import org.openhab.binding.bluetooth.BluetoothDescriptor;
-import org.openhab.binding.bluetooth.BluetoothService;
-import org.openhab.binding.bluetooth.bluez.handler.BlueZBridgeHandler;
-import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
-import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
-import org.openhab.core.common.ThreadPoolManager;
-import org.openhab.core.util.HexUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import tinyb.BluetoothException;
-import tinyb.BluetoothGattCharacteristic;
-import tinyb.BluetoothGattDescriptor;
-import tinyb.BluetoothGattService;
-
-/**
- * Implementation of BluetoothDevice for BlueZ via TinyB
- *
- * @author Kai Kreuzer - Initial contribution and API
- *
- */
-public class BlueZBluetoothDevice extends BaseBluetoothDevice {
-
- private tinyb.BluetoothDevice device;
-
- private final Logger logger = LoggerFactory.getLogger(BlueZBluetoothDevice.class);
-
- private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
-
- /**
- * Constructor
- *
- * @param adapter the bridge handler through which this device is connected
- * @param address the Bluetooth address of the device
- * @param name the name of the device
- */
- public BlueZBluetoothDevice(BlueZBridgeHandler adapter, BluetoothAddress address) {
- super(adapter, address);
- logger.debug("Creating BlueZ device with address '{}'", address);
- }
-
- /**
- * Initializes a newly created instance of this class.
- * This method should always be called directly after creating a new object instance.
- */
- public void initialize() {
- updateLastSeenTime();
- }
-
- /**
- * Updates the internally used tinyB device instance. It replaces any previous instance, disables notifications on
- * it and enables notifications on the new instance.
- *
- * @param tinybDevice the new device instance to use for communication
- */
- public synchronized void updateTinybDevice(tinyb.BluetoothDevice tinybDevice) {
- if (Objects.equals(device, tinybDevice)) {
- return;
- }
-
- if (device != null) {
- // we need to replace the instance - let's deactivate notifications on the old one
- disableNotifications();
- }
- this.device = tinybDevice;
-
- if (this.device == null) {
- return;
- }
- updateLastSeenTime();
-
- this.name = device.getName();
- this.rssi = (int) device.getRSSI();
- this.txPower = (int) device.getTxPower();
-
- device.getManufacturerData().entrySet().stream().map(Map.Entry::getKey).filter(Objects::nonNull).findFirst()
- .ifPresent(manufacturerId ->
- // Convert to unsigned int to match the convention in BluetoothCompanyIdentifiers
- this.manufacturer = manufacturerId & 0xFFFF);
-
- if (device.getConnected()) {
- this.connectionState = ConnectionState.CONNECTED;
- }
-
- enableNotifications();
- refreshServices();
- }
-
- private void enableNotifications() {
- logger.debug("Enabling notifications for device '{}'", device.getAddress());
- device.enableRSSINotifications(n -> {
- updateLastSeenTime();
- rssi = (int) n;
- BluetoothScanNotification notification = new BluetoothScanNotification();
- notification.setRssi(n);
- notifyListeners(BluetoothEventType.SCAN_RECORD, notification);
- });
- device.enableManufacturerDataNotifications(n -> {
- updateLastSeenTime();
- for (Map.Entry entry : n.entrySet()) {
- BluetoothScanNotification notification = new BluetoothScanNotification();
- byte[] data = new byte[entry.getValue().length + 2];
- data[0] = (byte) (entry.getKey() & 0xFF);
- data[1] = (byte) (entry.getKey() >>> 8);
- System.arraycopy(entry.getValue(), 0, data, 2, entry.getValue().length);
- if (logger.isDebugEnabled()) {
- logger.debug("Received manufacturer data for '{}': {}", address, HexUtils.bytesToHex(data, " "));
- }
- notification.setManufacturerData(data);
- notifyListeners(BluetoothEventType.SCAN_RECORD, notification);
- }
- });
- device.enableConnectedNotifications(connected -> {
- updateLastSeenTime();
- connectionState = connected ? ConnectionState.CONNECTED : ConnectionState.DISCONNECTED;
- logger.debug("Connection state of '{}' changed to {}", address, connectionState);
- notifyListeners(BluetoothEventType.CONNECTION_STATE,
- new BluetoothConnectionStatusNotification(connectionState));
- });
- device.enableServicesResolvedNotifications(resolved -> {
- updateLastSeenTime();
- logger.debug("Received services resolved event for '{}': {}", address, resolved);
- if (resolved) {
- refreshServices();
- notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
- }
- });
- device.enableServiceDataNotifications(data -> {
- updateLastSeenTime();
- if (logger.isDebugEnabled()) {
- logger.debug("Received service data for '{}':", address);
- for (Map.Entry entry : data.entrySet()) {
- logger.debug("{} : {}", entry.getKey(), HexUtils.bytesToHex(entry.getValue(), " "));
- }
- }
- });
- }
-
- private void disableNotifications() {
- logger.debug("Disabling notifications for device '{}'", device.getAddress());
- device.disableBlockedNotifications();
- device.disableManufacturerDataNotifications();
- device.disablePairedNotifications();
- device.disableRSSINotifications();
- device.disableServiceDataNotifications();
- device.disableTrustedNotifications();
- }
-
- protected void refreshServices() {
- if (device.getServices().size() > getServices().size()) {
- for (BluetoothGattService tinybService : device.getServices()) {
- BluetoothService service = new BluetoothService(UUID.fromString(tinybService.getUUID()),
- tinybService.getPrimary());
- for (BluetoothGattCharacteristic tinybCharacteristic : tinybService.getCharacteristics()) {
- BluetoothCharacteristic characteristic = new BluetoothCharacteristic(
- UUID.fromString(tinybCharacteristic.getUUID()), 0);
- for (BluetoothGattDescriptor tinybDescriptor : tinybCharacteristic.getDescriptors()) {
- BluetoothDescriptor descriptor = new BluetoothDescriptor(characteristic,
- UUID.fromString(tinybDescriptor.getUUID()));
- characteristic.addDescriptor(descriptor);
- }
- service.addCharacteristic(characteristic);
- }
- addService(service);
- }
- notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
- }
- }
-
- @Override
- public boolean connect() {
- if (device != null && !device.getConnected()) {
- try {
- return device.connect();
- } catch (BluetoothException e) {
- if ("Timeout was reached".equals(e.getMessage())) {
- notifyListeners(BluetoothEventType.CONNECTION_STATE,
- new BluetoothConnectionStatusNotification(ConnectionState.DISCONNECTED));
- } else if (e.getMessage() != null && e.getMessage().contains("Protocol not available")) {
- // this device does not seem to be connectable at all - let's log a warning and ignore it.
- logger.warn("Bluetooth device '{}' does not allow a connection.", device.getAddress());
- } else {
- logger.debug("Exception occurred when trying to connect device '{}': {}", device.getAddress(),
- e.getMessage());
- }
- }
- }
- return false;
- }
-
- @Override
- public boolean disconnect() {
- if (device != null && device.getConnected()) {
- logger.debug("Disconnecting '{}'", address);
- try {
- return device.disconnect();
- } catch (BluetoothException e) {
- logger.debug("Exception occurred when trying to disconnect device '{}': {}", device.getAddress(),
- e.getMessage());
- }
- }
- return false;
- }
-
- @Override
- public boolean discoverServices() {
- return false;
- }
-
- private void ensureConnected() {
- if (device == null || !device.getConnected()) {
- throw new IllegalStateException("TinyB device is not set or not connected");
- }
- }
-
- @Override
- public boolean readCharacteristic(BluetoothCharacteristic characteristic) {
- ensureConnected();
-
- BluetoothGattCharacteristic c = getTinybCharacteristicByUUID(characteristic.getUuid().toString());
- if (c == null) {
- logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
- return false;
- }
- scheduler.submit(() -> {
- try {
- byte[] value = c.readValue();
- characteristic.setValue(value);
- notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
- BluetoothCompletionStatus.SUCCESS);
- } catch (BluetoothException e) {
- logger.debug("Exception occurred when trying to read characteristic '{}': {}", characteristic.getUuid(),
- e.getMessage());
- notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
- BluetoothCompletionStatus.ERROR);
- }
- });
- return true;
- }
-
- @Override
- public boolean writeCharacteristic(BluetoothCharacteristic characteristic) {
- ensureConnected();
-
- BluetoothGattCharacteristic c = getTinybCharacteristicByUUID(characteristic.getUuid().toString());
- if (c == null) {
- logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
- return false;
- }
- scheduler.submit(() -> {
- try {
- BluetoothCompletionStatus successStatus = c.writeValue(characteristic.getByteValue())
- ? BluetoothCompletionStatus.SUCCESS
- : BluetoothCompletionStatus.ERROR;
- notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic, successStatus);
- } catch (BluetoothException e) {
- logger.debug("Exception occurred when trying to write characteristic '{}': {}",
- characteristic.getUuid(), e.getMessage());
- notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic,
- BluetoothCompletionStatus.ERROR);
- }
- });
- return true;
- }
-
- @Override
- public boolean enableNotifications(BluetoothCharacteristic characteristic) {
- ensureConnected();
-
- BluetoothGattCharacteristic c = getTinybCharacteristicByUUID(characteristic.getUuid().toString());
- if (c != null) {
- try {
- c.enableValueNotifications(value -> {
- characteristic.setValue(value);
- notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, characteristic);
- });
- } catch (BluetoothException e) {
- if (e.getMessage().contains("Already notifying")) {
- return false;
- } else if (e.getMessage().contains("In Progress")) {
- // let's retry in 10 seconds
- scheduler.schedule(() -> enableNotifications(characteristic), 10, TimeUnit.SECONDS);
- } else {
- logger.warn("Exception occurred while activating notifications on '{}'", address, e);
- }
- }
- return true;
- } else {
- logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
- return false;
- }
- }
-
- @Override
- public boolean disableNotifications(BluetoothCharacteristic characteristic) {
- ensureConnected();
-
- BluetoothGattCharacteristic c = getTinybCharacteristicByUUID(characteristic.getUuid().toString());
- if (c != null) {
- c.disableValueNotifications();
- return true;
- } else {
- logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
- return false;
- }
- }
-
- @Override
- public boolean enableNotifications(BluetoothDescriptor descriptor) {
- ensureConnected();
-
- BluetoothGattDescriptor d = getTinybDescriptorByUUID(descriptor.getUuid().toString());
- if (d != null) {
- d.enableValueNotifications(value -> {
- descriptor.setValue(value);
- notifyListeners(BluetoothEventType.DESCRIPTOR_UPDATED, descriptor);
- });
- return true;
- } else {
- logger.warn("Descriptor '{}' is missing on device '{}'.", descriptor.getUuid(), address);
- return false;
- }
- }
-
- @Override
- public boolean disableNotifications(BluetoothDescriptor descriptor) {
- ensureConnected();
-
- BluetoothGattDescriptor d = getTinybDescriptorByUUID(descriptor.getUuid().toString());
- if (d != null) {
- d.disableValueNotifications();
- return true;
- } else {
- logger.warn("Descriptor '{}' is missing on device '{}'.", descriptor.getUuid(), address);
- return false;
- }
- }
-
- private BluetoothGattCharacteristic getTinybCharacteristicByUUID(String uuid) {
- for (BluetoothGattService service : device.getServices()) {
- for (BluetoothGattCharacteristic c : service.getCharacteristics()) {
- if (c.getUUID().equals(uuid)) {
- return c;
- }
- }
- }
- return null;
- }
-
- private BluetoothGattDescriptor getTinybDescriptorByUUID(String uuid) {
- for (BluetoothGattService service : device.getServices()) {
- for (BluetoothGattCharacteristic c : service.getCharacteristics()) {
- for (BluetoothGattDescriptor d : c.getDescriptors()) {
- if (d.getUUID().equals(uuid)) {
- return d;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Clean up and release memory.
- */
- @Override
- public void dispose() {
- if (device == null) {
- return;
- }
- disableNotifications();
- try {
- device.remove();
- } catch (BluetoothException ex) {
- if (ex.getMessage().contains("Does Not Exist")) {
- // this happens when the underlying device has already been removed
- // but we don't have a way to check if that is the case beforehand so
- // we will just eat the error here.
- } else {
- logger.debug("Exception occurred when trying to remove inactive device '{}': {}", address,
- ex.getMessage());
- }
- }
- }
-}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZBridgeHandler.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZBridgeHandler.java
deleted file mode 100644
index 34a1d81eb..000000000
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZBridgeHandler.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.bluetooth.bluez.handler;
-
-import java.util.List;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
-import org.openhab.binding.bluetooth.BluetoothAddress;
-import org.openhab.binding.bluetooth.bluez.BlueZBluetoothDevice;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import tinyb.BluetoothException;
-import tinyb.BluetoothManager;
-
-/**
- * The {@link BlueZBridgeHandler} is responsible for talking to the BlueZ stack.
- * It provides a private interface for {@link BlueZBluetoothDevice}s to access the stack and provides top
- * level adaptor functionality for scanning and arbitration.
- *
- * @author Kai Kreuzer - Initial contribution and API
- * @author Hilbrand Bouwkamp - Simplified calling scan and better handling manual scanning
- * @author Connor Petty - Simplified device scan logic
- */
-@NonNullByDefault
-public class BlueZBridgeHandler extends AbstractBluetoothBridgeHandler {
-
- private final Logger logger = LoggerFactory.getLogger(BlueZBridgeHandler.class);
-
- private @NonNullByDefault({}) tinyb.BluetoothAdapter adapter;
-
- // Our BT address
- private @NonNullByDefault({}) BluetoothAddress adapterAddress;
-
- private @NonNullByDefault({}) ScheduledFuture> discoveryJob;
-
- /**
- * Constructor
- *
- * @param bridge the bridge definition for this handler
- */
- public BlueZBridgeHandler(Bridge bridge) {
- super(bridge);
- }
-
- @Override
- public void initialize() {
- super.initialize();
- BluetoothManager manager;
- try {
- manager = BluetoothManager.getBluetoothManager();
- if (manager == null) {
- throw new IllegalStateException("Received null BlueZ manager");
- }
- } catch (UnsatisfiedLinkError e) {
- throw new IllegalStateException("BlueZ JNI connection cannot be established.", e);
- } catch (RuntimeException e) {
- // we do not get anything more specific from TinyB here
- if (e.getMessage() != null && e.getMessage().contains("AccessDenied")) {
- throw new IllegalStateException(
- "Cannot access BlueZ stack due to permission problems. Make sure that your OS user is part of the 'bluetooth' group of BlueZ.");
- } else {
- throw new IllegalStateException("Cannot access BlueZ layer.", e);
- }
- }
-
- final BlueZAdapterConfiguration configuration = getConfigAs(BlueZAdapterConfiguration.class);
- if (configuration.address != null) {
- adapterAddress = new BluetoothAddress(configuration.address);
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "address not set");
- return;
- }
-
- logger.debug("Creating BlueZ adapter with address '{}'", adapterAddress);
-
- for (tinyb.BluetoothAdapter adapter : manager.getAdapters()) {
- if (adapter == null) {
- logger.warn("got null adapter from bluetooth manager");
- continue;
- }
- if (adapter.getAddress().equals(adapterAddress.toString())) {
- this.adapter = adapter;
- discoveryJob = scheduler.scheduleWithFixedDelay(this::refreshDevices, 0, 10, TimeUnit.SECONDS);
- return;
- }
- }
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No adapter for this address found.");
- }
-
- private void startDiscovery() {
- // we need to make sure the adapter is powered first
- if (!adapter.getPowered()) {
- adapter.setPowered(true);
- }
- if (!adapter.getDiscovering()) {
- adapter.setRssiDiscoveryFilter(-96);
- adapter.startDiscovery();
- }
- }
-
- private void refreshDevices() {
- refreshTry: try {
- logger.debug("Refreshing Bluetooth device list...");
- List tinybDevices = adapter.getDevices();
- logger.debug("Found {} Bluetooth devices.", tinybDevices.size());
- for (tinyb.BluetoothDevice tinybDevice : tinybDevices) {
- BlueZBluetoothDevice device = getDevice(new BluetoothAddress(tinybDevice.getAddress()));
- device.updateTinybDevice(tinybDevice);
- deviceDiscovered(device);
- }
- // For whatever reason, bluez will sometimes turn off scanning. So we just make sure it keeps running.
- startDiscovery();
- } catch (BluetoothException ex) {
- String message = ex.getMessage();
- if (message != null) {
- if (message.contains("Operation already in progress")) {
- // we shouldn't go offline in this case
- break refreshTry;
- }
- int idx = message.lastIndexOf(':');
- if (idx != -1) {
- message = message.substring(idx).trim();
- }
- }
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
- return;
- }
- updateStatus(ThingStatus.ONLINE);
- }
-
- @Override
- public @Nullable BluetoothAddress getAddress() {
- return adapterAddress;
- }
-
- @Override
- protected BlueZBluetoothDevice createDevice(BluetoothAddress address) {
- BlueZBluetoothDevice device = new BlueZBluetoothDevice(this, address);
- device.initialize();
- return device;
- }
-
- @Override
- public void dispose() {
- if (discoveryJob != null) {
- discoveryJob.cancel(true);
- discoveryJob = null;
- }
- if (adapter != null && adapter.getDiscovering()) {
- adapter.stopDiscovery();
- }
- super.dispose();
- }
-}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZAdapterConfiguration.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZAdapterConfiguration.java
similarity index 71%
rename from bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZAdapterConfiguration.java
rename to bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZAdapterConfiguration.java
index 0999624ee..1e3d8e6ad 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZAdapterConfiguration.java
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZAdapterConfiguration.java
@@ -10,16 +10,19 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.bluetooth.bluez.handler;
+package org.openhab.binding.bluetooth.bluez.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BaseBluetoothBridgeHandlerConfiguration;
/**
- * Configuration properties class.
+ * Configuration properties for a bridge.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
+@NonNullByDefault
public class BlueZAdapterConfiguration extends BaseBluetoothBridgeHandlerConfiguration {
- public String address;
+ public @Nullable String address;
}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZAdapterConstants.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZAdapterConstants.java
similarity index 91%
rename from bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZAdapterConstants.java
rename to bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZAdapterConstants.java
index 4edfada16..89a5f5569 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZAdapterConstants.java
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZAdapterConstants.java
@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.bluetooth.bluez;
+package org.openhab.binding.bluetooth.bluez.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
@@ -30,4 +30,7 @@ public class BlueZAdapterConstants {
// Properties
public static final String PROPERTY_ADDRESS = "address";
+
+ private BlueZAdapterConstants() {
+ }
}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java
new file mode 100644
index 000000000..d11b1bb64
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java
@@ -0,0 +1,467 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.bluez.exceptions.BluezFailedException;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.freedesktop.dbus.errors.NoReply;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.types.UInt16;
+import org.openhab.binding.bluetooth.BaseBluetoothDevice;
+import org.openhab.binding.bluetooth.BluetoothAddress;
+import org.openhab.binding.bluetooth.BluetoothCharacteristic;
+import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
+import org.openhab.binding.bluetooth.BluetoothDescriptor;
+import org.openhab.binding.bluetooth.BluetoothService;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEventListener;
+import org.openhab.binding.bluetooth.bluez.internal.events.CharacteristicUpdateEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.ConnectedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.ManufacturerDataEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.NameEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.RssiEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
+import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
+import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
+import org.openhab.core.common.ThreadPoolManager;
+import org.openhab.core.util.HexUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothDevice;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothGattCharacteristic;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothGattDescriptor;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothGattService;
+
+/**
+ * Implementation of BluetoothDevice for BlueZ via DBus-BlueZ API
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ * @author Benjamin Lafois - Replaced tinyB with bluezDbus
+ *
+ */
+@NonNullByDefault
+public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEventListener {
+
+ private final Logger logger = LoggerFactory.getLogger(BlueZBluetoothDevice.class);
+
+ private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
+
+ // Device from native lib
+ private @Nullable BluetoothDevice device = null;
+
+ /**
+ * Constructor
+ *
+ * @param adapter the bridge handler through which this device is connected
+ * @param address the Bluetooth address of the device
+ * @param name the name of the device
+ */
+ public BlueZBluetoothDevice(BlueZBridgeHandler adapter, BluetoothAddress address) {
+ super(adapter, address);
+ logger.debug("Creating DBusBlueZ device with address '{}'", address);
+ }
+
+ public synchronized void updateBlueZDevice(@Nullable BluetoothDevice blueZDevice) {
+ if (this.device != null && this.device == blueZDevice) {
+ return;
+ }
+ logger.debug("updateBlueZDevice({})", blueZDevice);
+
+ this.device = blueZDevice;
+
+ if (blueZDevice == null) {
+ return;
+ }
+
+ Short rssi = blueZDevice.getRssi();
+ if (rssi != null) {
+ this.rssi = rssi.intValue();
+ }
+ this.name = blueZDevice.getName();
+ Map manData = blueZDevice.getManufacturerData();
+ if (manData != null) {
+ manData.entrySet().stream().map(Map.Entry::getKey).filter(Objects::nonNull).findFirst()
+ .ifPresent((UInt16 manufacturerId) ->
+ // Convert to unsigned int to match the convention in BluetoothCompanyIdentifiers
+ this.manufacturer = manufacturerId.intValue() & 0xFFFF);
+ }
+
+ if (Boolean.TRUE.equals(blueZDevice.isConnected())) {
+ setConnectionState(ConnectionState.CONNECTED);
+ }
+
+ discoverServices();
+ }
+
+ /**
+ * Clean up and release memory.
+ */
+ @Override
+ public void dispose() {
+ BluetoothDevice dev = device;
+ if (dev != null) {
+ try {
+ dev.getAdapter().removeDevice(dev.getRawDevice());
+ } catch (DBusException ex) {
+ if (ex.getMessage().contains("Does Not Exist")) {
+ // this happens when the underlying device has already been removed
+ // but we don't have a way to check if that is the case beforehand so
+ // we will just eat the error here.
+ } else {
+ logger.debug("Exception occurred when trying to remove inactive device '{}': {}", address,
+ ex.getMessage());
+ }
+ } catch (RuntimeException ex) {
+ // try to catch any other exceptions
+ logger.debug("Exception occurred when trying to remove inactive device '{}': {}", address,
+ ex.getMessage());
+ }
+ }
+ }
+
+ private void setConnectionState(ConnectionState state) {
+ if (this.connectionState != state) {
+ this.connectionState = state;
+ notifyListeners(BluetoothEventType.CONNECTION_STATE, new BluetoothConnectionStatusNotification(state));
+ }
+ }
+
+ @Override
+ public boolean connect() {
+ logger.debug("Connect({})", device);
+
+ BluetoothDevice dev = device;
+ if (dev != null) {
+ if (Boolean.FALSE.equals(dev.isConnected())) {
+ try {
+ boolean ret = dev.connect();
+ logger.debug("Connect result: {}", ret);
+ return ret;
+ } catch (NoReply e) {
+ // Have to double check because sometimes, exception but still worked
+ logger.debug("Got a timeout - but sometimes happen. Is Connected ? {}", dev.isConnected());
+ if (Boolean.FALSE.equals(dev.isConnected())) {
+
+ notifyListeners(BluetoothEventType.CONNECTION_STATE,
+ new BluetoothConnectionStatusNotification(ConnectionState.DISCONNECTED));
+ return false;
+ } else {
+ return true;
+ }
+ } catch (DBusExecutionException e) {
+ // Catch "software caused connection abort"
+ return false;
+ } catch (Exception e) {
+ logger.warn("error occured while trying to connect", e);
+ }
+
+ } else {
+ logger.debug("Device was already connected");
+ // we might be stuck in another state atm so we need to trigger a connected in this case
+ setConnectionState(ConnectionState.CONNECTED);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean disconnect() {
+ BluetoothDevice dev = device;
+ if (dev != null) {
+ logger.debug("Disconnecting '{}'", address);
+ return dev.disconnect();
+ }
+ return false;
+ }
+
+ private void ensureConnected() {
+ BluetoothDevice dev = device;
+ if (dev == null || !dev.isConnected()) {
+ throw new IllegalStateException("DBusBlueZ device is not set or not connected");
+ }
+ }
+
+ private @Nullable BluetoothGattCharacteristic getDBusBlueZCharacteristicByUUID(String uuid) {
+ BluetoothDevice dev = device;
+ if (dev == null) {
+ return null;
+ }
+ for (BluetoothGattService service : dev.getGattServices()) {
+ for (BluetoothGattCharacteristic c : service.getGattCharacteristics()) {
+ if (c.getUuid().equalsIgnoreCase(uuid)) {
+ return c;
+ }
+ }
+ }
+ return null;
+ }
+
+ private @Nullable BluetoothGattCharacteristic getDBusBlueZCharacteristicByDBusPath(String dBusPath) {
+ BluetoothDevice dev = device;
+ if (dev == null) {
+ return null;
+ }
+ for (BluetoothGattService service : dev.getGattServices()) {
+ if (dBusPath.startsWith(service.getDbusPath())) {
+ for (BluetoothGattCharacteristic characteristic : service.getGattCharacteristics()) {
+ if (dBusPath.startsWith(characteristic.getDbusPath())) {
+ return characteristic;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private @Nullable BluetoothGattDescriptor getDBusBlueZDescriptorByUUID(String uuid) {
+ BluetoothDevice dev = device;
+ if (dev == null) {
+ return null;
+ }
+ for (BluetoothGattService service : dev.getGattServices()) {
+ for (BluetoothGattCharacteristic c : service.getGattCharacteristics()) {
+ for (BluetoothGattDescriptor d : c.getGattDescriptors()) {
+ if (d.getUuid().equalsIgnoreCase(uuid)) {
+ return d;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean enableNotifications(BluetoothCharacteristic characteristic) {
+ ensureConnected();
+
+ BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
+ if (c != null) {
+
+ try {
+ c.startNotify();
+ } catch (DBusException e) {
+ if (e.getMessage().contains("Already notifying")) {
+ return false;
+ } else if (e.getMessage().contains("In Progress")) {
+ // let's retry in 10 seconds
+ scheduler.schedule(() -> enableNotifications(characteristic), 10, TimeUnit.SECONDS);
+ } else {
+ logger.warn("Exception occurred while activating notifications on '{}'", address, e);
+ }
+ }
+ return true;
+ } else {
+ logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean writeCharacteristic(BluetoothCharacteristic characteristic) {
+ logger.debug("writeCharacteristic()");
+
+ ensureConnected();
+
+ BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
+ if (c == null) {
+ logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
+ return false;
+ }
+
+ scheduler.submit(() -> {
+ try {
+ c.writeValue(characteristic.getByteValue(), null);
+ notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic,
+ BluetoothCompletionStatus.SUCCESS);
+
+ } catch (DBusException e) {
+ logger.debug("Exception occurred when trying to write characteristic '{}': {}",
+ characteristic.getUuid(), e.getMessage());
+ notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic,
+ BluetoothCompletionStatus.ERROR);
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public void onDBusBlueZEvent(BlueZEvent event) {
+ logger.debug("Unsupported event: {}", event);
+ }
+
+ @Override
+ public void onServicesResolved(ServicesResolvedEvent event) {
+ if (event.isResolved()) {
+ notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
+ }
+ }
+
+ @Override
+ public void onNameUpdate(NameEvent event) {
+ BluetoothScanNotification notification = new BluetoothScanNotification();
+ notification.setDeviceName(event.getName());
+ notifyListeners(BluetoothEventType.SCAN_RECORD, notification);
+ }
+
+ @Override
+ public void onManufacturerDataUpdate(ManufacturerDataEvent event) {
+ for (Map.Entry entry : event.getData().entrySet()) {
+ BluetoothScanNotification notification = new BluetoothScanNotification();
+ byte[] data = new byte[entry.getValue().length + 2];
+ data[0] = (byte) (entry.getKey() & 0xFF);
+ data[1] = (byte) (entry.getKey() >>> 8);
+
+ System.arraycopy(entry.getValue(), 0, data, 2, entry.getValue().length);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Received manufacturer data for '{}': {}", address, HexUtils.bytesToHex(data, " "));
+ }
+
+ notification.setManufacturerData(data);
+ notifyListeners(BluetoothEventType.SCAN_RECORD, notification);
+ }
+ }
+
+ @Override
+ public void onTxPowerUpdate(TXPowerEvent event) {
+ this.txPower = (int) event.getTxPower();
+ }
+
+ @Override
+ public void onCharacteristicNotify(CharacteristicUpdateEvent event) {
+ // Here it is a bit special - as the event is linked to the DBUS path, not characteristic UUID.
+ // So we need to find the characteristic by its DBUS path.
+ BluetoothGattCharacteristic characteristic = getDBusBlueZCharacteristicByDBusPath(event.getDbusPath());
+ if (characteristic == null) {
+ logger.debug("Received a notification for a characteristic not found on device.");
+ return;
+ }
+ BluetoothCharacteristic c = getCharacteristic(UUID.fromString(characteristic.getUuid()));
+ if (c != null) {
+ c.setValue(event.getData());
+ notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, c, BluetoothCompletionStatus.SUCCESS);
+ }
+ }
+
+ @Override
+ public void onRssiUpdate(RssiEvent event) {
+ int rssiTmp = event.getRssi();
+ this.rssi = rssiTmp;
+ BluetoothScanNotification notification = new BluetoothScanNotification();
+ notification.setRssi(rssiTmp);
+ notifyListeners(BluetoothEventType.SCAN_RECORD, notification);
+ }
+
+ @Override
+ public void onConnectedStatusUpdate(ConnectedEvent event) {
+ this.connectionState = event.isConnected() ? ConnectionState.CONNECTED : ConnectionState.DISCONNECTED;
+ notifyListeners(BluetoothEventType.CONNECTION_STATE,
+ new BluetoothConnectionStatusNotification(connectionState));
+ }
+
+ @Override
+ public boolean discoverServices() {
+ BluetoothDevice dev = device;
+ if (dev == null) {
+ return false;
+ }
+ if (dev.getGattServices().size() > getServices().size()) {
+ for (BluetoothGattService dBusBlueZService : dev.getGattServices()) {
+ BluetoothService service = new BluetoothService(UUID.fromString(dBusBlueZService.getUuid()),
+ dBusBlueZService.isPrimary());
+ for (BluetoothGattCharacteristic dBusBlueZCharacteristic : dBusBlueZService.getGattCharacteristics()) {
+ BluetoothCharacteristic characteristic = new BluetoothCharacteristic(
+ UUID.fromString(dBusBlueZCharacteristic.getUuid()), 0);
+
+ for (BluetoothGattDescriptor dBusBlueZDescriptor : dBusBlueZCharacteristic.getGattDescriptors()) {
+ BluetoothDescriptor descriptor = new BluetoothDescriptor(characteristic,
+ UUID.fromString(dBusBlueZDescriptor.getUuid()));
+ characteristic.addDescriptor(descriptor);
+ }
+ service.addCharacteristic(characteristic);
+ }
+ addService(service);
+ }
+ notifyListeners(BluetoothEventType.SERVICES_DISCOVERED);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean readCharacteristic(BluetoothCharacteristic characteristic) {
+ BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
+ if (c == null) {
+ logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
+ return false;
+ }
+
+ scheduler.submit(() -> {
+ try {
+ byte[] value = c.readValue(null);
+ characteristic.setValue(value);
+ notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
+ BluetoothCompletionStatus.SUCCESS);
+ } catch (DBusException e) {
+ logger.debug("Exception occurred when trying to read characteristic '{}': {}", characteristic.getUuid(),
+ e.getMessage());
+ notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
+ BluetoothCompletionStatus.ERROR);
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public boolean disableNotifications(BluetoothCharacteristic characteristic) {
+ BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
+ if (c != null) {
+ try {
+ c.stopNotify();
+ } catch (BluezFailedException e) {
+ if (e.getMessage().contains("In Progress")) {
+ // let's retry in 10 seconds
+ scheduler.schedule(() -> disableNotifications(characteristic), 10, TimeUnit.SECONDS);
+ } else {
+ logger.warn("Exception occurred while activating notifications on '{}'", address, e);
+ }
+ }
+ return true;
+ } else {
+ logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean enableNotifications(BluetoothDescriptor descriptor) {
+ // Not sure if it is possible to implement this
+ return false;
+ }
+
+ @Override
+ public boolean disableNotifications(BluetoothDescriptor descriptor) {
+ // Not sure if it is possible to implement this
+ return false;
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBridgeHandler.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBridgeHandler.java
new file mode 100644
index 000000000..962b0ef62
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBridgeHandler.java
@@ -0,0 +1,234 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal;
+
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
+import org.openhab.binding.bluetooth.BluetoothAddress;
+import org.openhab.binding.bluetooth.bluez.internal.events.AdapterDiscoveringChangedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.AdapterPoweredChangedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEventListener;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothAdapter;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothDevice;
+
+/**
+ * The {@link BlueZBridgeHandler} is responsible for talking to the BlueZ stack, using DBus Unix Socket.
+ * This Binding does not use any JNI.
+ * It provides a private interface for {@link BlueZBluetoothDevice}s to access the stack and provides top
+ * level adaptor functionality for scanning and arbitration.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ * @author Hilbrand Bouwkamp - Simplified calling scan and better handling manual scanning
+ * @author Connor Petty - Simplified device scan logic
+ * @author Benjamin Lafois - Replaced tinyB with bluezDbus
+ *
+ */
+@NonNullByDefault
+public class BlueZBridgeHandler extends AbstractBluetoothBridgeHandler
+ implements BlueZEventListener {
+
+ private final Logger logger = LoggerFactory.getLogger(BlueZBridgeHandler.class);
+
+ // ADAPTER from BlueZ-DBus Library
+ private @Nullable BluetoothAdapter adapter;
+
+ // Our BT address
+ private @Nullable BluetoothAddress adapterAddress;
+
+ private @Nullable ScheduledFuture> discoveryJob;
+
+ private final DeviceManagerFactory deviceManagerFactory;
+
+ /**
+ * Constructor
+ *
+ * @param bridge the bridge definition for this handler
+ */
+ public BlueZBridgeHandler(Bridge bridge, DeviceManagerFactory deviceManagerFactory) {
+ super(bridge);
+ this.deviceManagerFactory = deviceManagerFactory;
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+
+ // Load configuration
+ final BlueZAdapterConfiguration configuration = getConfigAs(BlueZAdapterConfiguration.class);
+ String addr = configuration.address;
+ if (addr != null) {
+ this.adapterAddress = new BluetoothAddress(addr.toUpperCase());
+ } else {
+ // If configuration does not contain adapter address to use, exit with error.
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "address not set");
+ return;
+ }
+
+ logger.debug("Creating BlueZ adapter with address '{}'", adapterAddress);
+ updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Initializing");
+ deviceManagerFactory.getPropertiesChangedHandler().addListener(this);
+ discoveryJob = scheduler.scheduleWithFixedDelay(this::initializeAndRefreshDevices, 5, 10, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void dispose() {
+ deviceManagerFactory.getPropertiesChangedHandler().removeListener(this);
+ logger.debug("Termination of DBus BlueZ handler");
+
+ Future> job = discoveryJob;
+ if (job != null) {
+ job.cancel(false);
+ discoveryJob = null;
+ }
+
+ BluetoothAdapter localAdatper = this.adapter;
+ if (localAdatper != null) {
+ localAdatper.stopDiscovery();
+ this.adapter = null;
+ }
+
+ super.dispose();
+ }
+
+ private @Nullable BluetoothAdapter prepareAdapter(DeviceManagerWrapper deviceManager) {
+ // next lets check if we can find our adapter in the manager.
+ BluetoothAdapter localAdapter = adapter;
+ if (localAdapter == null) {
+ BluetoothAddress localAddress = adapterAddress;
+ if (localAddress != null) {
+ localAdapter = adapter = deviceManager.getAdapter(localAddress);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No adapter address provided");
+ return null;
+ }
+ }
+ if (localAdapter == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Native adapter could not be found for address '" + adapterAddress + "'");
+ return null;
+ }
+ // now lets confirm that the adapter is powered
+ if (!localAdapter.isPowered()) {
+ localAdapter.setPowered(true);
+ // give the device some time to power on
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
+ "Adapter is not powered, attempting to turn on...");
+ return null;
+ }
+
+ // now lets make sure that discovery is turned on
+ if (!localAdapter.startDiscovery()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Trying to start discovery");
+ return null;
+ }
+ return localAdapter;
+ }
+
+ private void initializeAndRefreshDevices() {
+ logger.debug("initializeAndRefreshDevice()");
+
+ try {
+ // first check if the device manager is ready
+ DeviceManagerWrapper deviceManager = deviceManagerFactory.getDeviceManager();
+ if (deviceManager == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Bluez DeviceManager not available yet.");
+ return;
+ }
+
+ BluetoothAdapter adapter = prepareAdapter(deviceManager);
+ if (adapter == null) {
+ // adapter isn't prepared yet
+ return;
+ }
+
+ // now lets refresh devices
+ List bluezDevices = deviceManager.getDevices(adapter);
+ logger.debug("Found {} Bluetooth devices.", bluezDevices.size());
+ for (BluetoothDevice bluezDevice : bluezDevices) {
+ if (bluezDevice.getAddress() == null) {
+ // For some reasons, sometimes the address is null..
+ continue;
+ }
+ BlueZBluetoothDevice device = getDevice(new BluetoothAddress(bluezDevice.getAddress()));
+ device.updateBlueZDevice(bluezDevice);
+ deviceDiscovered(device);
+ }
+ updateStatus(ThingStatus.ONLINE);
+ } catch (Exception ex) {
+ // don't know what kind of exception the bluez library might throw at us so lets catch them here so our
+ // scheduler loop doesn't get terminated
+ logger.warn("Unknown exception", ex);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
+ }
+ }
+
+ @Override
+ public @Nullable BluetoothAddress getAddress() {
+ return adapterAddress;
+ }
+
+ @Override
+ protected BlueZBluetoothDevice createDevice(BluetoothAddress address) {
+ logger.debug("createDevice {}", address);
+ BlueZBluetoothDevice device = new BlueZBluetoothDevice(this, address);
+ return device;
+ }
+
+ @Override
+ public void onDBusBlueZEvent(BlueZEvent event) {
+ BluetoothAdapter localAdapter = this.adapter;
+ String adapterName = event.getAdapterName();
+ if (adapterName == null || localAdapter == null) {
+ // We cannot be sure that this event concerns this adapter.. So ignore message
+ return;
+ }
+ String localName = localAdapter.getDeviceName();
+
+ if (!adapterName.equals(localName)) {
+ // does not concern this adapter
+ return;
+ }
+
+ BluetoothAddress address = event.getDevice();
+
+ if (address != null) {
+ // now lets forward the event to the corresponding bluetooth device
+ BlueZBluetoothDevice device = getDevice(address);
+ event.dispatch(device);
+ }
+ }
+
+ @Override
+ public void onDiscoveringChanged(AdapterDiscoveringChangedEvent event) {
+ // do nothing for now
+ }
+
+ @Override
+ public void onPoweredChange(AdapterPoweredChangedEvent event) {
+ // do nothing for now
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/discovery/BlueZDiscoveryService.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZDiscoveryService.java
similarity index 54%
rename from bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/discovery/BlueZDiscoveryService.java
rename to bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZDiscoveryService.java
index 0ad4de71b..e6ddda926 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/discovery/BlueZDiscoveryService.java
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZDiscoveryService.java
@@ -10,22 +10,26 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.bluetooth.bluez.internal.discovery;
+package org.openhab.binding.bluetooth.bluez.internal;
import java.util.Collections;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
-import org.openhab.binding.bluetooth.bluez.BlueZAdapterConstants;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import tinyb.BluetoothAdapter;
-import tinyb.BluetoothManager;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothAdapter;
/**
* This is a discovery service, which checks whether we are running on a Linux with a BlueZ stack.
@@ -33,41 +37,59 @@ import tinyb.BluetoothManager;
*
* @author Kai Kreuzer - Initial Contribution and API
* @author Hilbrand Bouwkamp - Moved background scan to actual background method
+ * @author Connor Petty - Replaced tinyB with bluezDbus
*
*/
+@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.bluetooth.bluez")
public class BlueZDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(BlueZDiscoveryService.class);
- private BluetoothManager manager;
+ private final DeviceManagerFactory deviceManagerFactory;
+ private @Nullable Future> backgroundScan;
- public BlueZDiscoveryService() {
+ @Activate
+ public BlueZDiscoveryService(@Reference DeviceManagerFactory deviceManagerFactory) {
super(Collections.singleton(BlueZAdapterConstants.THING_TYPE_BLUEZ), 1, true);
+ this.deviceManagerFactory = deviceManagerFactory;
+ }
+
+ private static void cancel(@Nullable Future> future) {
+ if (future != null) {
+ future.cancel(false);
+ }
}
@Override
protected void startBackgroundDiscovery() {
- startScan();
+ backgroundScan = scheduler.scheduleWithFixedDelay(() -> {
+ DeviceManagerWrapper deviceManager = deviceManagerFactory.getDeviceManager();
+ if (deviceManager == null) {
+ return;
+ }
+ startScan();
+ }, 5, 10, TimeUnit.SECONDS);
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ cancel(backgroundScan);
+ backgroundScan = null;
}
@Override
protected void startScan() {
- try {
- manager = BluetoothManager.getBluetoothManager();
- manager.getAdapters().stream().map(this::createDiscoveryResult).forEach(this::thingDiscovered);
- } catch (UnsatisfiedLinkError e) {
- logger.debug("Not possible to initialize the BlueZ stack. ", e);
+ DeviceManagerWrapper deviceManager = deviceManagerFactory.getDeviceManager();
+ if (deviceManager == null) {
+ logger.warn("The DeviceManager is not available");
return;
- } catch (RuntimeException e) {
- // we do not get anything more specific from TinyB here
- if (e.getMessage() != null && e.getMessage().contains("AccessDenied")) {
- logger.warn(
- "Cannot access BlueZ stack due to permission problems. Make sure that your OS user is part of the 'bluetooth' group of BlueZ.");
- } else {
- logger.warn("Failed to scan for Bluetooth devices", e);
- }
}
+ // the first time the device manager is not null we can cancel background discovery
+ stopBackgroundDiscovery();
+ deviceManager.scanForBluetoothAdapters().stream()//
+ .map(this::createDiscoveryResult)//
+ .forEach(this::thingDiscovered);
}
private DiscoveryResult createDiscoveryResult(BluetoothAdapter adapter) {
@@ -78,6 +100,6 @@ public class BlueZDiscoveryService extends AbstractDiscoveryService {
}
private String getId(BluetoothAdapter adapter) {
- return adapter.getInterfaceName().replaceAll("[^a-zA-Z0-9_]", "");
+ return adapter.getDeviceName().replaceAll("[^a-zA-Z0-9_]", "");
}
}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZHandlerFactory.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZHandlerFactory.java
index 59adb7130..0ddb3fa3d 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZHandlerFactory.java
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZHandlerFactory.java
@@ -18,9 +18,9 @@ import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BluetoothAdapter;
-import org.openhab.binding.bluetooth.bluez.BlueZAdapterConstants;
-import org.openhab.binding.bluetooth.bluez.handler.BlueZBridgeHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
@@ -30,21 +30,32 @@ import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
/**
* The {@link BlueZHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Kai Kreuzer - Initial contribution and API
+ * @author Connor Petty - Added DeviceManagerFactory
*/
+@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.bluetooth.bluez")
public class BlueZHandlerFactory extends BaseThingHandlerFactory {
private static final Set SUPPORTED_THING_TYPES_UIDS = Collections
.singleton(BlueZAdapterConstants.THING_TYPE_BLUEZ);
- private final Map> serviceRegs = new HashMap<>();
+ private final Map> serviceRegs = new HashMap<>();
+
+ private final DeviceManagerFactory deviceManagerFactory;
+
+ @Activate
+ public BlueZHandlerFactory(@Reference DeviceManagerFactory deviceManagerFactory) {
+ this.deviceManagerFactory = deviceManagerFactory;
+ }
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -52,11 +63,11 @@ public class BlueZHandlerFactory extends BaseThingHandlerFactory {
}
@Override
- protected ThingHandler createHandler(Thing thing) {
+ protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(BlueZAdapterConstants.THING_TYPE_BLUEZ)) {
- BlueZBridgeHandler handler = new BlueZBridgeHandler((Bridge) thing);
+ BlueZBridgeHandler handler = new BlueZBridgeHandler((Bridge) thing, deviceManagerFactory);
registerBluetoothAdapter(handler);
return handler;
} else {
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZPropertiesChangedHandler.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZPropertiesChangedHandler.java
new file mode 100644
index 000000000..db68b7610
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZPropertiesChangedHandler.java
@@ -0,0 +1,212 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.freedesktop.dbus.DBusMap;
+import org.freedesktop.dbus.handlers.AbstractPropertiesChangedHandler;
+import org.freedesktop.dbus.interfaces.Properties.PropertiesChanged;
+import org.freedesktop.dbus.types.UInt16;
+import org.freedesktop.dbus.types.Variant;
+import org.openhab.binding.bluetooth.bluez.internal.events.AdapterDiscoveringChangedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.AdapterPoweredChangedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEventListener;
+import org.openhab.binding.bluetooth.bluez.internal.events.CharacteristicUpdateEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.ConnectedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.ManufacturerDataEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.NameEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.RssiEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
+import org.openhab.core.common.ThreadPoolManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the PropertiesChangedHandler subclass used by the binding to handle/dispatch property change events
+ * from bluez.
+ *
+ * @author Benjamin Lafois - Initial contribution and API
+ * @author Connor Petty - Code cleanup
+ */
+@NonNullByDefault
+public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(BlueZPropertiesChangedHandler.class);
+
+ private final Set listeners = new CopyOnWriteArraySet<>();
+
+ private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
+
+ public void addListener(BlueZEventListener listener) {
+ this.listeners.add(listener);
+ }
+
+ public void removeListener(BlueZEventListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ private void notifyListeners(BlueZEvent event) {
+ for (BlueZEventListener listener : this.listeners) {
+ event.dispatch(listener);
+ }
+ }
+
+ @Override
+ public void handle(@Nullable PropertiesChanged properties) {
+ if (properties == null || properties.getPropertiesChanged() == null) {
+ logger.debug("Null properties. Skipping.");
+ return;
+ }
+ Map<@Nullable String, @Nullable Variant>> changedProperties = properties.getPropertiesChanged();
+ if (changedProperties == null) {
+ logger.debug("Null properties changed. Skipping.");
+ return;
+ }
+
+ // do this asynchronously so that we don't slow things down for the dbus event dispatcher
+ scheduler.execute(() -> {
+
+ String dbusPath = properties.getPath();
+ changedProperties.forEach((key, variant) -> {
+ if (key == null || variant == null) {
+ return;
+ }
+ switch (key.toLowerCase()) {
+ case "rssi":
+ // Signal Update
+ onRSSIUpdate(dbusPath, variant);
+ break;
+ case "txpower":
+ // TxPower
+ onTXPowerUpdate(dbusPath, variant);
+ break;
+ case "value":
+ // Characteristc value updated
+ onValueUpdate(dbusPath, variant);
+ break;
+ case "connected":
+ onConnectedUpdate(dbusPath, variant);
+ break;
+ case "name":
+ onNameUpdate(dbusPath, variant);
+ break;
+ case "alias":
+ // TODO
+ break;
+ case "manufacturerdata":
+ onManufacturerDataUpdate(dbusPath, variant);
+ break;
+ case "powered":
+ onPoweredUpdate(dbusPath, variant);
+ break;
+ case "discovering":
+ onDiscoveringUpdate(dbusPath, variant);
+ break;
+ case "servicesresolved":
+ onServicesResolved(dbusPath, variant);
+ break;
+ }
+ });
+
+ logger.debug("PropertiesPath: {}", dbusPath);
+ logger.debug("PropertiesChanged: {}", changedProperties);
+ });
+ }
+
+ private void onDiscoveringUpdate(String dbusPath, Variant> variant) {
+ Object discovered = variant.getValue();
+ if (discovered instanceof Boolean) {
+ notifyListeners(new AdapterDiscoveringChangedEvent(dbusPath, (boolean) discovered));
+ }
+ }
+
+ private void onPoweredUpdate(String dbusPath, Variant> variant) {
+ Object powered = variant.getValue();
+ if (powered instanceof Boolean) {
+ notifyListeners(new AdapterPoweredChangedEvent(dbusPath, (boolean) powered));
+ }
+ }
+
+ private void onServicesResolved(String dbusPath, Variant> variant) {
+ Object resolved = variant.getValue();
+ if (resolved instanceof Boolean) {
+ notifyListeners(new ServicesResolvedEvent(dbusPath, (boolean) resolved));
+ }
+ }
+
+ private void onNameUpdate(String dbusPath, Variant> variant) {
+ Object name = variant.getValue();
+ if (name instanceof String) {
+ notifyListeners(new NameEvent(dbusPath, (String) name));
+ }
+ }
+
+ private void onTXPowerUpdate(String dbusPath, Variant> variant) {
+ Object txPower = variant.getValue();
+ if (txPower instanceof Short) {
+ notifyListeners(new TXPowerEvent(dbusPath, (short) txPower));
+ }
+ }
+
+ private void onConnectedUpdate(String dbusPath, Variant> variant) {
+ Object connected = variant.getValue();
+ if (connected instanceof Boolean) {
+ notifyListeners(new ConnectedEvent(dbusPath, (boolean) connected));
+ }
+ }
+
+ private void onManufacturerDataUpdate(String dbusPath, Variant> variant) {
+ Map eventData = new HashMap<>();
+
+ Object map = variant.getValue();
+ if (map instanceof DBusMap) {
+ DBusMap, ?> dbm = (DBusMap, ?>) map;
+ for (Map.Entry, ?> entry : dbm.entrySet()) {
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ if (key instanceof UInt16 && value instanceof Variant>) {
+ value = ((Variant>) value).getValue();
+ if (value instanceof byte[]) {
+ eventData.put(((UInt16) key).shortValue(), ((byte[]) value));
+ }
+ }
+ }
+ }
+ if (!eventData.isEmpty()) {
+ notifyListeners(new ManufacturerDataEvent(dbusPath, eventData));
+ }
+ }
+
+ private void onValueUpdate(String dbusPath, Variant> variant) {
+ Object value = variant.getValue();
+ if (value instanceof byte[]) {
+ notifyListeners(new CharacteristicUpdateEvent(dbusPath, (byte[]) value));
+ }
+ }
+
+ private void onRSSIUpdate(String dbusPath, Variant> variant) {
+ Object rssi = variant.getValue();
+ if (rssi instanceof Short) {
+ notifyListeners(new RssiEvent(dbusPath, (short) rssi));
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/DeviceManagerFactory.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/DeviceManagerFactory.java
new file mode 100644
index 000000000..78ec6838b
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/DeviceManagerFactory.java
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.openhab.core.common.ThreadPoolManager;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.hypfvieh.bluetooth.DeviceManager;
+
+/**
+ * This service handles the lifecycle of the {@link DeviceManager} singleton instance.
+ * In addition, this class is responsible for managing the BlueZPropertiesChangedHandler instance
+ * used by the binding for listening and dispatching dbus events from the DeviceManager.
+ *
+ * Creation of the DeviceManagerWrapper is asynchronous and thus attempts to retrieve the
+ * DeviceManagerWrapper through 'getDeviceManager' may initially fail.
+ *
+ * @author Connor Petty - Initial Contribution
+ *
+ */
+@NonNullByDefault
+@Component(service = DeviceManagerFactory.class)
+public class DeviceManagerFactory {
+
+ private final Logger logger = LoggerFactory.getLogger(DeviceManagerFactory.class);
+ private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
+
+ private final BlueZPropertiesChangedHandler changeHandler = new BlueZPropertiesChangedHandler();
+
+ private @Nullable CompletableFuture deviceManagerFuture;
+ private @Nullable CompletableFuture deviceManagerWrapperFuture;
+
+ public BlueZPropertiesChangedHandler getPropertiesChangedHandler() {
+ return changeHandler;
+ }
+
+ public @Nullable DeviceManagerWrapper getDeviceManager() {
+ // we can cheat the null checker with casting here
+ var future = (CompletableFuture<@Nullable DeviceManagerWrapper>) deviceManagerWrapperFuture;
+ if (future != null) {
+ return future.getNow(null);
+ }
+ return null;
+ }
+
+ @Activate
+ public void initialize() {
+ logger.debug("initializing DeviceManagerFactory");
+
+ var stage1 = this.deviceManagerFuture = callAsync(() -> {
+ try {
+ // if this is the first call to the library, this call
+ // should throw an exception (that we are catching)
+ return DeviceManager.getInstance();
+ // Experimental - seems reuse does not work
+ } catch (IllegalStateException e) {
+ // Exception caused by first call to the library
+ return DeviceManager.createInstance(false);
+ }
+ }, scheduler);
+
+ stage1.thenCompose(devManager -> {
+ // lambdas can't modify outside variables due to scoping, so instead we use an AtomicInteger.
+ AtomicInteger tryCount = new AtomicInteger();
+ // We need to set deviceManagerWrapperFuture here since we want to be able to cancel the underlying
+ // AsyncCompletableFuture instance
+ return this.deviceManagerWrapperFuture = callAsync(() -> {
+ int count = tryCount.incrementAndGet();
+ try {
+ logger.debug("Registering property handler attempt: {}", count);
+ devManager.registerPropertyHandler(changeHandler);
+ logger.debug("Successfully registered property handler");
+ return new DeviceManagerWrapper(devManager);
+ } catch (DBusException e) {
+ if (count < 3) {
+ throw new RetryException(5, TimeUnit.SECONDS);
+ } else {
+ throw e;
+ }
+ }
+ }, scheduler);
+ }).whenComplete((devManagerWrapper, th) -> {
+ if (th != null) {
+ logger.warn("Failed to initialize DeviceManager: {}", th.getMessage());
+ }
+ });
+ }
+
+ @Deactivate
+ public void dispose() {
+ var stage1 = this.deviceManagerFuture;
+ if (stage1 != null) {
+ if (!stage1.cancel(true)) {
+ // a failure to cancel means that the stage completed normally
+ stage1.thenAccept(DeviceManager::closeConnection);
+ }
+ }
+ this.deviceManagerFuture = null;
+
+ var stage2 = this.deviceManagerWrapperFuture;
+ if (stage2 != null) {
+ stage2.cancel(true);
+ }
+ this.deviceManagerWrapperFuture = null;
+ }
+
+ private static CompletableFuture callAsync(Callable callable, ScheduledExecutorService scheduler) {
+ return new AsyncCompletableFuture<>(callable, scheduler);
+ }
+
+ // this is a utility class that allows use of Callable with CompletableFutures in a way such that the
+ // async future is cancellable thru this CompletableFuture instance.
+ private static class AsyncCompletableFuture extends CompletableFuture implements Runnable {
+
+ private final Callable callable;
+ private final ScheduledExecutorService scheduler;
+ private final Object futureLock = new Object();
+ private Future> future;
+
+ public AsyncCompletableFuture(Callable callable, ScheduledExecutorService scheduler) {
+ this.callable = callable;
+ this.scheduler = scheduler;
+ future = scheduler.submit(this);
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ synchronized (futureLock) {
+ future.cancel(mayInterruptIfRunning);
+ }
+ return super.cancel(mayInterruptIfRunning);
+ }
+
+ @Override
+ public void run() {
+ try {
+ complete(callable.call());
+ } catch (RetryException e) {
+ synchronized (futureLock) {
+ if (!future.isCancelled()) {
+ future = scheduler.schedule(this, e.delay, e.unit);
+ }
+ }
+ } catch (Exception e) {
+ completeExceptionally(e);
+ }
+ }
+ }
+
+ // this is a special exception to indicate to a AsyncCompletableFuture that the task needs to be retried.
+ private static class RetryException extends Exception {
+
+ private static final long serialVersionUID = 8512275408512109328L;
+ private long delay;
+ private TimeUnit unit;
+
+ public RetryException(long delay, TimeUnit unit) {
+ this.delay = delay;
+ this.unit = unit;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/DeviceManagerWrapper.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/DeviceManagerWrapper.java
new file mode 100644
index 000000000..484c1fe3d
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/DeviceManagerWrapper.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bluetooth.BluetoothAddress;
+
+import com.github.hypfvieh.bluetooth.DeviceManager;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothAdapter;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothDevice;
+
+/**
+ * This is a threadsafe wrapper for a {@link DeviceManager} that also only exposes the methods
+ * required to implement this binding.
+ *
+ * @author Connor Petty - Initial Contribution
+ */
+@NonNullByDefault
+public class DeviceManagerWrapper {
+
+ private DeviceManager deviceManager;
+
+ public DeviceManagerWrapper(DeviceManager deviceManager) {
+ this.deviceManager = deviceManager;
+ }
+
+ public synchronized Collection scanForBluetoothAdapters() {
+ return deviceManager.scanForBluetoothAdapters();
+ }
+
+ public synchronized @Nullable BluetoothAdapter getAdapter(BluetoothAddress address) {
+ // we don't use `deviceManager.getAdapter` here since it might perform a scan if the adapter is missing.
+ String addr = address.toString();
+ List adapters = deviceManager.getAdapters();
+ if (adapters != null) {
+ for (BluetoothAdapter btAdapter : adapters) {
+ String btAddr = btAdapter.getAddress();
+ if (addr.equalsIgnoreCase(btAddr)) {
+ return btAdapter;
+ }
+ }
+ }
+ return null;
+ }
+
+ public synchronized List getDevices(BluetoothAdapter adapter) {
+ return deviceManager.getDevices(adapter.getAddress(), true);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/AdapterDiscoveringChangedEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/AdapterDiscoveringChangedEvent.java
new file mode 100644
index 000000000..466ac750d
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/AdapterDiscoveringChangedEvent.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This is triggered when a bluetooth adapter's 'Discovering' property changes
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class AdapterDiscoveringChangedEvent extends BlueZEvent {
+
+ private boolean discovering;
+
+ public AdapterDiscoveringChangedEvent(String dbusPath, boolean discovering) {
+ super(dbusPath);
+ this.discovering = discovering;
+ }
+
+ public boolean isDiscovering() {
+ return discovering;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onDiscoveringChanged(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/AdapterPoweredChangedEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/AdapterPoweredChangedEvent.java
new file mode 100644
index 000000000..df68945e1
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/AdapterPoweredChangedEvent.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This is triggered when a bluetooth adapter's 'Powered' property changes
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class AdapterPoweredChangedEvent extends BlueZEvent {
+
+ private boolean powered;
+
+ public AdapterPoweredChangedEvent(String dbusPath, boolean powered) {
+ super(dbusPath);
+ this.powered = powered;
+ }
+
+ public boolean isPowered() {
+ return powered;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onPoweredChange(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/BlueZEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/BlueZEvent.java
new file mode 100644
index 000000000..1b4928ebb
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/BlueZEvent.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bluetooth.BluetoothAddress;
+
+/**
+ * The {@link BlueZEvent} class represents an event from dbus due to
+ * changes in the properties of a bluetooth device.
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public abstract class BlueZEvent {
+
+ private String dbusPath;
+
+ private @Nullable BluetoothAddress device;
+ private @Nullable String adapterName;
+
+ public BlueZEvent(String dbusPath) {
+ this.dbusPath = dbusPath;
+
+ // the rest of the code should be equivalent to parsing with the following regex:
+ // "/org/bluez/(?[^/]+)(/dev_(?[^/]+).*)?"
+ if (!dbusPath.startsWith("/org/bluez/")) {
+ return;
+ }
+ int start = dbusPath.indexOf('/', 11);
+ if (start == -1) {
+ this.adapterName = dbusPath.substring(11);
+ return;
+ } else {
+ this.adapterName = dbusPath.substring(11, start);
+ }
+ start++;
+ int end = dbusPath.indexOf('/', start);
+ String mac;
+ if (end == -1) {
+ mac = dbusPath.substring(start);
+ } else {
+ mac = dbusPath.substring(start, end);
+ }
+ if (!mac.startsWith("dev_")) {
+ return;
+ }
+ mac = mac.substring(4); // trim off the "dev_" prefix
+ if (!mac.isEmpty()) {
+ this.device = new BluetoothAddress(mac.replace('_', ':').toUpperCase());
+ }
+ }
+
+ public String getDbusPath() {
+ return dbusPath;
+ }
+
+ public @Nullable BluetoothAddress getDevice() {
+ return device;
+ }
+
+ public @Nullable String getAdapterName() {
+ return adapterName;
+ }
+
+ public abstract void dispatch(BlueZEventListener listener);
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + ": " + dbusPath;
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/BlueZEventListener.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/BlueZEventListener.java
new file mode 100644
index 000000000..24902862f
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/BlueZEventListener.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This is the listener interface for BlueZEvents.
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public interface BlueZEventListener {
+
+ public void onDBusBlueZEvent(BlueZEvent event);
+
+ public default void onDiscoveringChanged(AdapterDiscoveringChangedEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onPoweredChange(AdapterPoweredChangedEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onRssiUpdate(RssiEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onTxPowerUpdate(TXPowerEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onCharacteristicNotify(CharacteristicUpdateEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onManufacturerDataUpdate(ManufacturerDataEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onConnectedStatusUpdate(ConnectedEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onNameUpdate(NameEvent event) {
+ onDBusBlueZEvent(event);
+ }
+
+ public default void onServicesResolved(ServicesResolvedEvent event) {
+ onDBusBlueZEvent(event);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/CharacteristicUpdateEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/CharacteristicUpdateEvent.java
new file mode 100644
index 000000000..14526280c
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/CharacteristicUpdateEvent.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This event is triggered when a update notification is received for a characteristic.
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class CharacteristicUpdateEvent extends BlueZEvent {
+
+ private byte[] data;
+
+ public CharacteristicUpdateEvent(String dbusPath, byte[] data) {
+ super(dbusPath);
+ this.data = data;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onCharacteristicNotify(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ConnectedEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ConnectedEvent.java
new file mode 100644
index 000000000..75a247d83
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ConnectedEvent.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This event is triggered when a bluetooth device's 'Connected' property changes.
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class ConnectedEvent extends BlueZEvent {
+
+ private boolean connected;
+
+ public ConnectedEvent(String dbusPath, boolean connected) {
+ super(dbusPath);
+ this.connected = connected;
+ }
+
+ public boolean isConnected() {
+ return connected;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onConnectedStatusUpdate(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ManufacturerDataEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ManufacturerDataEvent.java
new file mode 100644
index 000000000..255bba6f8
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ManufacturerDataEvent.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This event is triggered when an update to a device's manufacturer data is received.
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class ManufacturerDataEvent extends BlueZEvent {
+
+ private Map data;
+
+ public ManufacturerDataEvent(String dbusPath, Map data) {
+ super(dbusPath);
+ this.data = data;
+ }
+
+ public Map getData() {
+ return data;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onManufacturerDataUpdate(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/NameEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/NameEvent.java
new file mode 100644
index 000000000..08b06e3f0
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/NameEvent.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This event is triggered when a device's 'Name' bluez property changes
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class NameEvent extends BlueZEvent {
+
+ private String name;
+
+ public NameEvent(String dbusPath, String name) {
+ super(dbusPath);
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onNameUpdate(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/RssiEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/RssiEvent.java
new file mode 100644
index 000000000..02bd858f4
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/RssiEvent.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This event is triggered when bluetooth advertisement packet is picked up from a device.
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class RssiEvent extends BlueZEvent {
+
+ private short rssi;
+
+ public RssiEvent(String dbusPath, short rssi) {
+ super(dbusPath);
+ this.rssi = rssi;
+ }
+
+ public short getRssi() {
+ return rssi;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onRssiUpdate(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ServicesResolvedEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ServicesResolvedEvent.java
new file mode 100644
index 000000000..006a51ae8
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/ServicesResolvedEvent.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This event is triggered when a device's GATT services get resovled/unresolved.
+ * Services become resolved after connecting to a device and become unresolved
+ * either due to error or connection issues.
+ *
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class ServicesResolvedEvent extends BlueZEvent {
+
+ private boolean resolved;
+
+ public ServicesResolvedEvent(String dbusPath, boolean resolved) {
+ super(dbusPath);
+ this.resolved = resolved;
+ }
+
+ public boolean isResolved() {
+ return resolved;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onServicesResolved(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/TXPowerEvent.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/TXPowerEvent.java
new file mode 100644
index 000000000..c8b88d560
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/events/TXPowerEvent.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal.events;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This event is triggered when a device's 'TxPower' property is changed, typically due to receiving an advertisement
+ * packet from the device.
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class TXPowerEvent extends BlueZEvent {
+
+ private short txPower;
+
+ public TXPowerEvent(String dbusPath, short txpower) {
+ super(dbusPath);
+ this.txPower = txpower;
+ }
+
+ public short getTxPower() {
+ return this.txPower;
+ }
+
+ @Override
+ public void dispatch(BlueZEventListener listener) {
+ listener.onTxPowerUpdate(this);
+ }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/package-info.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/package-info.java
deleted file mode 100644
index 40479c28e..000000000
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-@org.osgi.annotation.bundle.Header(name = org.osgi.framework.Constants.BUNDLE_NATIVECODE, value = "lib/armv6hf/libjavatinyb.so;lib/armv6hf/libtinyb.so;processor=arm;osname=linux, lib/x86-64/libjavatinyb.so;lib/x86-64/libtinyb.so;processor=amd64;osname=linux, *")
-@org.osgi.annotation.bundle.Header(name = "Specification-Version", value = "0.5.0-28-gac6d308.0.5.0-28-gac6d308")
-package org.openhab.binding.bluetooth.bluez;
-
-/**
- * Additional information for BlueZ package
- *
- * @author Jan N. Klug - Initial contribution
- *
- */
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/armv6hf/libjavatinyb.so b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/armv6hf/libjavatinyb.so
deleted file mode 100644
index b1b5da17f..000000000
Binary files a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/armv6hf/libjavatinyb.so and /dev/null differ
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/armv6hf/libtinyb.so b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/armv6hf/libtinyb.so
deleted file mode 100644
index bf444a7f1..000000000
Binary files a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/armv6hf/libtinyb.so and /dev/null differ
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/x86-64/libjavatinyb.so b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/x86-64/libjavatinyb.so
deleted file mode 100644
index 308abed35..000000000
Binary files a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/x86-64/libjavatinyb.so and /dev/null differ
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/x86-64/libtinyb.so b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/x86-64/libtinyb.so
deleted file mode 100644
index 430f3b6c4..000000000
Binary files a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/lib/x86-64/libtinyb.so and /dev/null differ
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/test/java/org/openhab/binding/bluetooth/bluez/internal/BlueZEventTest.java b/bundles/org.openhab.binding.bluetooth.bluez/src/test/java/org/openhab/binding/bluetooth/bluez/internal/BlueZEventTest.java
new file mode 100644
index 000000000..4d2572bd1
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/test/java/org/openhab/binding/bluetooth/bluez/internal/BlueZEventTest.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.bluez.internal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.bluetooth.BluetoothAddress;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEvent;
+import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEventListener;
+
+/**
+ *
+ * @author Benjamin Lafois - Initial Contribution
+ * @author Connor Petty - Added additional test cases
+ */
+public class BlueZEventTest {
+
+ @Test
+ public void testDbusPathParser0() {
+ BlueZEvent event = new DummyBlueZEvent("/org/bluez/hci0/dsqdsq/ds/dd");
+ assertEquals("hci0", event.getAdapterName());
+ assertNull(event.getDevice());
+ }
+
+ @Test
+ public void testDbusPathParser1() {
+ BlueZEvent event = new DummyBlueZEvent("/org/bluez/hci0/dev_00_CC_3F_B2_7E_60");
+ assertEquals("hci0", event.getAdapterName());
+ assertEquals(new BluetoothAddress("00:CC:3F:B2:7E:60"), event.getDevice());
+ }
+
+ @Test
+ public void testDbusPathParser2() {
+ BlueZEvent event = new DummyBlueZEvent("/org/bluez/hci0/dev_A4_34_D9_ED_D3_74/service0026/char0027");
+ assertEquals("hci0", event.getAdapterName());
+ assertEquals(new BluetoothAddress("A4:34:D9:ED:D3:74"), event.getDevice());
+ }
+
+ @Test
+ public void testDbusPathParser3() {
+ BlueZEvent event = new DummyBlueZEvent("/org/bluez/hci0/dev_00_CC_3F_B2_7E_60/");
+ assertEquals("hci0", event.getAdapterName());
+ assertEquals(new BluetoothAddress("00:CC:3F:B2:7E:60"), event.getDevice());
+ }
+
+ @Test
+ public void testDbusPathParser4() {
+ BlueZEvent event = new DummyBlueZEvent("/org/bluez/hci0/dev_");
+ assertEquals("hci0", event.getAdapterName());
+ assertNull(event.getDevice());
+ }
+
+ @Test
+ public void testDbusPathParser5() {
+ BlueZEvent event = new DummyBlueZEvent("/org/bluez/hci0/dev_/");
+ assertEquals("hci0", event.getAdapterName());
+ assertNull(event.getDevice());
+ }
+
+ @Test
+ public void testDbusPathParser6() {
+ BlueZEvent event = new DummyBlueZEvent("/org/bluez/hci0");
+ assertEquals("hci0", event.getAdapterName());
+ assertNull(event.getDevice());
+ }
+
+ private static class DummyBlueZEvent extends BlueZEvent {
+
+ public DummyBlueZEvent(String dbusPath) {
+ super(dbusPath);
+ }
+
+ @Override
+ public void dispatch(@NonNull BlueZEventListener listener) {
+ listener.onDBusBlueZEvent(this);
+ }
+ }
+}
diff --git a/features/openhab-addons/src/main/resources/footer.xml b/features/openhab-addons/src/main/resources/footer.xml
index 70dda1493..eeeabd0fd 100644
--- a/features/openhab-addons/src/main/resources/footer.xml
+++ b/features/openhab-addons/src/main/resources/footer.xml
@@ -2,6 +2,7 @@
openhab-runtime-base
openhab-transport-serial
+ mvn:com.github.hypfvieh/bluez-dbus-osgi/0.1.3
mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth/${project.version}
mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.airthings/${project.version}
mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.am43/${project.version}