Fix empty Bluetooth lists in web mixer; advertise it over Bonjour
busctl cannot convert some BlueZ advertisement data (Apple
ManufacturerData, a{qv}) to JSON, so GetManagedObjects failed as soon as
an iPad was paired and the web showed no devices at all. Read only the
needed Device1/Adapter1 properties per object instead. Also publish the
web mixer as an _http._tcp Avahi service.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,22 @@ def objects(service, path):
|
||||
for p, interfaces in raw.items()}
|
||||
|
||||
|
||||
def properties(service, path, interface, names):
|
||||
"""Read selected properties only.
|
||||
|
||||
GetManagedObjects cannot be used for BlueZ: busctl fails to convert some
|
||||
advertisement data (e.g. Apple ManufacturerData a{qv}) to JSON, which
|
||||
would hide every device.
|
||||
"""
|
||||
out = command('busctl', '--system', '--json=short', 'get-property', service, path, interface, *names)
|
||||
return {name: json.loads(line)['data'] for name, line in zip(names, out.splitlines())}
|
||||
|
||||
|
||||
def bluez_paths(pattern):
|
||||
return [p for p in command('busctl', '--system', 'tree', '--list', 'org.bluez').split()
|
||||
if re.fullmatch(pattern, p)]
|
||||
|
||||
|
||||
def pcms():
|
||||
return {p: interfaces['org.bluealsa.PCM1'] for p, interfaces in objects('org.bluealsa', '/org/bluealsa').items()
|
||||
if 'org.bluealsa.PCM1' in interfaces}
|
||||
@@ -141,9 +157,13 @@ def set_input(key, volume=None, mute=None):
|
||||
|
||||
def devices():
|
||||
result = []
|
||||
for path, interfaces in objects('org.bluez', '/').items():
|
||||
props = interfaces.get('org.bluez.Device1')
|
||||
if not props: continue
|
||||
base = ['Address', 'Alias', 'Paired', 'Bonded', 'Connected']
|
||||
for path in bluez_paths(r'/org/bluez/hci\d+/dev_[0-9A-F_]{17}'):
|
||||
try:
|
||||
try: props = properties('org.bluez', path, 'org.bluez.Device1', base + ['UUIDs'])
|
||||
# UUIDs is absent until services are known; devices may also vanish mid-read.
|
||||
except ValueError: props = properties('org.bluez', path, 'org.bluez.Device1', base)
|
||||
except ValueError: continue
|
||||
result.append({'address': props['Address'],'name': props.get('Alias',props['Address']),
|
||||
'bonded': bool(props.get('Bonded')), 'paired': bool(props.get('Paired')),'connected':bool(props.get('Connected')),
|
||||
'output': '0000110b-0000-1000-8000-00805f9b34fb' in props.get('UUIDs',[])})
|
||||
@@ -151,8 +171,8 @@ def devices():
|
||||
|
||||
|
||||
def pairing():
|
||||
adapter = next((i['org.bluez.Adapter1'] for i in objects('org.bluez', '/').values()
|
||||
if 'org.bluez.Adapter1' in i), {})
|
||||
paths = bluez_paths(r'/org/bluez/hci\d+')
|
||||
adapter = properties('org.bluez', paths[0], 'org.bluez.Adapter1', ['Discoverable', 'Alias']) if paths else {}
|
||||
on = bool(adapter.get('Discoverable'))
|
||||
remaining = 0
|
||||
try: remaining = max(0, round(float(PAIRING_UNTIL.read_text()) - time.time()))
|
||||
|
||||
Reference in New Issue
Block a user