add a python3 DBUs Bluetooth LE Service Discovery beacon controller

This commit is contained in:
F. Duncanh
2025-10-26 01:48:28 -04:00
parent 6d899820fb
commit 63f62e9f74
8 changed files with 705 additions and 553 deletions

View File

@@ -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 <a
href="#bluetooth-le-beacon-setup">given below</a>. <strong>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)</strong></p></li>
the UxPlay server, and which TCP port to contact UxPlay on. A python
script (Python &gt;=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
<strong>not</strong> require enhanced “root permissions” to run. A
windows version of this script is also planned for the future.
Instructions are <a href="#bluetooth-le-beacon-setup">given
below</a>.</p></li>
<li><p><strong>NEW on github</strong>: option
<code>-vrtp &lt;rest-of-pipeline&gt;</code> bypasses rendering by
UxPlay, and instead transmits rtp packets of decrypted h264 or h265
@@ -1437,11 +1439,12 @@ to a file to <em>n</em> or less. To change the name <em>audiodump</em>,
use -admp [n] <em>filename</em>. <em>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.</em></p>
<p><strong>-ble <em>filename</em></strong>. Enable Bluetooth beacon
Service Discovery. The PID and process name of the UxPlay process is
recorded in <em>filename</em>, which must be the full path to a
writeable file. (This file is created when UxPlay starts and deleted
when it stops.) <strong>See below for beacon setup
<p><strong>-ble [<em>filename</em>]</strong>. Enable Bluetooth beacon
Service Discovery. The port, PID and process name of the UxPlay process
is recorded by default in <code>~/.uxplay.ble</code> : (this file is
created when UxPlay starts and deleted when it stops.) Optionally the
file <em>filename</em>, which must be the full path to a writeable file
can instead be used. <strong>See below for beacon setup
instructions.</strong></p>
<p><strong>-d [n]</strong> 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.</p>
<h1 id="bluetooth-le-beacon-setup">Bluetooth LE beacon setup</h1>
<p>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
<code>-ble &lt;path-to-writeable-file&gt;</code>”, 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. <strong>This file is not
used in the simple manual method for creating a beacon described
below</strong>.</p>
<p>Bluetooth LE Service discovery uses a “beacon” broadcasting a simple
14-byte advertisement
<code>0D FF 4C 00 09 08 13 30 XX XX XX XX YY YY</code>” where XX XX XX
XX is an IPv4 internet address (and port YY YY) of the UxPlay host
translated into hexadecimal octets. For example,
<code>XX XX XX XX YY YY</code>” = “<code>C0 A8 01 FD 1B 58</code>
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 “<code>-p</code>” 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 (<code>&lt;n&gt;</code>)
of the 3 open TCP ports specified with uxplay option
<code>-p &lt;n&gt;</code>. If the <code>-p</code> 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 <code>-ble</code> option.</p>
<p>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 “<code>FF</code>”, called “Manufacturer-Specific Data”, with
“manufacturer code” “<code>4C 00</code>” = 0x004c = Apple (note the
reversal of octet order when two octets are combined to make a two-byte
unsigned short integer), and
<code>09 08 13 30 XX XX XX XX YY YY</code>” is the Apple-specific
data.</p>
<p>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 “<code>13 30 XX XX XX XX YY YY</code>” 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).</p>
<p>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 &lt;= AdvMin &lt;= AdvMax &lt;= 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 &lt; 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.</p>
<p>An automated script to setup and start the beacon should use a
high-level interface such as: (Linux) Bluez <a
href="https://manpages.opensuse.org/Leap-16.0/bluez/org.bluez.LEAdvertisement.5.en.html">LEAdvertisingManager1</a>
(with an <a
href="https://github.com/bluez/bluez/blob/master/test/example-advertisement">example</a>)
and (Windows 10/11) <a
href="https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementpublisher">BluetoothLEAdvertisementPublisherClass</a>
(with an <a
href="https://github.com/MicrosoftDocs/windows-dev-docs/blob/docs/uwp/devices-sensors/ble-beacon.md">example</a>).
<strong>We invite submission of Pull Requests for working
implementations!</strong></p>
<p>Until automated scripts are available, a simple Linux-only low-level
manual method is given below, using the <code>hcitool</code> and
<code>hciconfig</code> utilities which directly access the HCI stack,
and need elevated privileges (use <code>sudo</code>). These utilities
have been declared “deprecated” and “obsolete” by BlueZ developers: on
Debian-based Linux “<code>sudo apt install bluez</code>” still provides
<code>hcitool</code>, 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
<code>bluetoothctl</code> or <code>btmgmt</code> utilities, these
instructions will be updated.</p>
<p>First verify that a Bluetooth HCI interface is available:</p>
<pre><code>$hcitool dev
Devices:
hci1 E8:EA:6A:7C:3F:CC
hci0 08:BE:AC:40:A9:DC</code></pre>
<p>This shows two devices with their MAC addresses. You can use
<code>hciconfig -a</code>” 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.<br />
Choose which interface to use (we will use hci0), and reset it.</p>
<pre><code>$ sudo hciconfig hci0 reset
</code></pre>
<p><strong>Step 1.</strong> Configure the beacon by sending a configure
command 0x0006 to the Bluetooth LE stack 0x08. <code>hcitool</code>
echoes the HCI command and the 4-byte “HCI Event” response. The only
important part of the response is that the last byte is
<code>00</code>” (= “success”: other values are error codes):</p>
<pre><code>
$ sudo hcitool -i hci0 cmd 0x08 0x0006 0xa0 0x00 0xa0 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x07 0x00
&lt; HCI Command: ogf 0x08, ocf 0x0006, plen 15
A0 00 A0 00 03 00 00 00 00 00 00 00 00 07 00
&gt; HCI Event: 0x0e plen 4
02 06 20 00
</code></pre>
<p>The first “<code>0xa0 0x00</code>” sets AdvMin = 0x00a0 = 100 msec.
The second “<code>0xa0 0x00</code>” sets AdvMax = 0x00a0 = 100 msec.
Then “<code>0x03</code>” 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.</p>
<p>An Apple TV (Gen 3) seems to use AdvMin = AdvMax = 180 msec = 0x0120
(“<code>0x20 0x01</code>”).</p>
<p><strong>Step 2.</strong> Set the advertising message with HCI LE
command 0x0008. For this command, hcitool requires a 32 octet message
after <code>sudo hcitool -i hci0 cmd 0x08 0x0008</code>: 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 “<code>0xc0 0xa8 0x01 0xfd</code>” and
the TCP port as 0x1b 0x58 (port 7000 = 0x1b58):</p>
<pre><code>$ 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
&lt; 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
&gt; HCI Event: 0x0e plen 4
01 08 20 00 </code></pre>
<p><strong>Step 3</strong>. Start the beacon with a 1-byte message
<code>0x01</code>” = “on”, sent with HCI LE command 0x000a = 10:</p>
<pre><code>$ sudo hcitool -i hci0 cmd 0x08 0x000a 0x01
&lt; HCI Command: ogf 0x08, ocf 0x000a, plen 1
01
&gt; HCI Event: 0x0e plen 4
02 0A 20 00 </code></pre>
<p>The full length of the broadcasted beacon message is 46 bytes. To
stop the beacon, use this command to send the 1-byte message
<code>0x00</code>” = “off”.</p>
<p>The python&gt;=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.</p>
<p>If uxplay will be run with option “<code>uxplay -ble</code>” (so it
writes data for the Bluetooth beacon in the default BLE data file
<code>~/.uxplay.ble</code>), just run <code>uxplay-beacon.py</code> 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).</p>
<p>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 <code>~/.uxplay.beacon</code>, if it exists.
Configuration file entries are like the command line forms, one per line
(e.g., <code>--ipv4 192.168.1.100</code>). Lines commented out with an
initial <code>#</code> are ignored. Command line options override the
configuration file options.</p>
<ul>
<li>For testing Bluetooth beacon Service Discovery on Linux, you will
need to suppress the avahi-daemon which provides DNS-SD Service
Discovery on UxPlays Host system (replace <code>mask</code> and
<code>stop</code> below by <code>unmask</code> and <code>start</code> to
restore DNS-SD service):</li>
<li><p><code>--file &lt;config file&gt;</code> read beacon options from
<code>&lt;config file&gt;</code> instead of
<code>~/.uxplay.beacon</code>.</p></li>
<li><p><code>--ipv4 &lt;ipv4 address&gt;</code>. 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 <code>gethostbyname</code>. Only ipv4 addresses are
supported.</p></li>
<li><p><code>--path &lt;BLE data file&gt;</code>. This overrides the
default choice of BLE data file (<code>~/.uxplay.ble</code>) that is
monitored by the beacon script. This also requires that uxplay is run
with option “<code>uxplay -ble &lt;BLE data file&gt;</code>”.</p></li>
<li><p><code>--AdvMin x</code>, <code>--AdvMax y</code>. 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 &lt;=
x &lt;= y &lt;= 10240. If AdvMin=AdvMax, the interval is fixed: if
AdvMin &lt; 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.</p></li>
<li><p><code>--index x</code> (default x = 0, x &gt;= 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).
<em>Note: running multiple beacons simultaneously on the same host has
not been tested.</em></p></li>
</ul>
<p>If you wish to test Bluetooth LE Service Discovery on Linux/*BSD, you
can disable DNS_SD Service discovery by the avahi-daemon with</p>
<pre><code>$ sudo systemctl mask avahi-daemon.socket
$ sudo systemctl stop avahi-daemon</code></pre>
<p>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.)</p>
<p>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 <code>0x03</code>) in
Step 1 from “TxAdd” = <code>0x00</code> to TxAdd = <code>0x01</code>,
and add an intermediate “step 1.5”:</p>
<p><strong>Step 1.5</strong> Choose 6 random bytes r1, r2, r3, r4, r5,
r6, such as “<code>0x52 0xaa, 0xaa, 0x3a, 0xb4, 0x2f</code>”, and use
HCI LE command 0x0005 to set the random address:</p>
<pre><code>$sudo hcitool -i hci0 cmd 0x08 0x0005 0x52 0xaa 0xaa 0x3a 0xb4 0x2f
&lt; HCI Command: ogf 0x08, ocf 0x0005, plen 6
52 AA AA 3A B4 2F
&gt; HCI Event: 0x0e plen 4
02 05 20 00 </code></pre>
<p>On a Bluetooth packet sniffer with wireshark, this address displays
as: <strong>Advertising Address: 2f:b4:3a:aa:aa:52</strong>. 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 TVs also add a length 2 type 0x01 (“Flags”) Advertising PDU
<code>0x02 0x01 0x1a</code>” 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.</p>
<p>To restore DNS_SD Service discovery, replace “mask” by “unmask”, and
“stop” by start”.</p>
<p>For more information, see the <a
href="https://github.com/FDH2/UxPlay/wiki/Bluetooth_LE_beacon">wiki
page</a> This has useful information if you wish to build a python
beacon controller script for Windows (we would like to have one!).</p>
<ul>
<li><strong>Our current understanding is that Bluetooth LE AirPlay
Service Discovery only supports broadcast of IPv4 addresses. Please let