added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.bluetooth.bluez</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,46 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
== Third-party Content
TinyB Version: 0.5.1
* License: MIT License
* Project: https://github.com/intel-iot-devkit/tinyb
* Source: https://github.com/intel-iot-devkit/tinyb/tree/v0.5.1
== Third-party license(s)
=== MIT License
The MIT License (MIT)
Copyright © 2015-2016 Intel Corporation
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 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.

View File

@@ -0,0 +1,63 @@
# Bluetooth BlueZ Adapter
This extension supports Bluetooth access via BlueZ on Linux (ARMv6hf).
# Setup
Please note that at least BlueZ 5.43 is required, while 5.48 or above are [not (yet) supported](https://github.com/intel-iot-devkit/tinyb/issues/131) either.
Some settings are required to ensure that openHAB has access to Bluez.
To allow openHAB to access Bluez via dbus you need to add the following entry within your dbus configuration in `/etc/dbus-1/system.d/bluetooth.conf`
```xml
<busconfig>
<policy user="root">
...
</policy>
<policy group="bluetooth">
<allow send_destination="org.bluez"/>
</policy>
...
</busconfig>
```
and add openHAB to the "bluetooth" group.
```shell
sudo adduser openhab bluetooth
```
Also, in case you don't want to manually enable your bluetooth adapters with `bluetoothctl`, ensure that it's automatically enabled by setting the option `AutoEnable` in your `/etc/bluetooth/main.conf` to `true`.
Restart running services for changes to take effect.
```
systemctl restart dbus
systemctl restart bluetooth
systemctl restart openhab2
```
## Supported Things
It defines the following bridge type:
| Bridge Type ID | Description |
|----------------|---------------------------------------------------------------------------|
| 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
This is how an BlueZ adapter can be configured textually in a *.things file:
```
Bridge bluetooth:bluez:hci0 [ address="12:34:56:78:90:AB", backgroundDiscovery=false ]
```

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.bluetooth.bluez</artifactId>
<name>openHAB Add-ons :: Bundles :: BlueZ Bluetooth Adapter</name>
<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.bluetooth</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openhab.osgiify</groupId>
<artifactId>intel-iot-devkit.tinyb</artifactId>
<version>0.5.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.bluetooth.bluez-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-bluetooth-bluez" description="Bluetooth Binding Bluez" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.bluez/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,33 @@
/**
* 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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link BlueZAdapterConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Kai Kreuzer - Initial contribution and API
*/
@NonNullByDefault
public class BlueZAdapterConstants {
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BLUEZ = new ThingTypeUID(BluetoothBindingConstants.BINDING_ID, "bluez");
// Properties
public static final String PROPERTY_ADDRESS = "address";
}

View File

@@ -0,0 +1,409 @@
/**
* 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<Short, byte[]> 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<String, byte[]> 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());
}
}
}
}

View File

@@ -0,0 +1,25 @@
/**
* 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 org.openhab.binding.bluetooth.BaseBluetoothBridgeHandlerConfiguration;
/**
* Configuration properties class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class BlueZAdapterConfiguration extends BaseBluetoothBridgeHandlerConfiguration {
public String address;
}

View File

@@ -0,0 +1,172 @@
/**
* 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<BlueZBluetoothDevice> {
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<tinyb.BluetoothDevice> 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();
}
}

View File

@@ -0,0 +1,82 @@
/**
* 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.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
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;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.UID;
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.Component;
/**
* The {@link BlueZHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Kai Kreuzer - Initial contribution and API
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.bluetooth.bluez")
public class BlueZHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.singleton(BlueZAdapterConstants.THING_TYPE_BLUEZ);
private final Map<ThingUID, ServiceRegistration<?>> serviceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(BlueZAdapterConstants.THING_TYPE_BLUEZ)) {
BlueZBridgeHandler handler = new BlueZBridgeHandler((Bridge) thing);
registerBluetoothAdapter(handler);
return handler;
} else {
return null;
}
}
private synchronized void registerBluetoothAdapter(BluetoothAdapter adapter) {
this.serviceRegs.put(adapter.getUID(),
bundleContext.registerService(BluetoothAdapter.class.getName(), adapter, new Hashtable<>()));
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof BluetoothAdapter) {
UID uid = ((BluetoothAdapter) thingHandler).getUID();
ServiceRegistration<?> serviceReg = this.serviceRegs.remove(uid);
if (serviceReg != null) {
serviceReg.unregister();
}
}
}
}

View File

@@ -0,0 +1,83 @@
/**
* 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.discovery;
import java.util.Collections;
import org.openhab.binding.bluetooth.bluez.BlueZAdapterConstants;
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.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tinyb.BluetoothAdapter;
import tinyb.BluetoothManager;
/**
* This is a discovery service, which checks whether we are running on a Linux with a BlueZ stack.
* If this is the case, we create a bridge handler that provides Bluetooth access through BlueZ.
*
* @author Kai Kreuzer - Initial Contribution and API
* @author Hilbrand Bouwkamp - Moved background scan to actual background method
*
*/
@Component(immediate = true, service = DiscoveryService.class, configurationPid = "discovery.bluetooth.bluez")
public class BlueZDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(BlueZDiscoveryService.class);
private BluetoothManager manager;
public BlueZDiscoveryService() {
super(Collections.singleton(BlueZAdapterConstants.THING_TYPE_BLUEZ), 1, true);
}
@Override
protected void startBackgroundDiscovery() {
startScan();
}
@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);
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);
}
}
}
private DiscoveryResult createDiscoveryResult(BluetoothAdapter adapter) {
return DiscoveryResultBuilder.create(new ThingUID(BlueZAdapterConstants.THING_TYPE_BLUEZ, getId(adapter)))
.withLabel("Bluetooth Interface " + adapter.getName())
.withProperty(BlueZAdapterConstants.PROPERTY_ADDRESS, adapter.getAddress())
.withRepresentationProperty(BlueZAdapterConstants.PROPERTY_ADDRESS).build();
}
private String getId(BluetoothAdapter adapter) {
return adapter.getInterfaceName().replaceAll("[^a-zA-Z0-9_]", "");
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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
*
*/

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bluetooth"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="bluez">
<label>Bluetooth BlueZ Adapter</label>
<description>Linux built-in Bluetooth support</description>
<representation-property>address</representation-property>
<config-description>
<parameter name="address" type="text" required="true">
<label>Address</label>
<description>The Bluetooth address of the adapter in format XX:XX:XX:XX:XX:XX</description>
</parameter>
<parameter name="backgroundDiscovery" type="boolean">
<label>Background Discovery</label>
<description>Whether this adapter performs background discovery of Bluetooth devices</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
<parameter name="inactiveDeviceCleanupInterval" type="integer" min="1" unit="s">
<label>Device Cleanup Interval</label>
<description>How often device cleanup is performed</description>
<advanced>true</advanced>
<default>60</default>
</parameter>
<parameter name="inactiveDeviceCleanupThreshold" type="integer" min="1" unit="s">
<label>Device Cleanup Threshold</label>
<description>Timespan a device can remain radio silent before it is eligible for cleanup</description>
<advanced>true</advanced>
<default>300</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>