From 63f62e9f748bd430726c8ebaf4f55f4020cdc61c Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sun, 26 Oct 2025 01:48:28 -0400
Subject: [PATCH] add a python3 DBUs Bluetooth LE Service Discovery beacon
controller
---
Bluetooth_LE_beacon/dbus/uxplay-beacon.py | 456 ++++++++++++++++++++++
CMakeLists.txt | 8 +
README.html | 261 ++++---------
README.md | 197 ++--------
README.txt | 264 ++++---------
uxplay-beacon.1 | 45 +++
uxplay.1 | 6 +-
uxplay.cpp | 21 +-
8 files changed, 705 insertions(+), 553 deletions(-)
create mode 100644 Bluetooth_LE_beacon/dbus/uxplay-beacon.py
create mode 100644 uxplay-beacon.1
diff --git a/Bluetooth_LE_beacon/dbus/uxplay-beacon.py b/Bluetooth_LE_beacon/dbus/uxplay-beacon.py
new file mode 100644
index 0000000..8c4e863
--- /dev/null
+++ b/Bluetooth_LE_beacon/dbus/uxplay-beacon.py
@@ -0,0 +1,456 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# adapted from https://github.com/bluez/bluez/blob/master/test/example-advertisement
+#----------------------------------------------------------------
+# a standalone python-3.6 or later DBus-based AirPlay Service-Discovery Bluetooth LE beacon for UxPlay
+# (c) F. Duncanh, October 2025
+
+import argparse
+import gi
+import os
+import sys
+import psutil
+import struct
+import socket
+
+from gi.repository import GLib
+
+import dbus
+import dbus.exceptions
+import dbus.mainloop.glib
+import dbus.service
+import time
+import threading
+
+ad_manager = None
+airplay_advertisement = None
+port = int(0)
+advmin = int(100)
+advmax = int(100)
+ipv4_str = "ipv4_address"
+index = int(0)
+
+BLUEZ_SERVICE_NAME = 'org.bluez'
+LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
+DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
+DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
+
+LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1'
+
+
+class InvalidArgsException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'
+
+
+class NotSupportedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.NotSupported'
+
+
+class NotPermittedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.NotPermitted'
+
+
+class InvalidValueLengthException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.InvalidValueLength'
+
+
+class FailedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.Failed'
+
+
+class AirPlay_Service_Discovery_Advertisement(dbus.service.Object):
+ PATH_BASE = '/org/bluez/airplay_service_discovery_advertisement'
+
+ def __init__(self, bus, index):
+ self.path = self.PATH_BASE + str(index)
+ self.bus = bus
+ self.manufacturer_data = None
+ self.min_intrvl = 0
+ self.max_intrvl = 0
+
+ dbus.service.Object.__init__(self, bus, self.path)
+
+ def get_properties(self):
+ properties = dict()
+ properties['Type'] = 'broadcast'
+ if self.manufacturer_data is not None:
+ properties['ManufacturerData'] = dbus.Dictionary(
+ self.manufacturer_data, signature='qv')
+ if self.min_intrvl > 0:
+ properties['MinInterval'] = dbus.UInt32(self.min_intrvl)
+ if self.max_intrvl > 0:
+ properties['MaxInterval'] = dbus.UInt32(self.max_intrvl)
+ return {LE_ADVERTISEMENT_IFACE: properties}
+
+ def get_path(self):
+ return dbus.ObjectPath(self.path)
+
+ def add_manufacturer_data(self, manuf_code, manuf_data):
+ if not self.manufacturer_data:
+ self.manufacturer_data = dbus.Dictionary({}, signature='qv')
+ self.manufacturer_data[manuf_code] = dbus.Array(manuf_data, signature='y')
+
+ def set_min_intrvl(self, min_intrvl):
+ if self.min_intrvl == 0:
+ self.min_intrvl = 100
+ self.min_intrvl = max(min_intrvl, 100)
+
+ def set_max_intrvl(self, max_intrvl):
+ if self.max_intrvl == 0:
+ self.max_intrvl = 100
+ self.max_intrvl = max(max_intrvl, 100)
+
+ @dbus.service.method(DBUS_PROP_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, interface):
+ if interface != LE_ADVERTISEMENT_IFACE:
+ raise InvalidArgsException()
+ return self.get_properties()[LE_ADVERTISEMENT_IFACE]
+
+ @dbus.service.method(LE_ADVERTISEMENT_IFACE,
+ in_signature='',
+ out_signature='')
+ def Release(self):
+ print('%s: Released!' % self.path)
+
+
+class AirPlayAdvertisement(AirPlay_Service_Discovery_Advertisement):
+
+ def __init__(self, bus, index, ipv4_str, port, min_intrvl, max_intrvl):
+ AirPlay_Service_Discovery_Advertisement.__init__(self, bus, index)
+ assert port > 0
+ assert port <= 65535
+ mfg_data = bytearray([0x09, 0x08, 0x13, 0x30]) # Apple Data Unit type 9 (Airplay), length 8, flags 0001 0011, seed 30
+ import ipaddress
+ ipv4_address = ipaddress.ip_address(ipv4_str);
+ ipv4 = bytearray(ipv4_address.packed);
+ mfg_data.extend(ipv4)
+ port_bytes = port.to_bytes(2, 'big')
+ mfg_data.extend(port_bytes)
+ self.add_manufacturer_data(0x004c, mfg_data)
+ self.set_min_intrvl(min_intrvl)
+ self.set_max_intrvl(max_intrvl)
+
+
+def register_ad_cb():
+ global ipv4_str
+ global port
+ print(f'AirPlay Service_Discovery Advertisement ({ipv4_str}:{port}) registered')
+
+
+def register_ad_error_cb(error):
+ print('Failed to register advertisement: ' + str(error))
+ mainloop.quit()
+
+
+def find_adapter(bus):
+ remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
+ DBUS_OM_IFACE)
+ objects = remote_om.GetManagedObjects()
+
+ for o, props in objects.items():
+ if LE_ADVERTISING_MANAGER_IFACE in props:
+ return o
+
+ return None
+
+
+def setup_beacon(ipv4_str, port, advmin, advmax, index):
+ global ad_manager
+ global airplay_advertisement
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ bus = dbus.SystemBus()
+ adapter = find_adapter(bus)
+ if not adapter:
+ print('LEAdvertisingManager1 interface not found')
+ return
+ adapter_props = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
+ "org.freedesktop.DBus.Properties")
+
+ adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(1))
+
+ ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
+ LE_ADVERTISING_MANAGER_IFACE)
+ airplay_advertisement = AirPlayAdvertisement(bus, index, ipv4_str, port, advmin, advmax)
+
+def beacon_on():
+ global ad_manager
+ ad_manager.RegisterAdvertisement(airplay_advertisement.get_path(), {},
+ reply_handler=register_ad_cb,
+ error_handler=register_ad_error_cb)
+def beacon_off():
+ global ad_manager
+ global airplay_advertisement
+ ad_manager.UnregisterAdvertisement(airplay_advertisement)
+ print(f'AirPlay Service-Discovery beacon advertisement unregistered')
+ ad_manager = None
+ dbus.service.Object.remove_from_connection(airplay_advertisement)
+ airplay_advertisement = None
+
+#==generic code (non-dbus) below here =============
+
+
+# global variables
+beacon_is_running = False
+beacon_is_pending_on = False
+beacon_is_pending_off = False
+
+def start_beacon():
+ global beacon_is_running
+ global port
+ global ipv4_str
+ global advmin
+ global advmax
+ global index
+ setup_beacon(ipv4_str, port, advmin, advmax, index)
+ beacon_on()
+ beacon_is_running = True
+
+def stop_beacon():
+ global beacon_is_running
+ beacon_off()
+ beacon_is_running = False
+
+def check_process_name(pid, pname):
+ try:
+ process = psutil.Process(pid)
+ if process.name().find(pname,0) == 0:
+ return True
+ else:
+ return False
+ except psutil.NoSuchProcess:
+ return False
+
+def check_pending():
+ global beacon_is_running
+ global beacon_is_pending_on
+ global beacon_is_pending_off
+ if beacon_is_running:
+ #print(f"beacon running")
+ if beacon_is_pending_off:
+ stop_beacon()
+ beacon_is_pending_off = False
+ else:
+ #print(f"beacon not running")
+ if beacon_is_pending_on:
+ start_beacon()
+ beacon_is_pending_on = False
+ return True
+
+
+def check_file_exists(file_path):
+ global port
+ global beacon_is_running
+ global beacon_is_pending_on
+ global beacon_is_pending_off
+
+ if os.path.exists(file_path):
+ with open(file_path, 'rb') as file:
+ data = file.read(2)
+ port = struct.unpack('= min):
+ raise ValueError("AdvMax was smaller than AdvMin")
+ if not (max <= 10240):
+ raise ValueError("AdvMax was larger than 10240 msecs")
+
+
+def main(file_path, ipv4_str_in, advmin_in, advmax_in, index_in):
+ global ipv4_str
+ global advmin
+ global advmax
+ global index
+ ipv4_str = ipv4_str_in
+ advmin = advmin_in
+ advmax = advmax_in
+ index = index_in
+
+ try:
+ while True:
+ try:
+ check_adv_intrvl(advmin, advmax)
+ except ValueError as e:
+ print(f"Error: {e}")
+ raise SystemExit(1)
+
+ GLib.timeout_add_seconds(5, on_timeout, file_path)
+ GLib.timeout_add_seconds(1, check_pending)
+ mainloop = GLib.MainLoop()
+ mainloop.run()
+ except KeyboardInterrupt:
+ print(f"\nExiting ...")
+ sys.exit(0)
+
+
+
+if __name__ == '__main__':
+
+ # Create an ArgumentParser object
+ parser = argparse.ArgumentParser(
+ description='A program that runs an AirPlay service discovery BLE beacon.',
+ epilog='Example: python beacon.py --ipv4 "192.168.1.100" --path "/home/user/ble" --AdvMin 100 --AdvMax 100"'
+ )
+
+ home_dir = os.path.expanduser("~")
+ # Add arguments
+ parser.add_argument(
+ '--file',
+ type=str,
+ default= home_dir + "/.uxplay.beacon",
+ help='beacon startup file (optional): one entry (key, value) per line, e.g. --ipv4 192.168.1.100, (lines startng with with # are ignored)'
+ )
+
+ parser.add_argument(
+ '--path',
+ type=str,
+ default= home_dir + "/.uxplay.ble",
+ help='path to AirPlay server BLE beacon information file (default: ~/.uxplay.ble)).'
+ )
+ parser.add_argument(
+ '--ipv4',
+ type=str,
+ default='use gethostbyname',
+ help='ipv4 address of AirPlay server (default: use gethostbyname).'
+ )
+
+ parser.add_argument(
+ '--AdvMin',
+ type=str,
+ default="0",
+ help='The minimum Advertising Interval (>= 100) units=msec, default 100)'
+ )
+ parser.add_argument(
+ '--AdvMax',
+ type=str,
+ default="0",
+ help='The maximum Advertising Interval (>= AdvMin, <= 10240) units=msec, default 100)'
+ )
+
+ parser.add_argument(
+ '--index',
+ type=str,
+ default="0",
+ help='use index >= 0 to distinguish multiple AirPlay Service Discovery beacons, default 0)'
+ )
+
+ # Parse the command-line argunts
+ args = parser.parse_args()
+ ipv4_str = None
+ path = None
+ advmin = int(100)
+ advmax = int(100)
+ index = int(0)
+
+ if args.file:
+ print(f"Using config file: {args.file}")
+ if os.path.exists(args.file):
+ with open(args.file, 'r') as file:
+ for line in file:
+ stripped_line = line.strip()
+ if stripped_line.startswith('#'):
+ continue
+ parts = stripped_line.partition(" ")
+ part0 = parts[0]
+ part2 = parts[2]
+ key = part0.strip()
+ value = part2.strip()
+ if key == "--path":
+ path = value
+ elif key == "--ipv4":
+ ipv4_str = value
+ elif key == "--AdvMin":
+ if value.isdigit():
+ advmin = int(value)
+ else:
+ print(f"Invalid config file input (--AdvMin) {value} in {args.file}")
+ raise SystemExit(1)
+ elif key == "--AdvMax":
+ if value.isdigit():
+ advmax = int(value)
+ else:
+ print(f"Invalid config file input (--AdvMax) {value} in {args.file}")
+ raise SystemExit(1)
+ elif key == "--index":
+ if value.isdigit():
+ index = int(value)
+ else:
+ print(f"Invalid config file input (--index) {value} in {args.file}")
+ raise SystemExit(1)
+ else:
+ print(f"Unknown key \"{key}\" in config file {args.file}")
+ raise SystemExit(1)
+
+ if args.ipv4 == "use gethostbyname":
+ if (ipv4_str is None):
+ ipv4_str = socket.gethostbyname(socket.gethostname())
+ else:
+ ipv4_str = args.ipv4
+
+ if args.AdvMin != "0":
+ if args.AdvMin.isdigit():
+ advmin = int(args.AdvMin)
+ else:
+ print("Invalid input (AdvMin) {args.AdvMin}")
+ raise SystemExit(1)
+
+ if args.AdvMax != "0":
+ if args.AdvMax.isdigit():
+ advmax = int(args.AdvMax)
+ else:
+ print("Invalid input (AdvMin) {args.AdvMin}")
+ raise SystemExit(1)
+
+ if args.index != "0":
+ if args.index.isdigit():
+ index = int(args.index)
+ else:
+ print("Invalid input (AdvMin) {args.AdvMin}")
+ raise SystemExit(1)
+ if index < 0:
+ raise ValueError("index was negative (forbidden)")
+
+ print(f"AirPlay Service-Discovery Bluetooth LE beacon: using BLE file {args.path}, advmin:advmax {advmin}:{advmax} index:{index}")
+ print(f"(Press Ctrl+C to exit)")
+ main(args.path, ipv4_str, advmin, advmax, index)
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d797d79..b1f8b63 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,6 +75,14 @@ install( FILES README.md README.txt README.html LICENSE DESTINATION ${CMAKE_INST
install( FILES lib/llhttp/LICENSE-MIT DESTINATION ${CMAKE_INSTALL_DOCDIR}/llhttp )
install( FILES uxplay.service DESTINATION ${CMAKE_INSTALL_DOCDIR}/systemd )
+if (DBUS_FOUND)
+install( FILES Bluetooth_LE_beacon/dbus/uxplay-beacon.py
+ DESTINATION bin
+ PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ)
+install( FILES uxplay-beacon.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 )
+endif()
+
+
# uninstall target
if(NOT TARGET uninstall)
configure_file(
diff --git a/README.html b/README.html
index 610a2de..5f0765e 100644
--- a/README.html
+++ b/README.html
@@ -15,12 +15,14 @@ Bluetooth LE “beacon”, (a USB 4.0 or later “dongle” can be used). See
instructions below. The beacon runs independently of UxPlay and
regularly broadcasts a Bluetooth LE (“Low Energy”) 46 byte packet
informing nearby iOS/macOS devices of the local IPv4 network address of
-the UxPlay server, and which TCP port to contact UxPlay on. Instructions
-for manually setting up such a beacon in Linux are given below. It is hoped
-that users will submit Pull Requests contributing scripts for automating
-beacon setup on all platforms. (Python may be an appropriate language
-choice)
+the UxPlay server, and which TCP port to contact UxPlay on. A python
+script (Python >=3.6) “uxplay-beacon.py”, to broadcast the
+Service-Discovery advertisement will be installed on systems with DBus
+support (Linux and *BSD, using Bluez for Bluetooth control): this does
+NEW on github: option
-vrtp <rest-of-pipeline> bypasses rendering by
UxPlay, and instead transmits rtp packets of decrypted h264 or h265
@@ -1437,11 +1439,12 @@ to a file to n or less. To change the name audiodump,
use -admp [n] filename. Note that (unlike dumped video) the
dumped audio is currently only useful for debugging, as it is not
containerized to make it playable with standard audio players.
--ble filename. Enable Bluetooth beacon
-Service Discovery. The PID and process name of the UxPlay process is
-recorded in filename, which must be the full path to a
-writeable file. (This file is created when UxPlay starts and deleted
-when it stops.) See below for beacon setup
+-ble [filename]. Enable Bluetooth beacon
+Service Discovery. The port, PID and process name of the UxPlay process
+is recorded by default in ~/.uxplay.ble : (this file is
+created when UxPlay starts and deleted when it stops.) Optionally the
+file filename, which must be the full path to a writeable file
+can instead be used. See below for beacon setup
instructions.
-d [n] Enable debug output; optional argument n=1
suppresses audio/video packet data in debug output. Note: this does not
@@ -1451,191 +1454,65 @@ GST_DEBUG=2” before running uxplay. To see GStreamer information
messages, set GST_DEBUG=4; for DEBUG messages, GST_DEBUG=5; increase
this to see even more of the GStreamer inner workings.
Bluetooth LE beacon setup
-To allow UxPlay to work with Bluetooth Low Energy (LE) Service
-Discovery, as an alternative to DNS-SD (Bonjour/Rendezvous) service
-discovery, start it with the option
-“-ble <path-to-writeable-file>”, which at startup
-writes a data file containing the uxplay TCP port for receiving replies
-to the advertisement, plus the uxplay process ID and process name, and
-is deleted when uxplay terminates normally. This file is not
-used in the simple manual method for creating a beacon described
-below.
-Bluetooth LE Service discovery uses a “beacon” broadcasting a simple
-14-byte advertisement
-“0D FF 4C 00 09 08 13 30 XX XX XX XX YY YY” where XX XX XX
-XX is an IPv4 internet address (and port YY YY) of the UxPlay host
-translated into hexadecimal octets. For example,
-“XX XX XX XX YY YY” = “C0 A8 01 FD 1B 58”
-means 192.168.1.253 port 0x1b58 (decimal value 7000). UxPlay must be
-able to receive messages on this TCP port at this address. The uxplay
-option “-p” sets up uxplay to listen on the default port
-7000 for these messages, as used in the example above. Otherwise the
-port in the beacon message should be the first (<n>)
-of the 3 open TCP ports specified with uxplay option
--p <n>. If the -p option is not used
-(which is only possible if there is no active firewall) the TCP port is
-selected at random, and its value must be taken from the beginning of
-the file written with the -ble option.
-The full translation of this message is that it has length 0D = 0x0d
-= 13 octets, and is a single “Advertising Protocol Data Unit” (PDU) of
-type “FF”, called “Manufacturer-Specific Data”, with
-“manufacturer code” “4C 00” = 0x004c = Apple (note the
-reversal of octet order when two octets are combined to make a two-byte
-unsigned short integer), and
-“09 08 13 30 XX XX XX XX YY YY” is the Apple-specific
-data.
-The Apple-specific data contains a single Apple Data Unit with Apple
-type = 09 (Airplay), Apple Data length 08 (0x08 = 8 octets) and Apple
-Data “13 30 XX XX XX XX YY YY” where 13 = 0001 0011 is
-Apple Flags, 30 is a seed (which will be ignored), XX XX XX XX is the
-IPv4 internet address and YY YY is the port. This is smaller than the
-“iBeacon” Apple Data Unit, which has Apple type 02 and Apple length 15
-(0x15 = 21 octets).
-In addition to creating the message, we need to set the “Advertising
-type” (ADV_NONCONN_IND) and “Advertising interval” range [AdvMin,
-AdvMax], where 0x00a0 = 100 msec <= AdvMin <= AdvMax <= 0x4000
-= 10.24 sec (intervals are given in units of 0.625 msec as uint16_t
-unsigned short integers). Setting AdvMin = AdvMax fixes the interval;
-AdvMin < AdvMax allows the choice of the time of each advertising
-broadcast to be flexible within an allowed window to avoid clashing with
-other Bluetooth tasks. Keep the default choice to broadcast
-simultaneously on all three advertising channels, 37,38,39.
-An automated script to setup and start the beacon should use a
-high-level interface such as: (Linux) Bluez LEAdvertisingManager1
-(with an example)
-and (Windows 10/11) BluetoothLEAdvertisementPublisherClass
-(with an example).
-We invite submission of Pull Requests for working
-implementations!
-Until automated scripts are available, a simple Linux-only low-level
-manual method is given below, using the hcitool and
-hciconfig utilities which directly access the HCI stack,
-and need elevated privileges (use sudo). These utilities
-have been declared “deprecated” and “obsolete” by BlueZ developers: on
-Debian-based Linux “sudo apt install bluez” still provides
-hcitool, but on some other Linux distributions, it is split
-off from the main BlueZ package into an “extra” package with a name like
-“bluez-deprecated”. If we get the AirPlay beacon to work using the newer
-bluetoothctl or btmgmt utilities, these
-instructions will be updated.
-First verify that a Bluetooth HCI interface is available:
-$hcitool dev
-Devices:
- hci1 E8:EA:6A:7C:3F:CC
- hci0 08:BE:AC:40:A9:DC
-This shows two devices with their MAC addresses. You can use
-“hciconfig -a” to see which versions of Bluetooth they
-implement: we require Bluetooth v4.0 or later; you may need to use a
-cheap USB Bluetooth dongle if your system does not have it, or will not
-let you use it for LE (Low Energy) transmissions.
-Choose which interface to use (we will use hci0), and reset it.
-$ sudo hciconfig hci0 reset
-
-Step 1. Configure the beacon by sending a configure
-command 0x0006 to the Bluetooth LE stack 0x08. hcitool
-echoes the HCI command and the 4-byte “HCI Event” response. The only
-important part of the response is that the last byte is
-“00” (= “success”: other values are error codes):
-
-$ sudo hcitool -i hci0 cmd 0x08 0x0006 0xa0 0x00 0xa0 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x07 0x00
-
-< HCI Command: ogf 0x08, ocf 0x0006, plen 15
- A0 00 A0 00 03 00 00 00 00 00 00 00 00 07 00
-> HCI Event: 0x0e plen 4
- 02 06 20 00
-
-The first “0xa0 0x00” sets AdvMin = 0x00a0 = 100 msec.
-The second “0xa0 0x00” sets AdvMax = 0x00a0 = 100 msec.
-Then “0x03” sets the Advertising Type to ADV_NONCONN_IND.
-The other non-zero entry (0x07 = 0000 0111) is the flag for using all
-three advertising channels.
-An Apple TV (Gen 3) seems to use AdvMin = AdvMax = 180 msec = 0x0120
-(“0x20 0x01”).
-Step 2. Set the advertising message with HCI LE
-command 0x0008. For this command, hcitool requires a 32 octet message
-after sudo hcitool -i hci0 cmd 0x08 0x0008: The first octet
-is the length 0E = 0x0e = 14 of the “significant part” of the following
-31 octets, followed by the 14 octets of the advertisement, then padded
-with 17 zeroes to a total length of 32 octets. The example below sends
-an IPv4 address 192.168.1.253 as “0xc0 0xa8 0x01 0xfd” and
-the TCP port as 0x1b 0x58 (port 7000 = 0x1b58):
-$ sudo hcitool -i hci0 cmd 0x08 0x0008 0x0e 0x0d 0xff 0x4c 0x00 0x09 0x08 0x13 0x30 0xc0 0xa8 0x01 0xfd 0x1b 0x58 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
-< HCI Command: ogf 0x08, ocf 0x0008, plen 32
- 0E 0D FF 4C 00 09 08 13 30 C0 A8 01 FD 1B 58 00 00 00 00 00
- 00 00 00 00 00 00 00 00 00 00 00 00
-> HCI Event: 0x0e plen 4
- 01 08 20 00
-Step 3. Start the beacon with a 1-byte message
-“0x01” = “on”, sent with HCI LE command 0x000a = 10:
-$ sudo hcitool -i hci0 cmd 0x08 0x000a 0x01
-< HCI Command: ogf 0x08, ocf 0x000a, plen 1
- 01
-> HCI Event: 0x0e plen 4
- 02 0A 20 00
-The full length of the broadcasted beacon message is 46 bytes. To
-stop the beacon, use this command to send the 1-byte message
-“0x00” = “off”.
+The python>=3.6 script for running a Bluetooth-LE Service
+Discovery beacon is uxplay-beacon.py. Currently only a DBus version (for
+Linux and *BSD) is available, and it is only installed on systems which
+support DBus.
+If uxplay will be run with option “uxplay -ble” (so it
+writes data for the Bluetooth beacon in the default BLE data file
+~/.uxplay.ble), just run uxplay-beacon.py in a
+separate terminal. The python script will start Bluetooth LE
+Service-Discovery advertising when it detects that UxPlay is running by
+checking if the BLE data file exists, and stop when it no longer detects
+a running UxPlay plus this file (it will restart advertising if UxPlay
+later reappears). The script will remain active until stopped with
+Ctrl+C in its terminal window (or its terminal window is closed).
+The beacon script can be more finely controlled using five possible
+options: these can be given on the command line, or read from a
+configuration file ~/.uxplay.beacon, if it exists.
+Configuration file entries are like the command line forms, one per line
+(e.g., --ipv4 192.168.1.100). Lines commented out with an
+initial # are ignored. Command line options override the
+configuration file options.
-- For testing Bluetooth beacon Service Discovery on Linux, you will
-need to suppress the avahi-daemon which provides DNS-SD Service
-Discovery on UxPlay’s Host system (replace
mask and
-stop below by unmask and start to
-restore DNS-SD service):
+--file <config file> read beacon options from
+<config file> instead of
+~/.uxplay.beacon.
+--ipv4 <ipv4 address>. This option can be
+used to specify the ipv4 address at which the UxPlay server should be
+contacted by the client. If it is not given, an address will be obtained
+automatically using gethostbyname. Only ipv4 addresses are
+supported.
+--path <BLE data file>. This overrides the
+default choice of BLE data file (~/.uxplay.ble) that is
+monitored by the beacon script. This also requires that uxplay is run
+with option “uxplay -ble <BLE data file>”.
+--AdvMin x, --AdvMax y. These controls
+the interval between BLE advertisement broadcasts. This interval is in
+the range [x, y], given in units of msecs. Allowed ranges are 100 <=
+x <= y <= 10240. If AdvMin=AdvMax, the interval is fixed: if
+AdvMin < AdvMax it is chosen flexibly in this range to avoid
+interfering with other tasks the Bluetooth device is carrying out. The
+default values are AdvMin = AdvMax = 100. The advertisement is broadcast
+on all three Bluetooth LE advertising channels: 37,38,39.
+--index x (default x = 0, x >= 0). This should be
+used to distinguish between multiple simultaneous instances of
+uxplay-beacon.py that are running to support multiple instances of
+UxPlay. Each instance must have its own BLE Data file (just as each
+instance of UxPlay must also have its own MAC address and ports).
+Note: running multiple beacons simultaneously on the same host has
+not been tested.
+If you wish to test Bluetooth LE Service Discovery on Linux/*BSD, you
+can disable DNS_SD Service discovery by the avahi-daemon with
$ sudo systemctl mask avahi-daemon.socket
$ sudo systemctl stop avahi-daemon
-An automated procedure for creating the beacon would presumably want
-to switch it on when uxplay starts, and off when it stops. It has the
-task of determing a host IPv4 address that the client can use to reach
-uxplay. The 22-byte file created when uxplay starts (and deleted when it
-stops) contains the RAOP port as a uint16_t unsigned short, in the first
-2 bytes, followed by the uxplay PID as a uint32_t unsigned integer in
-the next 4 bytes, then followed by up to the first 15 characters of the
-process name (usually “uxplay”) as a null-terminated string, padded with
-zeroes to 16 bytes. The port data identifies the port on the Host that
-uxplay listens on, which should be included along with the Host IPv4
-address in the advertisement broadcast by the beacon. The path to this
-file is needed as the only input by the procedure when it is started.
-The presence of the file should be checked at regular intervals (once
-per second?). If it is absent, uxplay has stopped running, but if it
-exists the process ID and process name of that PID should be checked to
-handle cases where a new uxplay process has started, or if uxplay has
-exited abnormally and failed to delete the file. (While it is probably
-not an important use case, the possibility of concurrent uxplay
-processes listening on different ports and writing different files could
-be handled: the advertising protocol allows cycling between different
-messages.)
-This method above creates a beacon that identifies itself with a
-“public Advertising Address” (the MAC hardware address of the Bluetooth
-device). An Apple TV uses a private random address. If you wish to do
-that, change the sixth octet (the one following 0x03) in
-Step 1 from “TxAdd” = 0x00 to TxAdd = 0x01,
-and add an intermediate “step 1.5”:
-Step 1.5 Choose 6 random bytes r1, r2, r3, r4, r5,
-r6, such as “0x52 0xaa, 0xaa, 0x3a, 0xb4, 0x2f”, and use
-HCI LE command 0x0005 to set the random address:
-$sudo hcitool -i hci0 cmd 0x08 0x0005 0x52 0xaa 0xaa 0x3a 0xb4 0x2f
-< HCI Command: ogf 0x08, ocf 0x0005, plen 6
- 52 AA AA 3A B4 2F
-> HCI Event: 0x0e plen 4
- 02 05 20 00
-On a Bluetooth packet sniffer with wireshark, this address displays
-as: Advertising Address: 2f:b4:3a:aa:aa:52. In
-principle, random byte r6 should be masked with 0x03 (r6 = r6 | 0x03) to
-mark the address as a “static random private address”, but Apple TV does
-not do this. In fact it updates to a new random Advertising Address
-every 20 mins or so, increasing the seed in the Apple Data by 1 each
-time. Apple TV’s also add a length 2 type 0x01 (“Flags”) Advertising PDU
-“0x02 0x01 0x1a” in front of the main type 0xff
-“Manufacturer-Specific Data” Advertising PDU in Step 2. This is
-“optional” for ADV_NONCONN_IND advertisement type, and testing shows
-that it can be dropped without affecting Service Discovery, which is
-fortunate because the high-level Linux and Windows interfaces mentioned
-earlier do not permit users to send a “Flags”-type PDU.
+To restore DNS_SD Service discovery, replace “mask” by “unmask”, and
+“stop” by “start”.
+For more information, see the wiki
+page This has useful information if you wish to build a python
+beacon controller script for Windows (we would like to have one!).
- Our current understanding is that Bluetooth LE AirPlay
Service Discovery only supports broadcast of IPv4 addresses. Please let
diff --git a/README.md b/README.md
index b478591..ce57702 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,11 @@
- **NEW on github**: Support for **service discovery using a Bluetooth LE "beacon"** (as an alternative to Bonjour/Rendezvous DNS-SD
service discovery). The user must set up a Bluetooth LE "beacon", (a USB 4.0 or later "dongle" can be used). See instructions
below. The beacon runs independently of UxPlay and regularly broadcasts a Bluetooth LE ("Low Energy") 46 byte packet informing nearby iOS/macOS devices of
- the local IPv4 network address of the UxPlay server, and which TCP port to contact UxPlay on.
- Instructions for manually setting up such a beacon in Linux are [given below](#bluetooth-le-beacon-setup).
- __It is hoped that users will submit Pull Requests contributing scripts for automating beacon setup on all platforms.
- (Python may be an appropriate language choice)__
+ the local IPv4 network address of the UxPlay server, and which TCP port to contact UxPlay on. A python script (Python >=3.6) "uxplay-beacon.py",
+ to broadcast the Service-Discovery advertisement will be installed on systems with DBus support (Linux and *BSD, using Bluez for Bluetooth control):
+ this does **not** require enhanced "root permissions" to run.
+ A windows version of this script is also planned for the future.
+ Instructions are [given below](#bluetooth-le-beacon-setup).
- **NEW on github**: option `-vrtp ` bypasses rendering by UxPlay, and instead
transmits rtp packets of decrypted h264 or h265 video to
@@ -1452,10 +1453,13 @@ that (unlike dumped video) the dumped audio is currently only useful for
debugging, as it is not containerized to make it playable with standard
audio players.*
-**-ble *filename***. Enable Bluetooth beacon Service Discovery.
-The PID and process name of the UxPlay process is recorded in
-*filename*, which must be the full path to a writeable file. (This file is created
-when UxPlay starts and deleted when it stops.) __See below for beacon setup
+**-ble [*filename*]**. Enable Bluetooth beacon Service Discovery.
+The port, PID and process name of the UxPlay process is recorded by default in
+`~/.uxplay.ble` : (this file is created
+when UxPlay starts and deleted when it stops.)
+Optionally the file
+*filename*, which must be the full path to a writeable file can instead be used.
+__See below for beacon setup
instructions.__
**-d \[n\]** Enable debug output; optional argument n=1 suppresses audio/video
@@ -1469,170 +1473,51 @@ GStreamer inner workings.
# Bluetooth LE beacon setup
-To allow UxPlay to work with Bluetooth Low Energy (LE) Service Discovery, as an alternative to DNS-SD (Bonjour/Rendezvous)
-service discovery, start it with the option "`-ble `", which at startup writes a data file containing
-the uxplay TCP port for receiving replies to the advertisement, plus the uxplay process ID and process name, and is deleted when uxplay terminates normally. **This file
-is not used in the simple manual method for creating a beacon described below**.
+The python>=3.6 script for running a Bluetooth-LE Service Discovery beacon is uxplay-beacon.py.
+Currently only a DBus version (for Linux and *BSD) is available, and it is only installed on systems which
+support DBus.
+If uxplay will be run with option "`uxplay -ble`" (so it writes data for the Bluetooth beacon in the default BLE data file
+`~/.uxplay.ble`), just run ``uxplay-beacon.py`` in a separate terminal. The python script will start
+Bluetooth LE Service-Discovery advertising when it detects that UxPlay is running by checking if the BLE data file exists, and stop when it no longer detects
+a running UxPlay plus this file (it will restart advertising if UxPlay later reappears). The script will remain active until stopped with Ctrl+C in its
+terminal window (or its terminal window is closed).
-Bluetooth LE Service discovery uses a "beacon" broadcasting a simple 14-byte
-advertisement "`0D FF 4C 00 09 08 13 30 XX XX XX XX YY YY`" where XX XX XX XX is an IPv4 internet
-address (and port YY YY) of the UxPlay host translated into hexadecimal octets. For
-example, "`XX XX XX XX YY YY`" = "``C0 A8 01 FD 1B 58``" means 192.168.1.253 port 0x1b58 (decimal value 7000). UxPlay
-must be able to receive messages on this TCP port at this
-address. The uxplay option "`-p`" sets up uxplay to listen on the default port 7000 for these messages, as used in the
-example above. Otherwise the port in the beacon message should
-be the first (``) of the 3 open TCP ports specified with uxplay option ``-p ``. If
-the `-p` option is not used (which is only possible if there is no active firewall) the TCP port is selected at random, and its value
-must be taken from the beginning of the file written with the `-ble` option.
+The beacon script can be more finely controlled using five possible options: these can be given on the command line, or read from
+a configuration file `~/.uxplay.beacon`, if it exists. Configuration file entries are like the command line forms, one per line (e.g.,
+`--ipv4 192.168.1.100`). Lines commented out with an initial ``#`` are ignored. Command line options override the configuration file
+options.
-The full translation of this message is that it has length 0D = 0x0d = 13 octets, and is a single "Advertising Protocol Data Unit" (PDU) of type "`FF`",
-called "Manufacturer-Specific Data", with "manufacturer code" "`4C 00`" = 0x004c = Apple (note the reversal of octet order when
-two octets are combined to make a two-byte unsigned short integer), and "`09 08 13 30 XX XX XX XX YY YY`" is the Apple-specific data.
+* `--file ` read beacon options from ```` instead of
+`~/.uxplay.beacon`.
-The Apple-specific data contains a single Apple Data Unit with Apple type = 09 (Airplay), Apple Data length 08 (0x08 = 8 octets) and
-Apple Data "`13 30 XX XX XX XX YY YY`" where 13 = 0001 0011 is Apple Flags, 30 is a seed (which will be ignored), XX XX XX XX
-is the IPv4 internet address and YY YY is the port. This is smaller than the "iBeacon" Apple Data Unit, which has Apple type 02 and Apple length 15 (0x15 = 21 octets).
+* `--ipv4 `. This option can be used to specify the ipv4 address at which the UxPlay server should be contacted by the client. If
+it is not given, an address will be obtained automatically using `gethostbyname`. Only ipv4 addresses are supported.
-In addition to creating the message, we need to set the "Advertising type" (ADV_NONCONN_IND) and "Advertising interval" range [AdvMin, AdvMax],
-where 0x00a0 = 100 msec <= AdvMin <= AdvMax <= 0x4000 = 10.24 sec
-(intervals are given in units of 0.625 msec as uint16_t unsigned short integers). Setting AdvMin = AdvMax fixes the interval; AdvMin < AdvMax allows the choice
-of the time of each advertising broadcast to be flexible within an allowed window to avoid clashing with other Bluetooth tasks.
-Keep the default choice to broadcast simultaneously on all three advertising channels, 37,38,39.
+* `--path `. This overrides the default choice of BLE data file (``~/.uxplay.ble``) that is monitored by the beacon script. This also requires
+that uxplay is run with option "`uxplay -ble `".
-An automated script to setup and start the beacon should use a high-level interface
-such as: (Linux) Bluez [LEAdvertisingManager1](https://manpages.opensuse.org/Leap-16.0/bluez/org.bluez.LEAdvertisement.5.en.html) (with
-an [example](https://github.com/bluez/bluez/blob/master/test/example-advertisement))
-and (Windows 10/11) [BluetoothLEAdvertisementPublisherClass](https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementpublisher)
-(with an [example](https://github.com/MicrosoftDocs/windows-dev-docs/blob/docs/uwp/devices-sensors/ble-beacon.md)).
-**We invite submission of Pull Requests for working implementations!**
+* `--AdvMin x`, ``--AdvMax y``. These controls the interval between BLE advertisement broadcasts. This interval is in the range
+[x, y], given in units of msecs. Allowed ranges are 100 <= x <= y <= 10240. If AdvMin=AdvMax, the interval is fixed: if AdvMin < AdvMax
+it is chosen flexibly in this range to avoid interfering with other tasks the Bluetooth device is carrying out. The default values are
+AdvMin = AdvMax = 100. The advertisement is broadcast on all three Bluetooth LE advertising channels: 37,38,39.
-Until automated scripts are available, a simple Linux-only low-level manual method
-is given below, using the `hcitool` and ``hciconfig``
-utilities which directly access the HCI stack, and need elevated privileges (use `sudo`). These utilities
-have been declared "deprecated" and "obsolete" by BlueZ developers: on Debian-based Linux "`sudo apt install bluez`"
-still provides `hcitool`, but on some other Linux distributions, it is split off from the main BlueZ package into an "extra" package
-with a name like "bluez-deprecated". If we get the AirPlay beacon to work using the newer `bluetoothctl` or ``btmgmt`` utilities,
-these instructions will be updated.
+* `--index x` (default x = 0, x >= 0). This should be used to distinguish between multiple simultaneous instances of uxplay-beacon.py that are running to support multiple
+ instances of UxPlay. Each instance must have its own BLE Data file (just as each instance of UxPlay must also have its own MAC address and ports). _Note:
+ running multiple beacons simultaneously on the same host has not been tested._
-First verify that a Bluetooth HCI interface is available:
-
-```
-$hcitool dev
-Devices:
- hci1 E8:EA:6A:7C:3F:CC
- hci0 08:BE:AC:40:A9:DC
-```
-
-This shows two devices with their MAC addresses. You can use "`hciconfig -a`" to see which versions of Bluetooth they
-implement: we require Bluetooth v4.0 or later;
-you may need to use a cheap USB Bluetooth dongle if your system does not have it,
-or will not let you use it for LE (Low Energy) transmissions.
-Choose which interface to use (we will use hci0), and reset it.
-
-```
-$ sudo hciconfig hci0 reset
-
-```
-
-**Step 1.** Configure the beacon by sending a configure command 0x0006 to the Bluetooth LE stack 0x08. `hcitool` echoes the HCI command
-and the 4-byte "HCI Event" response. The only important part of the response is that the last byte is "`00`" (= "success":
-other values are error codes):
-
-
-```
-
-$ sudo hcitool -i hci0 cmd 0x08 0x0006 0xa0 0x00 0xa0 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x07 0x00
-
-< HCI Command: ogf 0x08, ocf 0x0006, plen 15
- A0 00 A0 00 03 00 00 00 00 00 00 00 00 07 00
-> HCI Event: 0x0e plen 4
- 02 06 20 00
-
-```
-
-The first "`0xa0 0x00`" sets AdvMin = 0x00a0 = 100 msec. The second "``0xa0 0x00``" sets AdvMax = 0x00a0 = 100 msec.
-Then "`0x03`" sets the Advertising Type to ADV_NONCONN_IND. The other non-zero entry (0x07 = 0000 0111) is the flag for using
-all three
-advertising channels.
-
-An Apple TV (Gen 3) seems to use AdvMin = AdvMax = 180 msec = 0x0120 ("`0x20 0x01`").
-
-**Step 2.** Set the advertising message with HCI LE command 0x0008. For this command, hcitool requires a 32 octet message after
-`sudo hcitool -i hci0 cmd 0x08 0x0008`: The first octet is the length 0E = 0x0e = 14 of the "significant part" of the following 31 octets,
-followed by the 14 octets of the advertisement, then padded with 17 zeroes to a total length of 32 octets. The example below sends an
-IPv4 address 192.168.1.253 as "`0xc0 0xa8 0x01 0xfd`" and the TCP port as 0x1b 0x58 (port 7000 = 0x1b58):
-
-```
-$ sudo hcitool -i hci0 cmd 0x08 0x0008 0x0e 0x0d 0xff 0x4c 0x00 0x09 0x08 0x13 0x30 0xc0 0xa8 0x01 0xfd 0x1b 0x58 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
-< HCI Command: ogf 0x08, ocf 0x0008, plen 32
- 0E 0D FF 4C 00 09 08 13 30 C0 A8 01 FD 1B 58 00 00 00 00 00
- 00 00 00 00 00 00 00 00 00 00 00 00
-> HCI Event: 0x0e plen 4
- 01 08 20 00
-```
-
-**Step 3**. Start the beacon with a 1-byte message "`0x01`" = "on", sent with HCI LE command 0x000a = 10:
-
-```
-$ sudo hcitool -i hci0 cmd 0x08 0x000a 0x01
-< HCI Command: ogf 0x08, ocf 0x000a, plen 1
- 01
-> HCI Event: 0x0e plen 4
- 02 0A 20 00
-```
-The full length of the broadcasted beacon message is 46 bytes.
-To stop the beacon, use this command to send the 1-byte message "`0x00`" = "off".
-
-
-
-* For testing Bluetooth beacon Service Discovery on Linux, you will need to suppress the avahi-daemon which
-provides DNS-SD Service Discovery on UxPlay's Host system (replace `mask` and ``stop`` below
-by `unmask` and ``start`` to restore DNS-SD service):
+If you wish to test Bluetooth LE Service Discovery on Linux/*BSD, you can disable DNS_SD Service discovery by the avahi-daemon with
```
$ sudo systemctl mask avahi-daemon.socket
$ sudo systemctl stop avahi-daemon
```
-An automated procedure for creating the beacon would presumably want to switch it on when uxplay starts, and off when it
-stops. It has the task of determing a host IPv4 address that the client can use to reach uxplay.
-The 22-byte file created when uxplay starts (and deleted when it stops) contains the RAOP port as a uint16_t unsigned short,
-in the first 2 bytes, followed by
-the uxplay PID as a uint32_t unsigned integer in the next 4 bytes, then
-followed by up to the first
-15 characters of the process name (usually "uxplay") as a null-terminated string, padded with zeroes to 16 bytes. The port data
-identifies the port on the Host that uxplay listens on, which should be included along with the Host IPv4 address
-in the advertisement broadcast by the beacon. The path to this file is needed as the only input by the procedure when it is started.
-The presence of the file should be checked at regular intervals (once per second?). If it is absent, uxplay has stopped running,
-but if it exists the process ID and process name of that PID should be checked to handle cases where a new uxplay process has
-started, or if uxplay has exited abnormally and failed to delete the file. (While it is probably not an important use case, the possibility of
-concurrent uxplay processes listening on different ports and writing different files could be handled: the advertising protocol allows
-cycling between different messages.)
+To restore DNS_SD Service discovery, replace "mask" by "unmask", and "stop" by "start".
-This method above creates a beacon that identifies itself with a "public Advertising Address" (the MAC hardware address of
-the Bluetooth device). An Apple TV uses a private random address. If you wish to do that, change the sixth octet (the one following `0x03`)
-in Step 1 from "TxAdd" = `0x00` to TxAdd = ``0x01``, and add an intermediate "step 1.5":
-
-**Step 1.5** Choose 6 random bytes r1, r2, r3, r4, r5, r6, such as
-"`0x52 0xaa, 0xaa, 0x3a, 0xb4, 0x2f`", and use HCI LE command 0x0005 to set the random address:
-
-```
-$sudo hcitool -i hci0 cmd 0x08 0x0005 0x52 0xaa 0xaa 0x3a 0xb4 0x2f
-< HCI Command: ogf 0x08, ocf 0x0005, plen 6
- 52 AA AA 3A B4 2F
-> HCI Event: 0x0e plen 4
- 02 05 20 00
-```
-
-On a Bluetooth packet sniffer with wireshark, this address displays as: **Advertising Address: 2f:b4:3a:aa:aa:52**.
-In principle, random byte r6 should be masked with 0x03 (r6 = r6 | 0x03) to mark the address as a "static random private address",
-but Apple TV does not do this. In fact it updates to a new random Advertising Address every 20 mins or so, increasing
-the seed in the Apple Data by 1 each time. Apple TV's also add a length 2 type 0x01 ("Flags") Advertising PDU "`0x02 0x01 0x1a`" in front of
-the main type 0xff "Manufacturer-Specific Data" Advertising PDU in Step 2. This is "optional" for ADV_NONCONN_IND advertisement type,
-and testing shows that it can be dropped without affecting Service Discovery, which is fortunate
-because the high-level Linux and Windows interfaces mentioned earlier do not permit users to send a "Flags"-type PDU.
+For more information, see the [wiki page](https://github.com/FDH2/UxPlay/wiki/Bluetooth_LE_beacon)
+This has useful information if you wish to build a python beacon controller script for Windows (we would like to have one!).
* **Our current understanding is that Bluetooth LE AirPlay Service Discovery only supports
broadcast of IPv4 addresses. Please let us know if this is incorrect, or if IPv6 support is introduced in the future.**
diff --git a/README.txt b/README.txt
index 4667087..4f9292b 100644
--- a/README.txt
+++ b/README.txt
@@ -9,12 +9,13 @@
beacon runs independently of UxPlay and regularly broadcasts a
Bluetooth LE ("Low Energy") 46 byte packet informing nearby
iOS/macOS devices of the local IPv4 network address of the UxPlay
- server, and which TCP port to contact UxPlay on. Instructions for
- manually setting up such a beacon in Linux are [given
- below](#bluetooth-le-beacon-setup). **It is hoped that users will
- submit Pull Requests contributing scripts for automating beacon
- setup on all platforms. (Python may be an appropriate language
- choice)**
+ server, and which TCP port to contact UxPlay on. A python script
+ (Python \>=3.6) "uxplay-beacon.py", to broadcast the
+ Service-Discovery advertisement will be installed on systems with
+ DBus support (Linux and \*BSD, using Bluez for Bluetooth control):
+ this does **not** require enhanced "root permissions" to run. A
+ windows version of this script is also planned for the future.
+ Instructions are [given below](#bluetooth-le-beacon-setup).
- **NEW on github**: option `-vrtp ` bypasses
rendering by UxPlay, and instead transmits rtp packets of decrypted
@@ -1478,11 +1479,12 @@ that (unlike dumped video) the dumped audio is currently only useful for
debugging, as it is not containerized to make it playable with standard
audio players.*
-**-ble *filename***. Enable Bluetooth beacon Service Discovery. The PID
-and process name of the UxPlay process is recorded in *filename*, which
-must be the full path to a writeable file. (This file is created when
-UxPlay starts and deleted when it stops.) **See below for beacon setup
-instructions.**
+**-ble \[*filename*\]**. Enable Bluetooth beacon Service Discovery. The
+port, PID and process name of the UxPlay process is recorded by default
+in `~/.uxplay.ble` : (this file is created when UxPlay starts and
+deleted when it stops.) Optionally the file *filename*, which must be
+the full path to a writeable file can instead be used. **See below for
+beacon setup instructions.**
**-d \[n\]** Enable debug output; optional argument n=1 suppresses
audio/video packet data in debug output. Note: this does not show
@@ -1494,206 +1496,72 @@ this to see even more of the GStreamer inner workings.
# Bluetooth LE beacon setup
-To allow UxPlay to work with Bluetooth Low Energy (LE) Service
-Discovery, as an alternative to DNS-SD (Bonjour/Rendezvous) service
-discovery, start it with the option "`-ble `",
-which at startup writes a data file containing the uxplay TCP port for
-receiving replies to the advertisement, plus the uxplay process ID and
-process name, and is deleted when uxplay terminates normally. **This
-file is not used in the simple manual method for creating a beacon
-described below**.
+The python\>=3.6 script for running a Bluetooth-LE Service Discovery
+beacon is uxplay-beacon.py. Currently only a DBus version (for Linux and
+\*BSD) is available, and it is only installed on systems which support
+DBus.
-Bluetooth LE Service discovery uses a "beacon" broadcasting a simple
-14-byte advertisement "`0D FF 4C 00 09 08 13 30 XX XX XX XX YY YY`"
-where XX XX XX XX is an IPv4 internet address (and port YY YY) of the
-UxPlay host translated into hexadecimal octets. For example,
-"`XX XX XX XX YY YY`" = "`C0 A8 01 FD 1B 58`" means 192.168.1.253 port
-0x1b58 (decimal value 7000). UxPlay must be able to receive messages on
-this TCP port at this address. The uxplay option "`-p`" sets up uxplay
-to listen on the default port 7000 for these messages, as used in the
-example above. Otherwise the port in the beacon message should be the
-first (``) of the 3 open TCP ports specified with uxplay option
-`-p `. If the `-p` option is not used (which is only possible if
-there is no active firewall) the TCP port is selected at random, and its
-value must be taken from the beginning of the file written with the
-`-ble` option.
+If uxplay will be run with option "`uxplay -ble`" (so it writes data for
+the Bluetooth beacon in the default BLE data file `~/.uxplay.ble`), just
+run `uxplay-beacon.py` in a separate terminal. The python script will
+start Bluetooth LE Service-Discovery advertising when it detects that
+UxPlay is running by checking if the BLE data file exists, and stop when
+it no longer detects a running UxPlay plus this file (it will restart
+advertising if UxPlay later reappears). The script will remain active
+until stopped with Ctrl+C in its terminal window (or its terminal window
+is closed).
-The full translation of this message is that it has length 0D = 0x0d =
-13 octets, and is a single "Advertising Protocol Data Unit" (PDU) of
-type "`FF`", called "Manufacturer-Specific Data", with "manufacturer
-code" "`4C 00`" = 0x004c = Apple (note the reversal of octet order when
-two octets are combined to make a two-byte unsigned short integer), and
-"`09 08 13 30 XX XX XX XX YY YY`" is the Apple-specific data.
+The beacon script can be more finely controlled using five possible
+options: these can be given on the command line, or read from a
+configuration file `~/.uxplay.beacon`, if it exists. Configuration file
+entries are like the command line forms, one per line (e.g.,
+`--ipv4 192.168.1.100`). Lines commented out with an initial `#` are
+ignored. Command line options override the configuration file options.
-The Apple-specific data contains a single Apple Data Unit with Apple
-type = 09 (Airplay), Apple Data length 08 (0x08 = 8 octets) and Apple
-Data "`13 30 XX XX XX XX YY YY`" where 13 = 0001 0011 is Apple Flags, 30
-is a seed (which will be ignored), XX XX XX XX is the IPv4 internet
-address and YY YY is the port. This is smaller than the "iBeacon" Apple
-Data Unit, which has Apple type 02 and Apple length 15 (0x15 = 21
-octets).
+- `--file ` read beacon options from ``
+ instead of `~/.uxplay.beacon`.
-In addition to creating the message, we need to set the "Advertising
-type" (ADV_NONCONN_IND) and "Advertising interval" range \[AdvMin,
-AdvMax\], where 0x00a0 = 100 msec \<= AdvMin \<= AdvMax \<= 0x4000 =
-10.24 sec (intervals are given in units of 0.625 msec as uint16_t
-unsigned short integers). Setting AdvMin = AdvMax fixes the interval;
-AdvMin \< AdvMax allows the choice of the time of each advertising
-broadcast to be flexible within an allowed window to avoid clashing with
-other Bluetooth tasks. Keep the default choice to broadcast
-simultaneously on all three advertising channels, 37,38,39.
+- `--ipv4 `. This option can be used to specify the
+ ipv4 address at which the UxPlay server should be contacted by the
+ client. If it is not given, an address will be obtained
+ automatically using `gethostbyname`. Only ipv4 addresses are
+ supported.
-An automated script to setup and start the beacon should use a
-high-level interface such as: (Linux) Bluez
-[LEAdvertisingManager1](https://manpages.opensuse.org/Leap-16.0/bluez/org.bluez.LEAdvertisement.5.en.html)
-(with an
-[example](https://github.com/bluez/bluez/blob/master/test/example-advertisement))
-and (Windows 10/11)
-[BluetoothLEAdvertisementPublisherClass](https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementpublisher)
-(with an
-[example](https://github.com/MicrosoftDocs/windows-dev-docs/blob/docs/uwp/devices-sensors/ble-beacon.md)).
-**We invite submission of Pull Requests for working implementations!**
+- `--path `. This overrides the default choice of BLE
+ data file (`~/.uxplay.ble`) that is monitored by the beacon script.
+ This also requires that uxplay is run with option
+ "`uxplay -ble `".
-Until automated scripts are available, a simple Linux-only low-level
-manual method is given below, using the `hcitool` and `hciconfig`
-utilities which directly access the HCI stack, and need elevated
-privileges (use `sudo`). These utilities have been declared "deprecated"
-and "obsolete" by BlueZ developers: on Debian-based Linux
-"`sudo apt install bluez`" still provides `hcitool`, but on some other
-Linux distributions, it is split off from the main BlueZ package into an
-"extra" package with a name like "bluez-deprecated". If we get the
-AirPlay beacon to work using the newer `bluetoothctl` or `btmgmt`
-utilities, these instructions will be updated.
+- `--AdvMin x`, `--AdvMax y`. These controls the interval between BLE
+ advertisement broadcasts. This interval is in the range \[x, y\],
+ given in units of msecs. Allowed ranges are 100 \<= x \<= y
+ \<= 10240. If AdvMin=AdvMax, the interval is fixed: if AdvMin \<
+ AdvMax it is chosen flexibly in this range to avoid interfering with
+ other tasks the Bluetooth device is carrying out. The default values
+ are AdvMin = AdvMax = 100. The advertisement is broadcast on all
+ three Bluetooth LE advertising channels: 37,38,39.
-First verify that a Bluetooth HCI interface is available:
+- `--index x` (default x = 0, x \>= 0). This should be used to
+ distinguish between multiple simultaneous instances of
+ uxplay-beacon.py that are running to support multiple instances of
+ UxPlay. Each instance must have its own BLE Data file (just as each
+ instance of UxPlay must also have its own MAC address and ports).
+ *Note: running multiple beacons simultaneously on the same host has
+ not been tested.*
- $hcitool dev
- Devices:
- hci1 E8:EA:6A:7C:3F:CC
- hci0 08:BE:AC:40:A9:DC
+If you wish to test Bluetooth LE Service Discovery on Linux/\*BSD, you
+can disable DNS_SD Service discovery by the avahi-daemon with
-This shows two devices with their MAC addresses. You can use
-"`hciconfig -a`" to see which versions of Bluetooth they implement: we
-require Bluetooth v4.0 or later; you may need to use a cheap USB
-Bluetooth dongle if your system does not have it, or will not let you
-use it for LE (Low Energy) transmissions.\
-Choose which interface to use (we will use hci0), and reset it.
-
- $ sudo hciconfig hci0 reset
-
-**Step 1.** Configure the beacon by sending a configure command 0x0006
-to the Bluetooth LE stack 0x08. `hcitool` echoes the HCI command and the
-4-byte "HCI Event" response. The only important part of the response is
-that the last byte is "`00`" (= "success": other values are error
-codes):
-
-
- $ sudo hcitool -i hci0 cmd 0x08 0x0006 0xa0 0x00 0xa0 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x07 0x00
-
- < HCI Command: ogf 0x08, ocf 0x0006, plen 15
- A0 00 A0 00 03 00 00 00 00 00 00 00 00 07 00
- > HCI Event: 0x0e plen 4
- 02 06 20 00
-
-The first "`0xa0 0x00`" sets AdvMin = 0x00a0 = 100 msec. The second
-"`0xa0 0x00`" sets AdvMax = 0x00a0 = 100 msec. Then "`0x03`" sets the
-Advertising Type to ADV_NONCONN_IND. The other non-zero entry (0x07 =
-0000 0111) is the flag for using all three advertising channels.
-
-An Apple TV (Gen 3) seems to use AdvMin = AdvMax = 180 msec = 0x0120
-("`0x20 0x01`").
-
-**Step 2.** Set the advertising message with HCI LE command 0x0008. For
-this command, hcitool requires a 32 octet message after
-`sudo hcitool -i hci0 cmd 0x08 0x0008`: The first octet is the length 0E
-= 0x0e = 14 of the "significant part" of the following 31 octets,
-followed by the 14 octets of the advertisement, then padded with 17
-zeroes to a total length of 32 octets. The example below sends an IPv4
-address 192.168.1.253 as "`0xc0 0xa8 0x01 0xfd`" and the TCP port as
-0x1b 0x58 (port 7000 = 0x1b58):
-
- $ sudo hcitool -i hci0 cmd 0x08 0x0008 0x0e 0x0d 0xff 0x4c 0x00 0x09 0x08 0x13 0x30 0xc0 0xa8 0x01 0xfd 0x1b 0x58 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
- < HCI Command: ogf 0x08, ocf 0x0008, plen 32
- 0E 0D FF 4C 00 09 08 13 30 C0 A8 01 FD 1B 58 00 00 00 00 00
- 00 00 00 00 00 00 00 00 00 00 00 00
- > HCI Event: 0x0e plen 4
- 01 08 20 00
-
-**Step 3**. Start the beacon with a 1-byte message "`0x01`" = "on", sent
-with HCI LE command 0x000a = 10:
-
- $ sudo hcitool -i hci0 cmd 0x08 0x000a 0x01
- < HCI Command: ogf 0x08, ocf 0x000a, plen 1
- 01
- > HCI Event: 0x0e plen 4
- 02 0A 20 00
-
-The full length of the broadcasted beacon message is 46 bytes. To stop
-the beacon, use this command to send the 1-byte message "`0x00`" =
-"off".
-
-- For testing Bluetooth beacon Service Discovery on Linux, you will
- need to suppress the avahi-daemon which provides DNS-SD Service
- Discovery on UxPlay's Host system (replace `mask` and `stop` below
- by `unmask` and `start` to restore DNS-SD service):
-
-```{=html}
-
-```
$ sudo systemctl mask avahi-daemon.socket
$ sudo systemctl stop avahi-daemon
-An automated procedure for creating the beacon would presumably want to
-switch it on when uxplay starts, and off when it stops. It has the task
-of determing a host IPv4 address that the client can use to reach
-uxplay. The 22-byte file created when uxplay starts (and deleted when it
-stops) contains the RAOP port as a uint16_t unsigned short, in the first
-2 bytes, followed by the uxplay PID as a uint32_t unsigned integer in
-the next 4 bytes, then followed by up to the first 15 characters of the
-process name (usually "uxplay") as a null-terminated string, padded with
-zeroes to 16 bytes. The port data identifies the port on the Host that
-uxplay listens on, which should be included along with the Host IPv4
-address in the advertisement broadcast by the beacon. The path to this
-file is needed as the only input by the procedure when it is started.
-The presence of the file should be checked at regular intervals (once
-per second?). If it is absent, uxplay has stopped running, but if it
-exists the process ID and process name of that PID should be checked to
-handle cases where a new uxplay process has started, or if uxplay has
-exited abnormally and failed to delete the file. (While it is probably
-not an important use case, the possibility of concurrent uxplay
-processes listening on different ports and writing different files could
-be handled: the advertising protocol allows cycling between different
-messages.)
+To restore DNS_SD Service discovery, replace "mask" by "unmask", and
+"stop" by "start".
-This method above creates a beacon that identifies itself with a "public
-Advertising Address" (the MAC hardware address of the Bluetooth device).
-An Apple TV uses a private random address. If you wish to do that,
-change the sixth octet (the one following `0x03`) in Step 1 from "TxAdd"
-= `0x00` to TxAdd = `0x01`, and add an intermediate "step 1.5":
-
-**Step 1.5** Choose 6 random bytes r1, r2, r3, r4, r5, r6, such as
-"`0x52 0xaa, 0xaa, 0x3a, 0xb4, 0x2f`", and use HCI LE command 0x0005 to
-set the random address:
-
- $sudo hcitool -i hci0 cmd 0x08 0x0005 0x52 0xaa 0xaa 0x3a 0xb4 0x2f
- < HCI Command: ogf 0x08, ocf 0x0005, plen 6
- 52 AA AA 3A B4 2F
- > HCI Event: 0x0e plen 4
- 02 05 20 00
-
-On a Bluetooth packet sniffer with wireshark, this address displays as:
-**Advertising Address: 2f:b4:3a:aa:aa:52**. In principle, random byte r6
-should be masked with 0x03 (r6 = r6 \| 0x03) to mark the address as a
-"static random private address", but Apple TV does not do this. In fact
-it updates to a new random Advertising Address every 20 mins or so,
-increasing the seed in the Apple Data by 1 each time. Apple TV's also
-add a length 2 type 0x01 ("Flags") Advertising PDU "`0x02 0x01 0x1a`" in
-front of the main type 0xff "Manufacturer-Specific Data" Advertising PDU
-in Step 2. This is "optional" for ADV_NONCONN_IND advertisement type,
-and testing shows that it can be dropped without affecting Service
-Discovery, which is fortunate because the high-level Linux and Windows
-interfaces mentioned earlier do not permit users to send a "Flags"-type
-PDU.
+For more information, see the [wiki
+page](https://github.com/FDH2/UxPlay/wiki/Bluetooth_LE_beacon) This has
+useful information if you wish to build a python beacon controller
+script for Windows (we would like to have one!).
- **Our current understanding is that Bluetooth LE AirPlay Service
Discovery only supports broadcast of IPv4 addresses. Please let us
diff --git a/uxplay-beacon.1 b/uxplay-beacon.1
new file mode 100644
index 0000000..edfa117
--- /dev/null
+++ b/uxplay-beacon.1
@@ -0,0 +1,45 @@
+.TH UXPLAY 1 2025-10-26 "UxPlay 1.72" "User Commands"
+.SH NAME
+uxplay-beacon.py \- Python (>= 3.6) script for a Bluetooth LE Service-Discovery beacon.
+.SH SYNOPSIS
+.B uxplay-beacon.py
+[\fI\, -h, --help] + more options.
+.SH DESCRIPTION
+UxPlay 1.72: Standalone Python Script for managing Bluetooth LE Service Discovery.
+.SH OPTIONS
+.TP
+.B
+\fB\--file\fR fn Specify alternate configuration file
+.TP
+\fB\--path\fR fn Specify non-default Bluetooth LE data file used by uxplay
+.TP
+\fB\--ipv4\fR ip Override automatically-found ipv4 address for contacting UxPlay
+.TP
+\fB\--AdvMin\fR x Minimum Advertising interval in msecs (>= 100)
+.TP
+\fB\--AdvMin\fR y Maximum Advertising interval in msecs (>= AdvMin, <= 102400)
+.TP
+\fB\--index\fR x Used to distinguish different instances of beacons
+.TP
+\fB \-h, --help\fR Show help text.
+.SH
+FILES
+Options in beacon configuration file ~/.uxplay.beacon
+.TP
+are applied first (command-line options may modify them). Format:
+.TP
+one option per line, with initial "--"; lines beginning with "#" ignored.
+.SH
+AUTHORS
+.TP
+Various, see website or distribution.
+.SH
+COPYRIGHT
+.TP
+Various, see website or distribution. License: GPL v3+:
+.TP
+GNU GPL version 3 or later. (some parts LGPL v.2.1+ or MIT).
+.SH
+SEE ALSO
+.TP
+Website:
diff --git a/uxplay.1 b/uxplay.1
index 7a1fde2..4d2e715 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -1,4 +1,4 @@
-.TH UXPLAY "1" "May 2025" "1.72" "User Commands"
+.TH UXPLAY "1" "2025-10-26" "UxPlay 1.72" "User Commands"
.SH NAME
uxplay \- start AirPlay server
.SH SYNOPSIS
@@ -191,7 +191,9 @@ UxPlay 1.72: An open\-source AirPlay mirroring (+ audio streaming) server:
audio packets are dumped. "aud"= unknown format.
.PP
.TP
-\fB\-ble\fI fn\fR For BluetoothLE beacon: write PID to file fn ("off" to cancel)
+\fB\-ble\fI [fn]\fR For BluetoothLE beacon: write data to default file ~/.uxplay.ble
+.IP
+ optional: write to file "fn" ("fn" = "off" to cancel)
.TP
\fB\-d [n]\fR Enable debug logging; optional: n=1 to skip normal packet data.
.TP
diff --git a/uxplay.cpp b/uxplay.cpp
index 2f06242..49ef86d 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -924,7 +924,8 @@ static void print_info (char *name) {
printf(" =1,2,..; fn=\"audiodump\"; change with \"-admp [n] filename\".\n");
printf(" x increases when audio format changes. If n is given, <= n\n");
printf(" audio packets are dumped. \"aud\"= unknown format.\n");
- printf("-ble fn For BluetoothLE beacon: write PID to file fn (\"off\" to cancel)\n");
+ printf("-ble [fn] For BluetoothLE beacon: write data to file ~/.uxplay.ble\n");
+ printf(" optional: write to file \"fn\" (\"fn\" = \"off\" to cancel)\n");
printf("-d [n] Enable debug logging; optional: n=1 to skip normal packet data\n");
printf("-v Displays version information\n");
printf("-h Displays this help\n");
@@ -1386,8 +1387,8 @@ static void parse_arguments (int argc, char *argv[]) {
exit(1);
}
} else if (arg == "-ble" ) {
- if (option_has_value(i, argc, arg, argv[i+1])) {
- ble_filename.erase();
+ ble_filename.erase();
+ if (i < argc - 1 && *argv[i+1] != '-') {
i++;
if (strlen(argv[i]) != 3 || strncmp(argv[i], "off", 3)) {
ble_filename.append(argv[i]);
@@ -1397,8 +1398,18 @@ static void parse_arguments (int argc, char *argv[]) {
}
}
} else {
- fprintf(stderr,"option -ble must be followed by a filename for PID data or by \"off\"\n");
- exit(1);
+ static const char* homedir = get_homedir();
+ if (homedir) {
+ ble_filename = homedir;
+ ble_filename.append("/.uxplay.ble");
+ if (!file_has_write_access(ble_filename.c_str())) {
+ fprintf(stderr, "%s cannot be written to\n",ble_filename.c_str()) ;
+ exit(1);
+ }
+ } else {
+ fprintf(stderr,"failed to obtain home directory\n");
+ exit(1);
+ }
}
} else if (arg == "-bt709") {
bt709_fix = true;