[bluetooth] Add support for service data (#10278)
Signed-off-by: Peter Rosenberg <prosenb.dev@gmail.com>
This commit is contained in:
parent
d4c472a04c
commit
34bdc21370
@ -37,6 +37,7 @@ 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.ManufacturerDataEvent;
|
||||||
import org.openhab.binding.bluetooth.bluez.internal.events.NameEvent;
|
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.RssiEvent;
|
||||||
|
import org.openhab.binding.bluetooth.bluez.internal.events.ServiceDataEvent;
|
||||||
import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent;
|
import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent;
|
||||||
import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
|
import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
|
||||||
import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
|
import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
|
||||||
@ -358,6 +359,13 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDataUpdate(ServiceDataEvent event) {
|
||||||
|
BluetoothScanNotification notification = new BluetoothScanNotification();
|
||||||
|
notification.setServiceData(event.getData());
|
||||||
|
notifyListeners(BluetoothEventType.SCAN_RECORD, notification);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTxPowerUpdate(TXPowerEvent event) {
|
public void onTxPowerUpdate(TXPowerEvent event) {
|
||||||
this.txPower = (int) event.getTxPower();
|
this.txPower = (int) event.getTxPower();
|
||||||
|
|||||||
@ -34,6 +34,7 @@ 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.ManufacturerDataEvent;
|
||||||
import org.openhab.binding.bluetooth.bluez.internal.events.NameEvent;
|
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.RssiEvent;
|
||||||
|
import org.openhab.binding.bluetooth.bluez.internal.events.ServiceDataEvent;
|
||||||
import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent;
|
import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent;
|
||||||
import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
|
import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
|
||||||
import org.openhab.core.common.ThreadPoolManager;
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
@ -46,6 +47,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Benjamin Lafois - Initial contribution and API
|
* @author Benjamin Lafois - Initial contribution and API
|
||||||
* @author Connor Petty - Code cleanup
|
* @author Connor Petty - Code cleanup
|
||||||
|
* @author Peter Rosenberg - Add support for ServiceData
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHandler {
|
public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHandler {
|
||||||
@ -115,6 +117,9 @@ public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHand
|
|||||||
case "manufacturerdata":
|
case "manufacturerdata":
|
||||||
onManufacturerDataUpdate(dbusPath, variant);
|
onManufacturerDataUpdate(dbusPath, variant);
|
||||||
break;
|
break;
|
||||||
|
case "servicedata":
|
||||||
|
onServiceDataUpdate(dbusPath, variant);
|
||||||
|
break;
|
||||||
case "powered":
|
case "powered":
|
||||||
onPoweredUpdate(dbusPath, variant);
|
onPoweredUpdate(dbusPath, variant);
|
||||||
break;
|
break;
|
||||||
@ -196,6 +201,28 @@ public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onServiceDataUpdate(String dbusPath, Variant<?> variant) {
|
||||||
|
Map<String, byte[]> serviceData = 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 String && value instanceof Variant<?>) {
|
||||||
|
value = ((Variant<?>) value).getValue();
|
||||||
|
if (value instanceof byte[]) {
|
||||||
|
serviceData.put(((String) key), ((byte[]) value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!serviceData.isEmpty()) {
|
||||||
|
notifyListeners(new ServiceDataEvent(dbusPath, serviceData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onValueUpdate(String dbusPath, Variant<?> variant) {
|
private void onValueUpdate(String dbusPath, Variant<?> variant) {
|
||||||
Object value = variant.getValue();
|
Object value = variant.getValue();
|
||||||
if (value instanceof byte[]) {
|
if (value instanceof byte[]) {
|
||||||
|
|||||||
@ -49,6 +49,10 @@ public interface BlueZEventListener {
|
|||||||
onDBusBlueZEvent(event);
|
onDBusBlueZEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public default void onServiceDataUpdate(ServiceDataEvent event) {
|
||||||
|
onDBusBlueZEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
public default void onConnectedStatusUpdate(ConnectedEvent event) {
|
public default void onConnectedStatusUpdate(ConnectedEvent event) {
|
||||||
onDBusBlueZEvent(event);
|
onDBusBlueZEvent(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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 service data is received.
|
||||||
|
*
|
||||||
|
* @author Peter Rosenberg - Initial Contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ServiceDataEvent extends BlueZEvent {
|
||||||
|
|
||||||
|
final private Map<String, byte[]> data;
|
||||||
|
|
||||||
|
public ServiceDataEvent(String dbusPath, Map<String, byte[]> data) {
|
||||||
|
super(dbusPath);
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, byte[]> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(BlueZEventListener listener) {
|
||||||
|
listener.onServiceDataUpdate(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -28,7 +28,9 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||||||
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
|
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
|
||||||
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
|
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
|
||||||
import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
|
import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
|
||||||
|
import org.openhab.binding.bluetooth.BluetoothService;
|
||||||
import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
|
import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
|
||||||
|
import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
|
||||||
import org.openhab.bluetooth.gattparser.BluetoothGattParser;
|
import org.openhab.bluetooth.gattparser.BluetoothGattParser;
|
||||||
import org.openhab.bluetooth.gattparser.BluetoothGattParserFactory;
|
import org.openhab.bluetooth.gattparser.BluetoothGattParserFactory;
|
||||||
import org.openhab.bluetooth.gattparser.FieldHolder;
|
import org.openhab.bluetooth.gattparser.FieldHolder;
|
||||||
@ -57,7 +59,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* channels based off of a bluetooth device's GATT characteristics.
|
* channels based off of a bluetooth device's GATT characteristics.
|
||||||
*
|
*
|
||||||
* @author Connor Petty - Initial contribution
|
* @author Connor Petty - Initial contribution
|
||||||
* @author Peter Rosenberg - Use notifications
|
* @author Peter Rosenberg - Use notifications, add support for ServiceData
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@ -159,6 +161,71 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
|||||||
getCharacteristicHandler(characteristic).handleCharacteristicUpdate(value);
|
getCharacteristicHandler(characteristic).handleCharacteristicUpdate(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
|
||||||
|
super.onScanRecordReceived(scanNotification);
|
||||||
|
|
||||||
|
handleServiceData(scanNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service data is specified in the "Core Specification Supplement"
|
||||||
|
* https://www.bluetooth.com/specifications/specs/
|
||||||
|
* 1.11 SERVICE DATA
|
||||||
|
* <p>
|
||||||
|
* Broadcast configuration to configure what to advertise in service data
|
||||||
|
* is specified in "Core Specification 5.3"
|
||||||
|
* https://www.bluetooth.com/specifications/specs/
|
||||||
|
* Part G: GENERIC ATTRIBUTE PROFILE (GATT): 2.7 CONFIGURED BROADCAST
|
||||||
|
*
|
||||||
|
* This method extracts ServiceData, finds the Service and the Characteristic it belongs
|
||||||
|
* to and notifies a value change.
|
||||||
|
*
|
||||||
|
* @param scanNotification to get serviceData from
|
||||||
|
*/
|
||||||
|
private void handleServiceData(BluetoothScanNotification scanNotification) {
|
||||||
|
Map<String, byte[]> serviceData = scanNotification.getServiceData();
|
||||||
|
if (serviceData != null) {
|
||||||
|
for (String uuidStr : serviceData.keySet()) {
|
||||||
|
@Nullable
|
||||||
|
BluetoothService service = device.getServices(UUID.fromString(uuidStr));
|
||||||
|
if (service == null) {
|
||||||
|
logger.warn("Service with UUID {} not found on {}, ignored.", uuidStr,
|
||||||
|
scanNotification.getAddress());
|
||||||
|
} else {
|
||||||
|
// The ServiceData contains the UUID of the Service but no identifier of the
|
||||||
|
// Characteristic the data belongs to.
|
||||||
|
// Check which Characteristic within this service has the `Broadcast` property set
|
||||||
|
// and select this one as the Characteristic to assign the data to.
|
||||||
|
List<BluetoothCharacteristic> broadcastCharacteristics = service.getCharacteristics().stream()
|
||||||
|
.filter((characteristic) -> characteristic
|
||||||
|
.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_BROADCAST))
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
|
||||||
|
if (broadcastCharacteristics.size() == 0) {
|
||||||
|
logger.info(
|
||||||
|
"No Characteristic of service with UUID {} on {} has the broadcast property set, ignored.",
|
||||||
|
uuidStr, scanNotification.getAddress());
|
||||||
|
} else if (broadcastCharacteristics.size() > 1) {
|
||||||
|
logger.warn(
|
||||||
|
"Multiple Characteristics of service with UUID {} on {} have the broadcast property set what is not supported, ignored.",
|
||||||
|
uuidStr, scanNotification.getAddress());
|
||||||
|
} else {
|
||||||
|
BluetoothCharacteristic broadcastCharacteristic = broadcastCharacteristics.get(0);
|
||||||
|
|
||||||
|
byte[] value = serviceData.get(uuidStr);
|
||||||
|
if (value != null) {
|
||||||
|
onCharacteristicUpdate(broadcastCharacteristic, value);
|
||||||
|
} else {
|
||||||
|
logger.warn("Service Data for Service with UUID {} on {} is null, ignored.", uuidStr,
|
||||||
|
scanNotification.getAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateThingChannels() {
|
private void updateThingChannels() {
|
||||||
List<Channel> channels = device.getServices().stream()//
|
List<Channel> channels = device.getServices().stream()//
|
||||||
.flatMap(service -> service.getCharacteristics().stream())//
|
.flatMap(service -> service.getCharacteristics().stream())//
|
||||||
|
|||||||
@ -224,6 +224,11 @@ public class BeaconBluetoothHandler extends BaseThingHandler implements Bluetoot
|
|||||||
int rssi = scanNotification.getRssi();
|
int rssi = scanNotification.getRssi();
|
||||||
if (rssi != Integer.MIN_VALUE) {
|
if (rssi != Integer.MIN_VALUE) {
|
||||||
updateRSSI(rssi);
|
updateRSSI(rssi);
|
||||||
|
} else {
|
||||||
|
// we received a scan notification from this device so it is online
|
||||||
|
// TODO how can we detect if the underlying bluez stack is still receiving advertising packets when there
|
||||||
|
// are no changes?
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -85,11 +85,21 @@ public class ConnectedBluetoothHandler extends BeaconBluetoothHandler {
|
|||||||
idleDisconnectDelay = ((Number) idleDisconnectDelayRaw).intValue();
|
idleDisconnectDelay = ((Number) idleDisconnectDelayRaw).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alwaysConnected) {
|
// Start the recurrent job if the device is always connected
|
||||||
|
// or if the Services where not yet discovered.
|
||||||
|
// If the device is not always connected, the job will be terminated
|
||||||
|
// after successful connection and the device disconnected after Service
|
||||||
|
// discovery in `onServicesDiscovered()`.
|
||||||
|
if (alwaysConnected || !device.isServicesDiscovered()) {
|
||||||
reconnectJob = connectionTaskExecutor.scheduleWithFixedDelay(() -> {
|
reconnectJob = connectionTaskExecutor.scheduleWithFixedDelay(() -> {
|
||||||
try {
|
try {
|
||||||
if (device.getConnectionState() != ConnectionState.CONNECTED) {
|
if (device.getConnectionState() != ConnectionState.CONNECTED) {
|
||||||
if (!device.connect()) {
|
if (device.connect()) {
|
||||||
|
if (!alwaysConnected) {
|
||||||
|
cancel(reconnectJob, false);
|
||||||
|
reconnectJob = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
logger.debug("Failed to connect to {}", address);
|
logger.debug("Failed to connect to {}", address);
|
||||||
}
|
}
|
||||||
// we do not set the Thing status here, because we will anyhow receive a call to
|
// we do not set the Thing status here, because we will anyhow receive a call to
|
||||||
@ -326,4 +336,14 @@ public class ConnectedBluetoothHandler extends BeaconBluetoothHandler {
|
|||||||
descriptor.getUuid(), address);
|
descriptor.getUuid(), address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServicesDiscovered() {
|
||||||
|
super.onServicesDiscovered();
|
||||||
|
|
||||||
|
if (!alwaysConnected && device.getConnectionState() == ConnectionState.CONNECTED) {
|
||||||
|
// disconnect when the device was only connected to discover the Services.
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,10 +12,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bluetooth.notification;
|
package org.openhab.binding.bluetooth.notification;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link BluetoothScanNotification} provides a notification of a received scan packet
|
* The {@link BluetoothScanNotification} provides a notification of a received scan packet
|
||||||
*
|
*
|
||||||
* @author Chris Jackson - Initial contribution
|
* @author Chris Jackson - Initial contribution
|
||||||
|
* @author Peter Rosenberg - Add support for ServiceData
|
||||||
*/
|
*/
|
||||||
public class BluetoothScanNotification extends BluetoothNotification {
|
public class BluetoothScanNotification extends BluetoothNotification {
|
||||||
/**
|
/**
|
||||||
@ -33,6 +36,13 @@ public class BluetoothScanNotification extends BluetoothNotification {
|
|||||||
*/
|
*/
|
||||||
private byte[] manufacturerData = null;
|
private byte[] manufacturerData = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service data.
|
||||||
|
* Key: UUID of the service
|
||||||
|
* Value: Data of the characteristic
|
||||||
|
*/
|
||||||
|
private Map<String, byte[]> serviceData = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The beacon type
|
* The beacon type
|
||||||
*/
|
*/
|
||||||
@ -106,6 +116,14 @@ public class BluetoothScanNotification extends BluetoothNotification {
|
|||||||
return manufacturerData;
|
return manufacturerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setServiceData(Map<String, byte[]> serviceData) {
|
||||||
|
this.serviceData = serviceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, byte[]> getServiceData() {
|
||||||
|
return serviceData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the beacon type for this packet
|
* Sets the beacon type for this packet
|
||||||
*
|
*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user