From 2683c1ef5eadcfd68bbf0809da02d15d05858375 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Tue, 9 Mar 2021 17:27:26 +0300 Subject: [PATCH] backend-native: report battery status to BlueZ --- spa/plugins/bluez5/backend-native.c | 239 +++++++++++++++++++++++++++- spa/plugins/bluez5/defs.h | 3 +- 2 files changed, 239 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 00bcfe526..b034072a5 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -47,6 +47,17 @@ #define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles" +#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" +#define DBUS_SIGNAL_INTERFACES_ADDED "InterfacesAdded" +#define DBUS_SIGNAL_INTERFACES_REMOVED "InterfacesRemoved" +#define DBUS_SIGNAL_PROPERTIES_CHANGED "PropertiesChanged" + +#define BLUEZ_INTERFACE_BATTERY_PROVIDER "org.bluez.BatteryProvider1" +#define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER "org.bluez.BatteryProviderManager1" + +#define PIPEWIRE_BATTERY_PROVIDER "/org/freedesktop/pipewire/battery" +#define BATTERY_PROVIDER_UNAVAILABLE 255 + struct spa_bt_backend { struct spa_bt_monitor *monitor; @@ -62,6 +73,8 @@ struct spa_bt_backend { struct spa_list rfcomm_list; unsigned int msbc_support_enabled_in_config:1; + unsigned int has_battery_provider; + unsigned int battery_provider_unavailable; }; struct transport_data { @@ -154,6 +167,205 @@ finish: return t; } +// Working with BlueZ Battery Provider. Developed using https://github.com/dgreid/adhd/commit/655b58f as example of DBus calls. +static char *battery_name(struct spa_bt_device *d) +{ + char *path = malloc(strlen(PIPEWIRE_BATTERY_PROVIDER) + strlen(d->path) + 1); + sprintf(path, PIPEWIRE_BATTERY_PROVIDER"%s", d->path); + + // /org/freedesktop/pipewire/battery/org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX + return path; +} + +static void write_battery(DBusMessageIter *iter, struct spa_bt_device *device) +{ + DBusMessageIter dict, entry, variant; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + const char *prop_percentage = "Percentage"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &prop_percentage); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &device->battery); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *prop_device = "Device"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &prop_device); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_OBJECT_PATH_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_OBJECT_PATH, &device->path); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_close_container(iter, &dict); +} + +static void update_battery(struct spa_bt_device *device) +{ + const char *name = battery_name(device); + spa_log_debug(device->monitor->log, NAME": updating battery: %s", name); + + DBusMessage *msg; + DBusMessageIter iter; + + msg = dbus_message_new_signal(name, + DBUS_INTERFACE_PROPERTIES, + DBUS_SIGNAL_PROPERTIES_CHANGED); + + dbus_message_iter_init_append(msg, &iter); + const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &interface); + + write_battery(&iter, device); + + if (!dbus_connection_send(device->monitor->conn, msg, NULL)) + spa_log_error(device->monitor->log, NAME": Error updating battery"); + + dbus_message_unref(msg); +} + +static void create_battery(struct spa_bt_device *device) { + DBusMessage *msg; + DBusMessageIter iter, entry, dict; + msg = dbus_message_new_signal(PIPEWIRE_BATTERY_PROVIDER, + DBUS_INTERFACE_OBJECT_MANAGER, + DBUS_SIGNAL_INTERFACES_ADDED); + + dbus_message_iter_init_append(msg, &iter); + const char *bat_name = battery_name(device); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &bat_name); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &interface); + + write_battery(&entry, device); + + dbus_message_iter_close_container(&dict, &entry); + dbus_message_iter_close_container(&iter, &dict); + + if (!dbus_connection_send(device->monitor->conn, msg, NULL)) { + spa_log_error(device->monitor->backend_native->log, NAME": Failed to create virtual battery for %s", device->address); + return; + } + + dbus_message_unref(msg); + + spa_log_debug(device->monitor->backend_native->log, NAME": Created virtual battery for %s", device->address); + device->has_battery = true; +} + +static void remove_battery(struct spa_bt_device *d) { + if (!d->monitor->backend_native->has_battery_provider) return; + + spa_log_debug(d->monitor->backend_native->log, NAME": Removing battery provider: %s", battery_name(d)); + + DBusMessage *m = dbus_message_new_signal(PIPEWIRE_BATTERY_PROVIDER, + DBUS_INTERFACE_OBJECT_MANAGER, + DBUS_SIGNAL_INTERFACES_REMOVED); + + DBusMessageIter i, entry; + + dbus_message_iter_init_append(m, &i); + const char *bat_name = battery_name(d); + dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, + &bat_name); + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &interface); + dbus_message_iter_close_container(&i, &entry); + + if (!dbus_connection_send(d->monitor->conn, m, NULL)) { + spa_log_error(d->monitor->backend_native->log, NAME": sending " DBUS_SIGNAL_INTERFACES_REMOVED " failed"); + } + + dbus_message_unref(m); + + d->has_battery = false; +} + +static void on_battery_provider_registered(DBusPendingCall *pending_call, + void *data) +{ + DBusMessage *reply; + struct spa_bt_device *device = data; + + reply = dbus_pending_call_steal_reply(pending_call); + dbus_pending_call_unref(pending_call); + + if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(device->monitor->log, NAME": Failed to register battery provider. Error: %s", dbus_message_get_error_name(reply)); + spa_log_error(device->monitor->log, NAME": BlueZ battery provider is not available, won't retry to register it"); + device->monitor->backend_native->battery_provider_unavailable = true; + dbus_message_unref(reply); + return; + } + + spa_log_debug(device->monitor->log, NAME": Registered battery provider"); + + device->monitor->backend_native->has_battery_provider = true; + + if (!device->has_battery) + create_battery(device); + + dbus_message_unref(reply); +} + +static void register_battery_provider(DBusConnection *conn, struct spa_bt_device *device) +{ + DBusMessage *method_call; + DBusMessageIter message_iter; + DBusPendingCall *pending_call; + + method_call = dbus_message_new_method_call( + BLUEZ_SERVICE, device->adapter_path, + BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER, + "RegisterBatteryProvider"); + + if (!method_call) { + spa_log_error(device->monitor->log, NAME": Failed to register battery provider"); + return; + } + + dbus_message_iter_init_append(method_call, &message_iter); + const char *object_path = PIPEWIRE_BATTERY_PROVIDER; + dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH, + &object_path); + + if (!dbus_connection_send_with_reply(conn, method_call, &pending_call, + DBUS_TIMEOUT_USE_DEFAULT)) { + dbus_message_unref(method_call); + spa_log_error(device->monitor->log, NAME": Failed to register battery provider"); + return; + } + + dbus_message_unref(method_call); + + if (!pending_call) { + spa_log_error(device->monitor->log, NAME": Failed to register battery provider"); + return; + } + + if (!dbus_pending_call_set_notify( + pending_call, on_battery_provider_registered, + device, NULL)) { + spa_log_error(device->monitor->log, "Failed to register battery provider"); + dbus_pending_call_cancel(pending_call); + dbus_pending_call_unref(pending_call); + } +} + static void rfcomm_free(struct rfcomm *rfcomm) { spa_list_remove(&rfcomm->link); @@ -477,8 +689,29 @@ static bool rfcomm_hfp_ag(struct spa_source *source, char* buf) if (k == SPA_BT_HFP_HF_IPHONEACCEV_KEY_BATTERY) { // Battery level is reported in range of 0-9, convert to 0-100% - rfcomm->transport->battery = (v + 1) * 10; - spa_log_debug(backend->log, NAME": battery level: %d%%", rfcomm->transport->battery); + uint8_t level = (v + 1) * 10; + spa_log_debug(backend->log, NAME": battery level: %d%%", level); + + // TODO: report without Battery Provider (using props) + + // BlueZ likely is running without battery provider support, don't try to report battery + if (backend->battery_provider_unavailable) return true; + + // If everything is initialized and battery level has not changed we don't need to send anything to BlueZ + if (backend->has_battery_provider && rfcomm->device->has_battery && rfcomm->device->battery == level) return true; + + rfcomm->device->battery = level; + + if (!backend->has_battery_provider) { + // No provider: register it, create battery in hook + register_battery_provider(backend->conn, rfcomm->device); + } else if (!rfcomm->device->has_battery) { + // Have provider but no battery: create battery, it will already contain percentage + create_battery(rfcomm->device); + } else { + // Just update existing battery percentage + update_battery(rfcomm->device); + } } } } else if (strncmp(buf, "AT+APLSIRI?", 11) == 0) { @@ -1141,6 +1374,8 @@ static DBusHandlerResult profile_request_disconnection(DBusConnection *conn, DBu return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } + remove_battery(d); + spa_list_for_each_safe(rfcomm, rfcomm_tmp, &backend->rfcomm_list, link) { if (rfcomm->device == d && rfcomm->profile == profile) { if (rfcomm->source.loop) diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index b07a03aad..44a065e2e 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -433,6 +433,8 @@ struct spa_bt_device { struct spa_list remote_endpoint_list; struct spa_list transport_list; struct spa_list codec_switch_list; + uint8_t battery; + int has_battery; struct spa_hook_list listener_list; bool added; @@ -503,7 +505,6 @@ struct spa_bt_transport { enum spa_bt_transport_state state; const struct a2dp_codec *a2dp_codec; unsigned int codec; - uint8_t battery; void *configuration; int configuration_len;