Add DotPad buffered-receive and skipped BLE tests (#19942)
N/A. This is a follow-up to #19122 (DotPad BLE support) and #19838 (hwIo.ble module). As discussed in those PRs, the work is being split into three smaller PRs. This is "PR B" — the buffered-receive implementation and skipped BLE tests in preparation for the final BLE driver integration in PR C. There is no standalone issue tracking this change.
Summary of the issue:
The DotPad driver's _onReceive method reads remaining packet bytes synchronously via self._dev.read() and raises RuntimeError on any malformed byte, which can kill the display connection. This approach also cannot handle BLE's packet-based delivery model where entire packets arrive in a single callback. The buffered-receive replaces this with a more robust approach as a prerequisite for BLE support.
Description of user facing changes:
DotPad braille displays now handle malformed serial data more gracefully. Instead of crashing the display connection on a single corrupted byte, the driver resynchronizes and continues operating. In practice, since DotPad devices currently only connect over USB serial (which is a stable connection), users are unlikely to notice this change. The real benefit comes when BLE support lands in PR C, where packet-based delivery makes the buffered approach essential.
Description of developer facing changes:
Replaced _onReceive(self, header1: bytes) with a buffered-receive approach _onReceive(self, data: bytes) that accepts variable-length input (1 byte from Serial, full packets from BLE).
Extracted _processPacket(self, packetBody: bytes) from the old inline parsing for clarity.
Added DP_MAX_PACKET_SIZE protocol constant to defs.py.
The buffered-receive handles: resynchronization on bad sync bytes, buffer overflow protection, partial-packet buffering, and multiple packets in a single call.
Description of development approach:
Ported the buffered-receive logic from the dotpad-ble branch (#19122), keeping only the transport-agnostic parts. No BLE-specific code is included — that lands in PR C.
The approach replaces synchronous self._dev.read() calls within the callback with a _receiveBuffer that accumulates bytes across callbacks. Complete packets are extracted from the buffer as they become available.