* [PATCH BlueZ 2/3] device: Add Role property
2026-10-08 13:36 [PATCH BlueZ 0/3] Add Role property to org.bluez.Device Kevin Dewald
2026-10-08 13:36 ` [PATCH BlueZ 1/3] org.bluez.Device: Add Role property Kevin Dewald
@ 2026-10-08 13:36 ` Kevin Dewald
2026-10-08 13:36 ` [PATCH BlueZ 3/3] client: Print Role in info Kevin Dewald
2026-10-08 14:37 ` [PATCH BlueZ 0/3] Add Role property to org.bluez.Device Luiz Augusto von Dentz
3 siblings, 0 replies; 6+ messages in thread
From: Kevin Dewald @ 2026-10-08 13:36 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Kevin Dewald
This implements Role property using the initiator flag of the Device
Connected event, which the kernel sets when the adapter created the
connection as central, and emits PropertiesChanged when an LE
connection is added or removed.
Assisted-by: Claude:claude-opus-5.5
---
src/device.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/src/device.c b/src/device.c
index 1ab4e5342..1984b3e1c 100644
--- a/src/device.c
+++ b/src/device.c
@@ -1457,6 +1457,28 @@ static gboolean dev_property_get_connected(const GDBusPropertyTable *property,
return TRUE;
}
+static gboolean dev_property_get_role(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct btd_device *dev = data;
+ const char *role;
+
+ /* The initiator of an LE connection is always the central */
+ role = dev->le_state.initiator ? "peripheral" : "central";
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &role);
+
+ return TRUE;
+}
+
+static gboolean dev_property_exists_role(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct btd_device *dev = data;
+
+ return dev->le_state.connected;
+}
+
static gboolean dev_property_get_uuids(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
@@ -3796,6 +3818,7 @@ static const GDBusPropertyTable device_properties[] = {
{ "CablePairing", "b", dev_property_get_cable_pairing },
{ "RSSI", "n", dev_property_get_rssi, NULL, dev_property_exists_rssi },
{ "Connected", "b", dev_property_get_connected },
+ { "Role", "s", dev_property_get_role, NULL, dev_property_exists_role },
{ "UUIDs", "as", dev_property_get_uuids },
{ "Modalias", "s", dev_property_get_modalias, NULL,
dev_property_exists_modalias },
@@ -3944,6 +3967,10 @@ void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type,
state->connected = true;
state->initiator = flags & BIT(3);
+ if (bdaddr_type != BDADDR_BREDR)
+ g_dbus_emit_property_changed(dbus_conn, dev->path,
+ DEVICE_INTERFACE, "Role");
+
if (dev->le_state.connected && dev->bredr_state.connected)
return;
@@ -4060,6 +4087,10 @@ void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type,
state->initiator = false;
device->general_connect = FALSE;
+ if (bdaddr_type != BDADDR_BREDR)
+ g_dbus_emit_property_changed(dbus_conn, device->path,
+ DEVICE_INTERFACE, "Role");
+
device_set_svc_refreshed(device, false);
if (device->disconn_timer > 0) {
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH BlueZ 3/3] client: Print Role in info
2026-10-08 13:36 [PATCH BlueZ 0/3] Add Role property to org.bluez.Device Kevin Dewald
2026-10-08 13:36 ` [PATCH BlueZ 1/3] org.bluez.Device: Add Role property Kevin Dewald
2026-10-08 13:36 ` [PATCH BlueZ 2/3] device: Add Role property Kevin Dewald
@ 2026-10-08 13:36 ` Kevin Dewald
2026-10-08 14:37 ` [PATCH BlueZ 0/3] Add Role property to org.bluez.Device Luiz Augusto von Dentz
3 siblings, 0 replies; 6+ messages in thread
From: Kevin Dewald @ 2026-10-08 13:36 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Kevin Dewald
This prints the Role property with the likes of info command:
bluetoothctl> info <addr>
...
Connected: yes
Role: central
Assisted-by: Claude:claude-opus-5.5
---
client/main.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/client/main.c b/client/main.c
index f5fdd7d12..7f6b276a8 100644
--- a/client/main.c
+++ b/client/main.c
@@ -2044,6 +2044,7 @@ static void cmd_info(int argc, char *argv[])
print_property(proxy, "Trusted");
print_property(proxy, "Blocked");
print_property(proxy, "Connected");
+ print_property(proxy, "Role");
print_property(proxy, "WakeAllowed");
print_property(proxy, "LegacyPairing");
print_property(proxy, "CablePairing");
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH BlueZ 0/3] Add Role property to org.bluez.Device
2026-10-08 13:36 [PATCH BlueZ 0/3] Add Role property to org.bluez.Device Kevin Dewald
` (2 preceding siblings ...)
2026-10-08 13:36 ` [PATCH BlueZ 3/3] client: Print Role in info Kevin Dewald
@ 2026-10-08 14:37 ` Luiz Augusto von Dentz
3 siblings, 0 replies; 6+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-08 14:37 UTC (permalink / raw)
To: Kevin Dewald; +Cc: linux-bluetooth
Hi Kevin,
On Thu, Oct 8, 2026 at 9:45 AM Kevin Dewald <kevin@simpleble.org> wrote:
>
> Applications acting as a peripheral, or in both roles, cannot tell
> from org.bluez.Device1 whether a connected device is a central that
> connected to the adapter or a peripheral the adapter connected to.
> When several applications share the adapter, a device object can
> appear because of any of them, so the order of events does not answer
> it either.
>
> bluetoothd already knows: the kernel flags connections the adapter
> initiated in the Device Connected event, and bluetoothd keeps that per
> bearer and uses it, e.g. to decide whether to discover the remote GATT
> database. This series exposes it as a Role property, "central" or
> "peripheral", present only while the device is connected over LE, and
> prints it in bluetoothctl info.
>
> It is limited to LE since an LE connection keeps its roles for its
> lifetime, while BR/EDR roles can switch and the switch is not reported
> to bluetoothd. It describes the link only, not GATT client and server
> roles, and nothing is stored. Other stacks report the same per
> connection, e.g. Zephyr's bt_conn_info role and NimBLE's
> ble_gap_conn_desc role.
Yeah, but if a device ever connects using both bearers, I'm afraid it
should be reported on a bearer interface so users don't assume the
Role applies to all bearers. I'm also afraid this may create problems
where the application might interpret this as all procedure roles
being equivalent to the device role (e.g. central acts as GATT client
vs peripheral is a GATT server). This was one of the reasons we didn't
expose this before. And yes, I agree LE connections don't have a role
switch procedure, but in GATT you can switch roles at will or even
have both client and server roles for profiles that support
simultaneous roles, so I'd leave this to be on a per-bearer basis only
to make it clear it is per connection.
> Tested with two btvirt controllers, where the advertising side reports
> the other as "central" and the connecting side reports it as
> "peripheral", and with a real adapter connecting to an nRF52840. The
> property goes away when the connection does.
>
> Kevin Dewald (3):
> org.bluez.Device: Add Role property
> device: Add Role property
> client: Print Role in info
>
> client/main.c | 1 +
> doc/org.bluez.Device.rst | 20 ++++++++++++++++++++
> src/device.c | 31 +++++++++++++++++++++++++++++++
> 3 files changed, 52 insertions(+)
>
> --
> 2.54.0 (Apple Git-157)
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 6+ messages in thread