[bluetooth] Add support for service data (#10278)
Signed-off-by: Peter Rosenberg <prosenb.dev@gmail.com>pull/13323/head
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.NameEvent;
|
||||
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.TXPowerEvent;
|
||||
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
|
||||
public void onTxPowerUpdate(TXPowerEvent event) {
|
||||
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.NameEvent;
|
||||
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.TXPowerEvent;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
|
@ -46,6 +47,7 @@ import org.slf4j.LoggerFactory;
|
|||
*
|
||||
* @author Benjamin Lafois - Initial contribution and API
|
||||
* @author Connor Petty - Code cleanup
|
||||
* @author Peter Rosenberg - Add support for ServiceData
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHandler {
|
||||
|
@ -115,6 +117,9 @@ public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHand
|
|||
case "manufacturerdata":
|
||||
onManufacturerDataUpdate(dbusPath, variant);
|
||||
break;
|
||||
case "servicedata":
|
||||
onServiceDataUpdate(dbusPath, variant);
|
||||
break;
|
||||
case "powered":
|
||||
onPoweredUpdate(dbusPath, variant);
|
||||
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) {
|
||||
Object value = variant.getValue();
|
||||
if (value instanceof byte[]) {
|
||||
|
|
|
@ -49,6 +49,10 @@ public interface BlueZEventListener {
|
|||
onDBusBlueZEvent(event);
|
||||
}
|
||||
|
||||
public default void onServiceDataUpdate(ServiceDataEvent event) {
|
||||
onDBusBlueZEvent(event);
|
||||
}
|
||||
|
||||
public default void onConnectedStatusUpdate(ConnectedEvent 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.BluetoothCharacteristic;
|
||||
import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
|
||||
import org.openhab.binding.bluetooth.BluetoothService;
|
||||
import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
|
||||
import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
|
||||
import org.openhab.bluetooth.gattparser.BluetoothGattParser;
|
||||
import org.openhab.bluetooth.gattparser.BluetoothGattParserFactory;
|
||||
import org.openhab.bluetooth.gattparser.FieldHolder;
|
||||
|
@ -57,7 +59,7 @@ import org.slf4j.LoggerFactory;
|
|||
* channels based off of a bluetooth device's GATT characteristics.
|
||||
*
|
||||
* @author Connor Petty - Initial contribution
|
||||
* @author Peter Rosenberg - Use notifications
|
||||
* @author Peter Rosenberg - Use notifications, add support for ServiceData
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
|
@ -159,6 +161,71 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
|||
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() {
|
||||
List<Channel> channels = device.getServices().stream()//
|
||||
.flatMap(service -> service.getCharacteristics().stream())//
|
||||
|
|
|
@ -224,6 +224,11 @@ public class BeaconBluetoothHandler extends BaseThingHandler implements Bluetoot
|
|||
int rssi = scanNotification.getRssi();
|
||||
if (rssi != Integer.MIN_VALUE) {
|
||||
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();
|
||||
}
|
||||
|
||||
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(() -> {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The {@link BluetoothScanNotification} provides a notification of a received scan packet
|
||||
*
|
||||
* @author Chris Jackson - Initial contribution
|
||||
* @author Peter Rosenberg - Add support for ServiceData
|
||||
*/
|
||||
public class BluetoothScanNotification extends BluetoothNotification {
|
||||
/**
|
||||
|
@ -33,6 +36,13 @@ public class BluetoothScanNotification extends BluetoothNotification {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -106,6 +116,14 @@ public class BluetoothScanNotification extends BluetoothNotification {
|
|||
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
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue