import os, sys import logging DEBUG = os.environ.get('DEBUG') logging.basicConfig(stream=sys.stdout, format='[%(asctime)s] %(levelname)s %(module)s/%(funcName)s - %(message)s', level=logging.DEBUG if DEBUG else logging.INFO) import time import json import asyncio from aiomqtt import Client from dbus_next.aio import MessageBus from dbus_next.service import ServiceInterface, method from dbus_next import Variant # --- Bluetooth constants and agent --- BLUEZ_SERVICE = 'org.bluez' ADAPTER_IFACE = 'org.bluez.Adapter1' DEVICE_IFACE = 'org.bluez.Device1' AGENT_IFACE = 'org.bluez.Agent1' AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1' AGENT_PATH = '/dev/what/agent' CAPABILITY = 'NoInputNoOutput' class Agent(ServiceInterface): def __init__(self, interface_name): super().__init__(interface_name) @method def Release(self): logging.info('Agent Released') @method def RequestPinCode(self, device: str) -> str: logging.info(f"RequestPinCode for {device}, returning static PIN") return "0000" @method def RequestPasskey(self, device: str) -> int: logging.info(f"RequestPasskey for {device}") return 0 @method def DisplayPasskey(self, device: str, passkey: int, entered: int): logging.info(f"DisplayPasskey for {device}: {passkey}") @method def DisplayPinCode(self, device: str, pincode: str): logging.info(f"DisplayPinCode for {device}: {pincode}") @method def RequestConfirmation(self, device: str, passkey: int): logging.info(f"RequestConfirmation for {device} with passkey {passkey}") # Automatically confirm and trust loop = asyncio.get_event_loop() loop.create_task(trust_and_connect_device(device)) @method def RequestAuthorization(self, device: str): logging.info(f"RequestAuthorization for {device}") pass @method def AuthorizeService(self, device: str, uuid: str): logging.info(f"AuthorizeService for {device} with uuid {uuid}") pass @method def Cancel(self): logging.info('Pairing Cancelled') async def trust_and_connect_device(device_path): logging.info(f'Trusting and connecting to {device_path}') try: bus = await MessageBus().connect() introspection = await bus.introspect(BLUEZ_SERVICE, device_path) device_obj = bus.get_proxy_object(BLUEZ_SERVICE, device_path, introspection) device_props = device_obj.get_interface('org.freedesktop.DBus.Properties') await device_props.call_set(DEVICE_IFACE, 'Trusted', Variant('b', True)) logging.info(f'Trusted device {device_path}') device_iface = device_obj.get_interface(DEVICE_IFACE) await device_iface.call_connect() logging.info(f'Connected to device {device_path}') except Exception as e: logging.error(f'Failed to trust/connect to {device_path}: {e}') async def get_adapter(): bus = await MessageBus().connect() introspection = await bus.introspect(BLUEZ_SERVICE, '/org/bluez') manager_obj = bus.get_proxy_object(BLUEZ_SERVICE, '/org/bluez', introspection) manager_iface = manager_obj.get_interface('org.freedesktop.DBus.ObjectManager') managed_objects = await manager_iface.call_get_managed_objects() for path, ifaces in managed_objects.items(): if ADAPTER_IFACE in ifaces: adapter_introspection = await bus.introspect(BLUEZ_SERVICE, path) return bus.get_proxy_object(BLUEZ_SERVICE, path, adapter_introspection) return None async def register_agent(): bus = await MessageBus().connect() agent = Agent(AGENT_IFACE) bus.export(AGENT_PATH, agent) introspection = await bus.introspect(BLUEZ_SERVICE, '/org/bluez') manager_obj = bus.get_proxy_object(BLUEZ_SERVICE, '/org/bluez', introspection) agent_manager = manager_obj.get_interface(AGENT_MANAGER_IFACE) try: await agent_manager.call_register_agent(AGENT_PATH, CAPABILITY) logging.info(f"Agent registered at {AGENT_PATH} with capability {CAPABILITY}") await agent_manager.call_request_default_agent(AGENT_PATH) logging.info("Agent set as default") except Exception as e: logging.error(f'Failed to register agent: {e}') logging.info('Trying to unregister and register again') try: await agent_manager.call_unregister_agent(AGENT_PATH) await agent_manager.call_register_agent(AGENT_PATH, CAPABILITY) await agent_manager.call_request_default_agent(AGENT_PATH) logging.info("Agent registered after unregistering") except Exception as e2: logging.error(f'Failed to register agent again: {e2}') # --- End Bluetooth --- async def manage_bluetooth(): await register_agent() # The agent will handle things, this task can just sleep while True: await asyncio.sleep(3600) async def process_bluetooth_command(topic, text): logging.info('Bluetooth command: %s', text) if text == "pair": logging.info('Starting pairing process by making adapter discoverable') adapter_obj = await get_adapter() if not adapter_obj: logging.error('Bluetooth adapter not found') return adapter_props = adapter_obj.get_interface('org.freedesktop.DBus.Properties') try: await adapter_props.call_set(ADAPTER_IFACE, 'Discoverable', Variant('b', True)) await adapter_props.call_set(ADAPTER_IFACE, 'Pairable', Variant('b', True)) logging.info('Adapter is discoverable and pairable for 120 seconds') await asyncio.sleep(120) await adapter_props.call_set(ADAPTER_IFACE, 'Discoverable', Variant('b', False)) logging.info('Adapter is no longer discoverable') except Exception as e: logging.error(f"Failed to set adapter properties: {e}") async def process_mqtt(message): text = message.payload.decode() topic = message.topic.value logging.debug('MQTT topic: %s, message: %s', topic, text) if topic.startswith('iot/12ser/bluetooth'): await process_bluetooth_command(topic, text) else: logging.debug('Invalid topic, returning') return async def fetch_mqtt(): await asyncio.sleep(3) async with Client( hostname='10.55.0.106', port=1883, ) as client: await client.subscribe('iot/12ser/#') async for message in client.messages: loop = asyncio.get_event_loop() loop.create_task(process_mqtt(message)) if __name__ == '__main__': logging.info('') logging.info('==========================') logging.info('Booting up...') loop = asyncio.get_event_loop() a = loop.create_task(manage_bluetooth()) loop.run_until_complete(fetch_mqtt())