Skip to content

P4 as BLE central loses notifications below MicroPython when Python is busy #87

Closed

Description

@bdbarnett

When the P4 panel is the BLE central, it sometimes loses notifications, and nothing reports it. The lost packets never reach MicroPython's IRQ handler, so the loss happens below Python: in NimBLE on the P4, the ESP-Hosted transport, or the C6.

How it shows up. In the bledev.hid board-to-board gate (#83), an S3 (the LCD-7) plays the keyboard and the P4 is the host. At a 7.5-15 ms connection interval it lost reports in 6 of 6 runs, and at the default interval in 1 of 4. The loss starts in the same place every time: the 26th to 28th report, where ! is typed. Sometimes two reports go missing, sometimes most of the rest of the script, and then reports arrive again. Pacing the device at 10 ms per key didn't help. A user IRQ handler (ble.irq) counting _IRQ_GATTC_NOTIFY saw exactly the reports that were decoded (44 of 70 sent), so they're missing before Python ever runs.

The reverse doesn't lose anything. With the P4 as the keyboard and an S3 as the host, the S3 got every report in every run. S3 to S3 is clean too.

A smaller reproduction (a characteristic notifying numbered packets back to back, 7.5-15 ms interval):

Receiver Work per packet 80 in a burst 200 in a burst 200, 2 ms apart
P4 none 80 200 200
P4 1.7 ms (time.sleep_us) 76 (first lost: #57) 169 (first lost: #42) 200
S3 (LCD-7) 1.7 ms 80 200 200
S3 (LCD-7) 5 ms 80 200 200

The esp32 port runs BLE events synchronously (MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS), so NimBLE's host task waits while Python is busy. On the S3 the controller then holds the link back and nothing is lost. The P4's path through ESP-Hosted appears to drop packets instead. This is probably the same thing as #79 (the P4 losing writes while it notifies), seen from the other role. What the HID gate does at report 26 to trigger it at the default interval isn't known yet.

What to try next: the ESP-Hosted and NimBLE buffer counts in the P4 hosted config (ACL buffers, flow control between host and controller), and whether HCI flow control is on over the hosted link at all.

Activity

  1. bdbarnett commented on Sep 24, 2026

    @bdbarnett
    ContributorAuthor

    A correction to the count above: at the default interval the P4 host passed 2 of 5 runs, not 1 of 4. The two diagnostic runs (raw reports, then the IRQ count) were at the default interval, and both lost reports. The fast-interval count stays at 6 of 6.

    Pairing doesn't change it. A bonded reconnect with the P4 as host lost 14 events in the same place.

    The scripts are tests/bledev_board/burst_source.py and burst_sink.py on bledev-hid (#83).

  2. bdbarnett commented on Sep 25, 2026

    @bdbarnett
    ContributorAuthor

    Found and fixed: micropython-pydevices#12, one sdkconfig line stacked on #10.

    The packets are dropped in ESP-Hosted's receive path on the P4 (vhci_drv.c, hci_rx_handler). It throws away an ACL packet when NimBLE's ACL pool is empty, and that happens whenever the NimBLE host task waits on Python. On an S3, IDF's own transport waits for a buffer instead, so the controller holds the link back. Every lost packet shows up on the P4's UART as E vhci_drv: Rx: alloc_acl_from_ll failed, one for one, but only from the friendly REPL. The raw REPL (every mpftp run) mutes ESP-IDF's log, which is why nobody saw it.

    CONFIG_BT_NIMBLE_HS_FLOW_CTRL=y makes the C6's controller respect the host's buffer count. I used the burst repro from this issue: the T-Embed notifies 940 packets, and the P4 receives them with 1.7 ms of Python work each.

    Two things are left open. First, 4 of 20 connects from the P4 as a central timed out at the default interval (1 of 10 before). Second, a Python reader that falls 128 packets behind still gets bledev's loud "queue overran" error, which is by design. I didn't rerun the HID gate itself, only this repro.

  3. bdbarnett commented on Sep 25, 2026

    @bdbarnett
    ContributorAuthor

    With micropython-pydevices#12 on the P4, the P4 no longer loses keystrokes as a HID host. I reran #83's full board-to-board gate with the LCD-7 as the keyboard and the P4 as the host. Each run typed 30 characters with Shift, two chords, two media keys and five gamepad reports, and the P4's events had to match all 112 exactly.

    Interval Before #12 With #12
    Default lost keystrokes in 3 of 5 10 of 10 pass, 112/112 each
    7.5-15 ms lost keystrokes in 6 of 6 10 of 10 pass, 112/112 each (11 attempts; one never connected)
    Planted lost Shift fails fails at event 0, as it must

    No run logged an alloc_acl_from_ll failed. The runs were at the friendly REPL, where ESP-IDF's log reaches the UART. The planted run is what proves the gate. Nobody has checked that the drop counter catches a drop, because the #10-only image wasn't reflashed.

    The round trip, key press to the host's LED write, had a median of 69-85 ms at the default interval and 29-37 ms at 7.5-15 ms, close to the S3-to-S3 numbers.

    The P4 was hard-reset before every run (micropython-pydevices#13). Its boot.py joins Wi-Fi, so Wi-Fi was associated throughout. The P4 ran the #12 kitchen sink + speaker-usermod image, which I checked by hashing the first 4 KB of the running partition against the build (fe5c70c3…). bledev was the bledev-pairing branch as .mpy. The rig is tests/bledev_board/p4_hid_gate.py in #89.

  4. bdbarnett commented on Sep 25, 2026

    @bdbarnett
    ContributorAuthor

    Fixed by micropython-pydevices#12, merged 2026-09-25. On the flow-control image, the full HID gate with an S3 typing into the P4 passed 10 of 10 runs at the default interval and 10 of 10 at 7.5–15 ms, 112/112 events each. Before the fix, 3 of 5 and 6 of 6 runs lost keystrokes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions