From 50e74f25d169e43566e670b449c44b2f04ae6931 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Tue, 28 Oct 2025 20:19:58 -0400 Subject: [PATCH] uxplay-beacon.py: code cleanups --- Bluetooth_LE_beacon/dbus/uxplay-beacon.py | 105 ++++++++++++---------- README.html | 12 ++- README.md | 14 ++- README.txt | 14 ++- 4 files changed, 93 insertions(+), 52 deletions(-) diff --git a/Bluetooth_LE_beacon/dbus/uxplay-beacon.py b/Bluetooth_LE_beacon/dbus/uxplay-beacon.py index 8c4e863..b73b073 100644 --- a/Bluetooth_LE_beacon/dbus/uxplay-beacon.py +++ b/Bluetooth_LE_beacon/dbus/uxplay-beacon.py @@ -5,30 +5,21 @@ # 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 +try: + from gi.repository import GLib +except ImportError: + print(f"ImportError: failed to 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) +server_address = None BLUEZ_SERVICE_NAME = 'org.bluez' LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1' @@ -112,7 +103,7 @@ class AirPlay_Service_Discovery_Advertisement(dbus.service.Object): in_signature='', out_signature='') def Release(self): - print('%s: Released!' % self.path) + print(f'{self.path}: Released!') class AirPlayAdvertisement(AirPlay_Service_Discovery_Advertisement): @@ -134,14 +125,14 @@ class AirPlayAdvertisement(AirPlay_Service_Discovery_Advertisement): def register_ad_cb(): - global ipv4_str - global port - print(f'AirPlay Service_Discovery Advertisement ({ipv4_str}:{port}) registered') + global server_address + print(f'AirPlay Service_Discovery Advertisement ({server_address}) registered') def register_ad_error_cb(error): - print('Failed to register advertisement: ' + str(error)) - mainloop.quit() + print(f'Failed to register advertisement: {error}') + global ad_manager + ad_manager = None def find_adapter(bus): @@ -159,11 +150,13 @@ def find_adapter(bus): def setup_beacon(ipv4_str, port, advmin, advmax, index): global ad_manager global airplay_advertisement + global server_address + server_address = f"{ipv4_str}:{port}" dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() adapter = find_adapter(bus) if not adapter: - print('LEAdvertisingManager1 interface not found') + print(f'LEAdvertisingManager1 interface not found') return adapter_props = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter), "org.freedesktop.DBus.Properties") @@ -176,9 +169,16 @@ def setup_beacon(ipv4_str, port, advmin, advmax, index): def beacon_on(): global ad_manager + global airplay_advertisement ad_manager.RegisterAdvertisement(airplay_advertisement.get_path(), {}, reply_handler=register_ad_cb, error_handler=register_ad_error_cb) + if ad_manager is None: + airplay_advertisement = None + return False + else: + return True + def beacon_off(): global ad_manager global airplay_advertisement @@ -190,12 +190,24 @@ def beacon_off(): #==generic code (non-dbus) below here ============= +import argparse +import os +import sys +import psutil +import struct +import socket # global variables beacon_is_running = False beacon_is_pending_on = 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(): global beacon_is_running global port @@ -204,8 +216,7 @@ def start_beacon(): global advmax global index setup_beacon(ipv4_str, port, advmin, advmax, index) - beacon_on() - beacon_is_running = True + beacon_is_running = beacon_on() def stop_beacon(): global beacon_is_running @@ -227,12 +238,10 @@ def check_pending(): 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 @@ -259,12 +268,12 @@ def check_file_exists(file_path): if not beacon_is_running: beacon_is_pending_on = True 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: os.remove(file_path) - print(f"File '{file_path}' deleted successfully.") + print(f'File "{file_path}" deleted successfully.') except FileNotFoundError: - print(f"File '{file_path}' not found.") + print(f'File "{file_path}" not found.') if beacon_is_running: beacon_is_pending_off = True else: @@ -281,7 +290,7 @@ def process_input(value): my_integer = int(value) return my_integer 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 @@ -289,11 +298,11 @@ def process_input(value): #check AdvInterval def check_adv_intrvl(min, max): if not (100 <= min): - raise ValueError("AdvMin was smaller than 100 msecs") + raise ValueError('AdvMin was smaller than 100 msecs') if not (max >= min): - raise ValueError("AdvMax was smaller than AdvMin") + raise ValueError('AdvMax was smaller than AdvMin') 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): @@ -311,7 +320,7 @@ def main(file_path, ipv4_str_in, advmin_in, advmax_in, index_in): try: check_adv_intrvl(advmin, advmax) except ValueError as e: - print(f"Error: {e}") + print(f'Error: {e}') raise SystemExit(1) 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.run() except KeyboardInterrupt: - print(f"\nExiting ...") + print(f'\nExiting ...') sys.exit(0) if __name__ == '__main__': + + + if not sys.version_info >= (3,6): + print("uxplay-beacon.py requires Python 3.6 or higher") # Create an ArgumentParser object parser = argparse.ArgumentParser( @@ -383,7 +396,7 @@ if __name__ == '__main__': index = int(0) if args.file: - print(f"Using config file: {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: @@ -403,22 +416,22 @@ if __name__ == '__main__': if value.isdigit(): advmin = int(value) 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) elif key == "--AdvMax": if value.isdigit(): advmax = int(value) 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) elif key == "--index": if value.isdigit(): index = int(value) 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) else: - print(f"Unknown key \"{key}\" in config file {args.file}") + print(f'Unknown key "{key}" in config file {args.file}') raise SystemExit(1) if args.ipv4 == "use gethostbyname": @@ -431,26 +444,26 @@ if __name__ == '__main__': if args.AdvMin.isdigit(): advmin = int(args.AdvMin) else: - print("Invalid input (AdvMin) {args.AdvMin}") + print(f'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}") + print(f'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}") + print(f'Invalid input (AdvMin) {args.AdvMin}') raise SystemExit(1) 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"(Press Ctrl+C to exit)") + 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/README.html b/README.html index 5f0765e..ceb564b 100644 --- a/README.html +++ b/README.html @@ -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 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 +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 @@ -1457,7 +1457,15 @@ this to see even more of the GStreamer inner workings.

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.

+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 ~/.uxplay.ble), just run uxplay-beacon.py in a diff --git a/README.md b/README.md index ce57702..064e077 100644 --- a/README.md +++ b/README.md @@ -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 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", - 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. A windows version of this script is also planned for the future. 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. 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 `~/.uxplay.ble`), just run ``uxplay-beacon.py`` in a separate terminal. The python script will start diff --git a/README.txt b/README.txt index 4f9292b..cc63607 100644 --- a/README.txt +++ b/README.txt @@ -12,7 +12,7 @@ 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): + 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). @@ -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 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. +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 `~/.uxplay.ble`), just