Repository navigation
bledev v1, first half: contract, fake, mpble, auto, nus - #78
Merged
Merged
Conversation
bledev/__init__ is the portable BLE contract (UUID, errors, capabilities, adapter, services and characteristics on both sides), async and aioble-shaped. bledev.fake is an in-process loopback that is stricter than a radio: over-MTU packets, over-max_len writes, unsubscribed notifications, transmit bursts and receive overruns all raise instead of truncating or dropping. bledev.nus is the Nordic UART byte stream on top of it, and bledev.auto picks a backend. tests/bledev_contract.py runs on CPython and unix MicroPython; test_bledev runs it both ways and proves five planted faults each fail it.
Both roles, on the one bluetooth.BLE(). Three places aioble loses data are closed here: a client characteristic keeps a queue instead of one notification slot (and notified() waits only on an empty queue, since aioble's wait-when-one-left logic strands the last item of a longer queue); captured writes are copied in the IRQ into a per-characteristic queue instead of a shared ten-entry deque; and payloads over mtu - 3 are refused instead of truncated. Scans default to full duty: aioble's 1 % default found a board a few inches away once in six seconds. Proven board to board on two ESP32-S3s: 16 KB each way and echoed, byte-for-byte, and a planted one-bit flip fails every phase.
tests/bledev_board holds the over-the-air checks run on two ESP32-S3s: gatt_peripheral/gatt_central (the contract over a real radio), gatt_latency (per-operation latency) and nus_server/nus_client (the byte-stream gate). Each has a PLANT switch whose run must fail. They found three things in mpble: aioble returns discovered services and characteristics in IRQ-race order (now sorted by handle), a BufferedCharacteristic loses its initial value when aioble sizes the buffer after writing it (now written again), and an operation on a dead connection raised TypeError from inside aioble (now DisconnectedError).
Declares the packaging from docs/ble.md section 3: bledev leaves the
pydevices MIP package so a board without a radio doesn't carry it, one
mip.install("bledev") brings aioble, and on PyPI bledev stays in the
pydevices wheel with bleak behind pydevices[ble]. The publisher learns
these keys in PyDevices/.github (branch bledev-packaging); until that
lands, the current publisher reads only host-only and ships bledev
inside pydevices as before.
bledev.md says what you can do (a nus stream first, then serving and connecting), where each backend runs, how to install and test, and the rules every backend keeps. bledev-internals.md is the next agents' brief: the hooks to fill in, what to translate, how mpble works around aioble, the checks to run, and the S3 measurements.
This was referenced Sep 24, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The first half of bledev v1 (workspace docs/ble.md §3 and §9): the contract, an in-process fake, the MicroPython backend over aioble,
auto, and the Nordic UART byte stream.bledev.bleak,bledev.repl,bledev.improvandbledev.webblecome from other agents, built on this.Start with docs/bledev.md. Backend authors want docs/bledev-internals.md.
Proven
tests/bledev_contract.py, 31 checks) pass on CPython, on unix MicroPython, and on an ESP32-S3 itself.tests/test_bledev.pyruns the first two, then five planted faults (drop, corrupt, reorder, truncate, overwrite), and each one fails the checks.tests/bledev_board/gatt_*) passes, and a planted missing notification fails it. Latency: 3966 operations in 120 s, max 80 ms, none over 500 ms.What this changes about aioble's behaviour. mpble closes three places where aioble loses data. The client notification queue is one slot deep. Captured writes share a ten-entry deque.
notified()strands the last item of a longer queue. mpble also scans at full duty: aioble's default scan found nothing in six seconds a few inches from the board. The details are in the internals page.Packaging.
mip-split.tomldeclares bledev its own MIP package, requiring aioble, and addspydevices[ble]for bleak. The publisher learns those keys in PyDevices/.github#52. Until that is released and this repo's pin moves pastpublishing-v11, the current publisher ignores the new keys and ships bledev insidepydevices, so merging this first is safe. I built the MIP index locally with that change and installedbledevfrom it onto an ESP32-S3; one install brought bledev and aioble, and both imported.Not in this PR:
board_config.blestill returns a rawbluetooth.BLE()in the 19 board_peripherals files.nus.serve()wraps one for you (mpble.get()), but the switch §2 describes hasn't been made.