"""CircuitPython BLE UART utility. Wraps the adafruit_ble library to advertise Nordic UART Service and scan for nearby devices. """ import time from adafruit_ble import BLERadio from adafruit_ble.services.nordic import UARTService from adafruit_ble.advertising.standard import ProvideServicesAdvertisement class BLEUART: def __init__(self, ble=None, name="ESP32-S3-RLCD"): self.ble = BLERadio() self.ble.name = name self.uart = UARTService() self.advertisement = ProvideServicesAdvertisement(self.uart) self.rx_callback = None self._is_advertising = False self._start_advertise() def _start_advertise(self): if not self.ble.connected and not self._is_advertising: try: self.ble.start_advertising(self.advertisement) self._is_advertising = True print(f"BLE advertising started as '{self.ble.name}'") except Exception as e: print(f"Failed to start BLE advertising: {e}") def on_rx(self, callback): self.rx_callback = callback return callback def update(self): """Polls the UART stream for incoming data and triggers callbacks.""" if self.ble.connected: self._is_advertising = False try: if self.uart.in_waiting: data = self.uart.read(self.uart.in_waiting) if self.rx_callback and data: try: decoded = data.decode("utf-8").strip() self.rx_callback(decoded) except: self.rx_callback(data) except Exception as e: print(f"Error reading BLE RX: {e}") else: if not self._is_advertising: self._start_advertise() def write(self, data): if not self.ble.connected: return False if isinstance(data, str): data = data.encode("utf-8") try: self.uart.write(data) return True except Exception as e: print(f"Error sending BLE data: {e}") return False def is_connected(self): return self.ble.connected def scan(self, duration_ms=3000): """Scans for nearby BLE devices.""" duration_s = duration_ms / 1000.0 results = {} was_advertising = self._is_advertising if was_advertising: try: self.ble.stop_advertising() except: pass self._is_advertising = False try: print("Starting BLE scan...") for adv in self.ble.start_scan(timeout=duration_s): # Clean up address representation (mac address) mac = str(adv.address).replace("Address(", "").replace(")", "").strip() name = adv.complete_name or "" results[mac] = {"rssi": adv.rssi, "name": name} self.ble.stop_scan() except Exception as e: print(f"BLE scan error: {e}") if was_advertising: self._start_advertise() return results def close(self): try: self.ble.stop_advertising() except: pass self._is_advertising = False print("BLE closed.")