added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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_]", "");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
*/
|
||||
@@ -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>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user