uxplay-beacon.py: code cleanups

This commit is contained in:
F. Duncanh
2025-10-28 20:19:58 -04:00
parent 63f62e9f74
commit 50e74f25d1
4 changed files with 93 additions and 52 deletions

View File

@@ -5,30 +5,21 @@
# a standalone python-3.6 or later DBus-based AirPlay Service-Discovery Bluetooth LE beacon for UxPlay # a standalone python-3.6 or later DBus-based AirPlay Service-Discovery Bluetooth LE beacon for UxPlay
# (c) F. Duncanh, October 2025 # (c) F. Duncanh, October 2025
import argparse
import gi import gi
import os try:
import sys from gi.repository import GLib
import psutil except ImportError:
import struct print(f"ImportError: failed to import GLib")
import socket
from gi.repository import GLib
import dbus import dbus
import dbus.exceptions import dbus.exceptions
import dbus.mainloop.glib import dbus.mainloop.glib
import dbus.service import dbus.service
import time
import threading
ad_manager = None ad_manager = None
airplay_advertisement = None airplay_advertisement = None
port = int(0) server_address = None
advmin = int(100)
advmax = int(100)
ipv4_str = "ipv4_address"
index = int(0)
BLUEZ_SERVICE_NAME = 'org.bluez' BLUEZ_SERVICE_NAME = 'org.bluez'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1' LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
@@ -112,7 +103,7 @@ class AirPlay_Service_Discovery_Advertisement(dbus.service.Object):
in_signature='', in_signature='',
out_signature='') out_signature='')
def Release(self): def Release(self):
print('%s: Released!' % self.path) print(f'{self.path}: Released!')
class AirPlayAdvertisement(AirPlay_Service_Discovery_Advertisement): class AirPlayAdvertisement(AirPlay_Service_Discovery_Advertisement):
@@ -134,14 +125,14 @@ class AirPlayAdvertisement(AirPlay_Service_Discovery_Advertisement):
def register_ad_cb(): def register_ad_cb():
global ipv4_str global server_address
global port print(f'AirPlay Service_Discovery Advertisement ({server_address}) registered')
print(f'AirPlay Service_Discovery Advertisement ({ipv4_str}:{port}) registered')
def register_ad_error_cb(error): def register_ad_error_cb(error):
print('Failed to register advertisement: ' + str(error)) print(f'Failed to register advertisement: {error}')
mainloop.quit() global ad_manager
ad_manager = None
def find_adapter(bus): def find_adapter(bus):
@@ -159,11 +150,13 @@ def find_adapter(bus):
def setup_beacon(ipv4_str, port, advmin, advmax, index): def setup_beacon(ipv4_str, port, advmin, advmax, index):
global ad_manager global ad_manager
global airplay_advertisement global airplay_advertisement
global server_address
server_address = f"{ipv4_str}:{port}"
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus() bus = dbus.SystemBus()
adapter = find_adapter(bus) adapter = find_adapter(bus)
if not adapter: if not adapter:
print('LEAdvertisingManager1 interface not found') print(f'LEAdvertisingManager1 interface not found')
return return
adapter_props = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter), adapter_props = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
"org.freedesktop.DBus.Properties") "org.freedesktop.DBus.Properties")
@@ -176,9 +169,16 @@ def setup_beacon(ipv4_str, port, advmin, advmax, index):
def beacon_on(): def beacon_on():
global ad_manager global ad_manager
global airplay_advertisement
ad_manager.RegisterAdvertisement(airplay_advertisement.get_path(), {}, ad_manager.RegisterAdvertisement(airplay_advertisement.get_path(), {},
reply_handler=register_ad_cb, reply_handler=register_ad_cb,
error_handler=register_ad_error_cb) error_handler=register_ad_error_cb)
if ad_manager is None:
airplay_advertisement = None
return False
else:
return True
def beacon_off(): def beacon_off():
global ad_manager global ad_manager
global airplay_advertisement global airplay_advertisement
@@ -190,12 +190,24 @@ def beacon_off():
#==generic code (non-dbus) below here ============= #==generic code (non-dbus) below here =============
import argparse
import os
import sys
import psutil
import struct
import socket
# global variables # global variables
beacon_is_running = False beacon_is_running = False
beacon_is_pending_on = False beacon_is_pending_on = False
beacon_is_pending_off = False beacon_is_pending_off = False
port = int(0)
advmin = int(100)
advmax = int(100)
ipv4_str = "ipv4_address"
index = int(0)
def start_beacon(): def start_beacon():
global beacon_is_running global beacon_is_running
global port global port
@@ -204,8 +216,7 @@ def start_beacon():
global advmax global advmax
global index global index
setup_beacon(ipv4_str, port, advmin, advmax, index) setup_beacon(ipv4_str, port, advmin, advmax, index)
beacon_on() beacon_is_running = beacon_on()
beacon_is_running = True
def stop_beacon(): def stop_beacon():
global beacon_is_running global beacon_is_running
@@ -227,12 +238,10 @@ def check_pending():
global beacon_is_pending_on global beacon_is_pending_on
global beacon_is_pending_off global beacon_is_pending_off
if beacon_is_running: if beacon_is_running:
#print(f"beacon running")
if beacon_is_pending_off: if beacon_is_pending_off:
stop_beacon() stop_beacon()
beacon_is_pending_off = False beacon_is_pending_off = False
else: else:
#print(f"beacon not running")
if beacon_is_pending_on: if beacon_is_pending_on:
start_beacon() start_beacon()
beacon_is_pending_on = False beacon_is_pending_on = False
@@ -259,12 +268,12 @@ def check_file_exists(file_path):
if not beacon_is_running: if not beacon_is_running:
beacon_is_pending_on = True beacon_is_pending_on = True
else: else:
print(f"orphan beacon file {file_path} exists, but process {pname} (pid {pid}) is no longer active") print(f'orphan beacon file {file_path} exists, but process {pname} (pid {pid}) is no longer active')
try: try:
os.remove(file_path) os.remove(file_path)
print(f"File '{file_path}' deleted successfully.") print(f'File "{file_path}" deleted successfully.')
except FileNotFoundError: except FileNotFoundError:
print(f"File '{file_path}' not found.") print(f'File "{file_path}" not found.')
if beacon_is_running: if beacon_is_running:
beacon_is_pending_off = True beacon_is_pending_off = True
else: else:
@@ -281,7 +290,7 @@ def process_input(value):
my_integer = int(value) my_integer = int(value)
return my_integer return my_integer
except ValueError: except ValueError:
printf(f"Error: could not convert '{value}' to integer: {my_integer}") print(f'Error: could not convert "{value}" to integer: {my_integer}')
return None return None
@@ -289,11 +298,11 @@ def process_input(value):
#check AdvInterval #check AdvInterval
def check_adv_intrvl(min, max): def check_adv_intrvl(min, max):
if not (100 <= min): if not (100 <= min):
raise ValueError("AdvMin was smaller than 100 msecs") raise ValueError('AdvMin was smaller than 100 msecs')
if not (max >= min): if not (max >= min):
raise ValueError("AdvMax was smaller than AdvMin") raise ValueError('AdvMax was smaller than AdvMin')
if not (max <= 10240): if not (max <= 10240):
raise ValueError("AdvMax was larger than 10240 msecs") raise ValueError('AdvMax was larger than 10240 msecs')
def main(file_path, ipv4_str_in, advmin_in, advmax_in, index_in): def main(file_path, ipv4_str_in, advmin_in, advmax_in, index_in):
@@ -311,7 +320,7 @@ def main(file_path, ipv4_str_in, advmin_in, advmax_in, index_in):
try: try:
check_adv_intrvl(advmin, advmax) check_adv_intrvl(advmin, advmax)
except ValueError as e: except ValueError as e:
print(f"Error: {e}") print(f'Error: {e}')
raise SystemExit(1) raise SystemExit(1)
GLib.timeout_add_seconds(5, on_timeout, file_path) GLib.timeout_add_seconds(5, on_timeout, file_path)
@@ -319,12 +328,16 @@ def main(file_path, ipv4_str_in, advmin_in, advmax_in, index_in):
mainloop = GLib.MainLoop() mainloop = GLib.MainLoop()
mainloop.run() mainloop.run()
except KeyboardInterrupt: except KeyboardInterrupt:
print(f"\nExiting ...") print(f'\nExiting ...')
sys.exit(0) sys.exit(0)
if __name__ == '__main__': if __name__ == '__main__':
if not sys.version_info >= (3,6):
print("uxplay-beacon.py requires Python 3.6 or higher")
# Create an ArgumentParser object # Create an ArgumentParser object
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@@ -383,7 +396,7 @@ if __name__ == '__main__':
index = int(0) index = int(0)
if args.file: if args.file:
print(f"Using config file: {args.file}") print(f'Using config file: {args.file}')
if os.path.exists(args.file): if os.path.exists(args.file):
with open(args.file, 'r') as file: with open(args.file, 'r') as file:
for line in file: for line in file:
@@ -403,22 +416,22 @@ if __name__ == '__main__':
if value.isdigit(): if value.isdigit():
advmin = int(value) advmin = int(value)
else: else:
print(f"Invalid config file input (--AdvMin) {value} in {args.file}") print(f'Invalid config file input (--AdvMin) {value} in {args.file}')
raise SystemExit(1) raise SystemExit(1)
elif key == "--AdvMax": elif key == "--AdvMax":
if value.isdigit(): if value.isdigit():
advmax = int(value) advmax = int(value)
else: else:
print(f"Invalid config file input (--AdvMax) {value} in {args.file}") print(f'Invalid config file input (--AdvMax) {value} in {args.file}')
raise SystemExit(1) raise SystemExit(1)
elif key == "--index": elif key == "--index":
if value.isdigit(): if value.isdigit():
index = int(value) index = int(value)
else: else:
print(f"Invalid config file input (--index) {value} in {args.file}") print(f'Invalid config file input (--index) {value} in {args.file}')
raise SystemExit(1) raise SystemExit(1)
else: else:
print(f"Unknown key \"{key}\" in config file {args.file}") print(f'Unknown key "{key}" in config file {args.file}')
raise SystemExit(1) raise SystemExit(1)
if args.ipv4 == "use gethostbyname": if args.ipv4 == "use gethostbyname":
@@ -431,26 +444,26 @@ if __name__ == '__main__':
if args.AdvMin.isdigit(): if args.AdvMin.isdigit():
advmin = int(args.AdvMin) advmin = int(args.AdvMin)
else: else:
print("Invalid input (AdvMin) {args.AdvMin}") print(f'Invalid input (AdvMin) {args.AdvMin}')
raise SystemExit(1) raise SystemExit(1)
if args.AdvMax != "0": if args.AdvMax != "0":
if args.AdvMax.isdigit(): if args.AdvMax.isdigit():
advmax = int(args.AdvMax) advmax = int(args.AdvMax)
else: else:
print("Invalid input (AdvMin) {args.AdvMin}") print(f'Invalid input (AdvMin) {args.AdvMin}')
raise SystemExit(1) raise SystemExit(1)
if args.index != "0": if args.index != "0":
if args.index.isdigit(): if args.index.isdigit():
index = int(args.index) index = int(args.index)
else: else:
print("Invalid input (AdvMin) {args.AdvMin}") print(f'Invalid input (AdvMin) {args.AdvMin}')
raise SystemExit(1) raise SystemExit(1)
if index < 0: if index < 0:
raise ValueError("index was negative (forbidden)") 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'AirPlay Service-Discovery Bluetooth LE beacon: using BLE file {args.path}, advmin:advmax {advmin}:{advmax} index:{index}')
print(f"(Press Ctrl+C to exit)") print(f'(Press Ctrl+C to exit)')
main(args.path, ipv4_str, advmin, advmax, index) main(args.path, ipv4_str, advmin, advmax, index)

View File

@@ -18,7 +18,7 @@ informing nearby iOS/macOS devices of the local IPv4 network address of
the UxPlay server, and which TCP port to contact UxPlay on. A python the UxPlay server, and which TCP port to contact UxPlay on. A python
script (Python &gt;=3.6) “uxplay-beacon.py”, to broadcast the script (Python &gt;=3.6) “uxplay-beacon.py”, to broadcast the
Service-Discovery advertisement will be installed on systems with DBus Service-Discovery advertisement will be installed on systems with DBus
support (Linux and *BSD, using Bluez for Bluetooth control): this does support (Linux and *BSD, using BlueZ for Bluetooth control): this does
<strong>not</strong> require enhanced “root permissions” to run. A <strong>not</strong> require enhanced “root permissions” to run. A
windows version of this script is also planned for the future. windows version of this script is also planned for the future.
Instructions are <a href="#bluetooth-le-beacon-setup">given Instructions are <a href="#bluetooth-le-beacon-setup">given
@@ -1457,7 +1457,15 @@ this to see even more of the GStreamer inner workings.</p>
<p>The python&gt;=3.6 script for running a Bluetooth-LE Service <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 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 Linux and *BSD) is available, and it is only installed on systems which
support DBus.</p> support DBus. Bluetooth &gt;= 4.0 hardware on the host computer is
required: a cheap USB bluetooth dongle can be used. Bluetooth support
(BlueZ) must be installed (on Debian-based systems:
<code>sudo apt install bluez bluez-tools</code>; recent Ubuntu releases
provide bluez as a snap package).</p>
<p>In addition to standard Python3 libraries, you may need to install
the gi, dbus, and psutil Python libraries used by uxplay-beacon.py. On
Debian-based systems:</p>
<pre><code>sudo apt install python3-gi python3-dbus python3-psutil</code></pre>
<p>If uxplay will be run with option “<code>uxplay -ble</code>” (so it <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 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 <code>~/.uxplay.ble</code>), just run <code>uxplay-beacon.py</code> in a

View File

@@ -6,7 +6,7 @@
service discovery). The user must set up a Bluetooth LE "beacon", (a USB 4.0 or later "dongle" can be used). See instructions 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 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. A python script (Python >=3.6) "uxplay-beacon.py", 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): 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. this does **not** require enhanced "root permissions" to run.
A windows version of this script is also planned for the future. A windows version of this script is also planned for the future.
Instructions are [given below](#bluetooth-le-beacon-setup). Instructions are [given below](#bluetooth-le-beacon-setup).
@@ -1475,7 +1475,17 @@ GStreamer inner workings.
The python>=3.6 script for running a Bluetooth-LE Service Discovery beacon is uxplay-beacon.py. 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 Currently only a DBus version (for Linux and *BSD) is available, and it is only installed on systems which
support DBus. support DBus. Bluetooth >= 4.0 hardware on the host computer is required: a cheap USB bluetooth dongle
can be used. Bluetooth support (BlueZ) must be installed (on Debian-based systems: `sudo apt install bluez bluez-tools`;
recent Ubuntu releases provide bluez as a snap package).
In addition to standard Python3 libraries, you may need to install the gi, dbus, and psutil Python libraries used by
uxplay-beacon.py. On Debian-based systems:
```
sudo apt install python3-gi python3-dbus python3-psutil
```
If uxplay will be run with option "`uxplay -ble`" (so it writes data for the Bluetooth beacon in the default BLE data file 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 `~/.uxplay.ble`), just run ``uxplay-beacon.py`` in a separate terminal. The python script will start

View File

@@ -12,7 +12,7 @@
server, and which TCP port to contact UxPlay on. A python script server, and which TCP port to contact UxPlay on. A python script
(Python \>=3.6) "uxplay-beacon.py", to broadcast the (Python \>=3.6) "uxplay-beacon.py", to broadcast the
Service-Discovery advertisement will be installed on systems with Service-Discovery advertisement will be installed on systems with
DBus support (Linux and \*BSD, using Bluez for Bluetooth control): DBus support (Linux and \*BSD, using BlueZ for Bluetooth control):
this does **not** require enhanced "root permissions" to run. A this does **not** require enhanced "root permissions" to run. A
windows version of this script is also planned for the future. windows version of this script is also planned for the future.
Instructions are [given below](#bluetooth-le-beacon-setup). Instructions are [given below](#bluetooth-le-beacon-setup).
@@ -1499,7 +1499,17 @@ this to see even more of the GStreamer inner workings.
The python\>=3.6 script for running a Bluetooth-LE Service Discovery 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 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 \*BSD) is available, and it is only installed on systems which support
DBus. DBus. Bluetooth \>= 4.0 hardware on the host computer is required: a
cheap USB bluetooth dongle can be used. Bluetooth support (BlueZ) must
be installed (on Debian-based systems:
`sudo apt install bluez bluez-tools`; recent Ubuntu releases provide
bluez as a snap package).
In addition to standard Python3 libraries, you may need to install the
gi, dbus, and psutil Python libraries used by uxplay-beacon.py. On
Debian-based systems:
sudo apt install python3-gi python3-dbus python3-psutil
If uxplay will be run with option "`uxplay -ble`" (so it writes data for 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 the Bluetooth beacon in the default BLE data file `~/.uxplay.ble`), just