#!/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 AirPlay Service-Discovery Bluetooth LE beacon for UxPlay # (c) F. Duncanh, October 2025 import sys if not sys.version_info >= (3,6): print("uxplay-beacon.py requires Python 3.6 or higher") import importlib import argparse import textwrap import os import struct import socket import time import platform import ipaddress try: import psutil except ImportError as e: print(f'ImportError {e}: failed to import psutil') print(f' install the python3 psutil package') raise SystemExit(1) # global variables beacon_is_running = False beacon_is_pending_on = False beacon_is_pending_off = False advertised_port = None port = None advmin = None advmax = None ipv4_str = None index = None windows = 'Windows' linux = 'Linux' os_name = platform.system() mainloop = None # BLE modules BLEUIO = 'BleuIO' WINRT = 'winrt' BLUEZ = 'BlueZ' HCI = 'HCI' # external functions that must be supplied by loading a module: from typing import Optional def setup_beacon(ipv4_str: str, port: int, advmin: Optional[int], advmax: Optional[int], index: Optional[int]) -> bool: return False def beacon_on() ->Optional[int]: return None def beacon_off(): return def find_device(device: Optional[str]) -> Optional[str]: return None #internal functions def exit(err_text): print(err_text) raise SystemExit(1) def start_beacon(): global beacon_is_running global port global ipv4_str global advmin global advmax global index if beacon_is_running: exit('code error, should not happen') setup_beacon(ipv4_str, port, advmin, advmax, index) advertised_port = beacon_on() beacon_is_running = advertised_port is not None if not beacon_is_running: exit('Failed to start beacon:\ngiving up, check Bluetooth adapter') def stop_beacon(): global beacon_is_running global advertised_port beacon_off() advertised_port = None beacon_is_running = False def pid_is_running(pid): return psutil.pid_exists(pid) def check_port(port): if advertised_port is None or port == advertised_port: return True else: return 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_pending_on global beacon_is_pending_off if beacon_is_running: if beacon_is_pending_off: stop_beacon() beacon_is_pending_off = False else: 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_pending_on global beacon_is_pending_off pname = "process name unread" if os.path.isfile(file_path): test = True try: with open(file_path, 'rb') as file: data = file.read(2) port = struct.unpack('= 4.0 HCI device for HCI module find_device = ble.find_device need_device = True if need_device: use_device = find_device(device_address) if use_device is None: err = f'No devices needed for BLE type {ble_type} were found' exit(err) if device_address is not None and use_device != device_address: print(f'Error: A required device was NOT found at {device_address} given as an optional argument') exit('(Note: required devices WERE found and are listed above)') print(f'using the required device found at {use_device}') else: #start beacon as test to see if Bluetooth is available, (WINRT and BLUEZ) test = None # initial test to see if Bluetooth is available setup_beacon(ipv4_str, 1, advmin, advmax, index) test = beacon_on() beacon_off() if test is not None: print(f"test passed ({ble_type}") advminmax = f'' indx = f'' if ble_type != WINRT: advminmax = f'[advmin:advmax]={advmin}:{advmax}' if ble_type == BLUEZ: indx = f'index {index}' print(f'AirPlay Service-Discovery Bluetooth LE beacon: BLE file {path} {advminmax} {indx}') print(f'Advertising IP address {ipv4_str}') print(f'(Press Ctrl+C to exit)') main(path, ipv4_str, advmin, advmax, index)