2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bluetooth.bluez.internal;
15 import java.util.HashMap;
18 import java.util.concurrent.CopyOnWriteArraySet;
19 import java.util.concurrent.ScheduledExecutorService;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.freedesktop.dbus.DBusMap;
24 import org.freedesktop.dbus.handlers.AbstractPropertiesChangedHandler;
25 import org.freedesktop.dbus.interfaces.Properties.PropertiesChanged;
26 import org.freedesktop.dbus.types.UInt16;
27 import org.freedesktop.dbus.types.Variant;
28 import org.openhab.binding.bluetooth.bluez.internal.events.AdapterDiscoveringChangedEvent;
29 import org.openhab.binding.bluetooth.bluez.internal.events.AdapterPoweredChangedEvent;
30 import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEvent;
31 import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEventListener;
32 import org.openhab.binding.bluetooth.bluez.internal.events.CharacteristicUpdateEvent;
33 import org.openhab.binding.bluetooth.bluez.internal.events.ConnectedEvent;
34 import org.openhab.binding.bluetooth.bluez.internal.events.ManufacturerDataEvent;
35 import org.openhab.binding.bluetooth.bluez.internal.events.NameEvent;
36 import org.openhab.binding.bluetooth.bluez.internal.events.RssiEvent;
37 import org.openhab.binding.bluetooth.bluez.internal.events.ServiceDataEvent;
38 import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent;
39 import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
40 import org.openhab.core.common.ThreadPoolManager;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * This is the PropertiesChangedHandler subclass used by the binding to handle/dispatch property change events
48 * @author Benjamin Lafois - Initial contribution and API
49 * @author Connor Petty - Code cleanup
50 * @author Peter Rosenberg - Add support for ServiceData
53 public class BlueZPropertiesChangedHandler extends AbstractPropertiesChangedHandler {
55 private final Logger logger = LoggerFactory.getLogger(BlueZPropertiesChangedHandler.class);
57 private final Set<BlueZEventListener> listeners = new CopyOnWriteArraySet<>();
59 private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
61 public void addListener(BlueZEventListener listener) {
62 this.listeners.add(listener);
65 public void removeListener(BlueZEventListener listener) {
66 this.listeners.remove(listener);
69 private void notifyListeners(BlueZEvent event) {
70 for (BlueZEventListener listener : this.listeners) {
71 event.dispatch(listener);
76 public void handle(@Nullable PropertiesChanged properties) {
77 if (properties == null || properties.getPropertiesChanged() == null) {
78 logger.debug("Null properties. Skipping.");
81 Map<@Nullable String, @Nullable Variant<?>> changedProperties = properties.getPropertiesChanged();
82 if (changedProperties == null) {
83 logger.debug("Null properties changed. Skipping.");
87 // do this asynchronously so that we don't slow things down for the dbus event dispatcher
88 scheduler.execute(() -> {
90 String dbusPath = properties.getPath();
91 changedProperties.forEach((key, variant) -> {
92 if (key == null || variant == null) {
95 switch (key.toLowerCase()) {
98 onRSSIUpdate(dbusPath, variant);
102 onTXPowerUpdate(dbusPath, variant);
105 // Characteristc value updated
106 onValueUpdate(dbusPath, variant);
109 onConnectedUpdate(dbusPath, variant);
112 onNameUpdate(dbusPath, variant);
117 case "manufacturerdata":
118 onManufacturerDataUpdate(dbusPath, variant);
121 onServiceDataUpdate(dbusPath, variant);
124 onPoweredUpdate(dbusPath, variant);
127 onDiscoveringUpdate(dbusPath, variant);
129 case "servicesresolved":
130 onServicesResolved(dbusPath, variant);
135 logger.debug("PropertiesPath: {}", dbusPath);
136 logger.debug("PropertiesChanged: {}", changedProperties);
140 private void onDiscoveringUpdate(String dbusPath, Variant<?> variant) {
141 Object discovered = variant.getValue();
142 if (discovered instanceof Boolean) {
143 notifyListeners(new AdapterDiscoveringChangedEvent(dbusPath, (boolean) discovered));
147 private void onPoweredUpdate(String dbusPath, Variant<?> variant) {
148 Object powered = variant.getValue();
149 if (powered instanceof Boolean) {
150 notifyListeners(new AdapterPoweredChangedEvent(dbusPath, (boolean) powered));
154 private void onServicesResolved(String dbusPath, Variant<?> variant) {
155 Object resolved = variant.getValue();
156 if (resolved instanceof Boolean) {
157 notifyListeners(new ServicesResolvedEvent(dbusPath, (boolean) resolved));
161 private void onNameUpdate(String dbusPath, Variant<?> variant) {
162 Object name = variant.getValue();
163 if (name instanceof String) {
164 notifyListeners(new NameEvent(dbusPath, (String) name));
168 private void onTXPowerUpdate(String dbusPath, Variant<?> variant) {
169 Object txPower = variant.getValue();
170 if (txPower instanceof Short) {
171 notifyListeners(new TXPowerEvent(dbusPath, (short) txPower));
175 private void onConnectedUpdate(String dbusPath, Variant<?> variant) {
176 Object connected = variant.getValue();
177 if (connected instanceof Boolean) {
178 notifyListeners(new ConnectedEvent(dbusPath, (boolean) connected));
182 private void onManufacturerDataUpdate(String dbusPath, Variant<?> variant) {
183 Map<Short, byte[]> eventData = new HashMap<>();
185 Object map = variant.getValue();
186 if (map instanceof DBusMap) {
187 DBusMap<?, ?> dbm = (DBusMap<?, ?>) map;
188 for (Map.Entry<?, ?> entry : dbm.entrySet()) {
189 Object key = entry.getKey();
190 Object value = entry.getValue();
191 if (key instanceof UInt16 && value instanceof Variant<?>) {
192 value = ((Variant<?>) value).getValue();
193 if (value instanceof byte[]) {
194 eventData.put(((UInt16) key).shortValue(), ((byte[]) value));
199 if (!eventData.isEmpty()) {
200 notifyListeners(new ManufacturerDataEvent(dbusPath, eventData));
204 private void onServiceDataUpdate(String dbusPath, Variant<?> variant) {
205 Map<String, byte[]> serviceData = new HashMap<>();
207 Object map = variant.getValue();
208 if (map instanceof DBusMap) {
209 DBusMap<?, ?> dbm = (DBusMap<?, ?>) map;
210 for (Map.Entry<?, ?> entry : dbm.entrySet()) {
211 Object key = entry.getKey();
212 Object value = entry.getValue();
213 if (key instanceof String && value instanceof Variant<?>) {
214 value = ((Variant<?>) value).getValue();
215 if (value instanceof byte[]) {
216 serviceData.put(((String) key), ((byte[]) value));
221 if (!serviceData.isEmpty()) {
222 notifyListeners(new ServiceDataEvent(dbusPath, serviceData));
226 private void onValueUpdate(String dbusPath, Variant<?> variant) {
227 Object value = variant.getValue();
228 if (value instanceof byte[]) {
229 notifyListeners(new CharacteristicUpdateEvent(dbusPath, (byte[]) value));
233 private void onRSSIUpdate(String dbusPath, Variant<?> variant) {
234 Object rssi = variant.getValue();
235 if (rssi instanceof Short) {
236 notifyListeners(new RssiEvent(dbusPath, (short) rssi));