Skip to content

Size write packets to the negotiated ATT MTU - #26

Open
Nailik wants to merge 3 commits into
mainfrom
fix/mtu-sized-packets
Open

Nailik wants to merge 3 commits into
mainfrom
fix/mtu-sized-packets

Conversation

@Nailik

@Nailik Nailik commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Rebased on main (0.0.14). Carries the test version 0.0.15-mtu-sized-packages for a prerelease. Set the real version before merging.

Summary

Requests were always split into 200 byte packets. When a connection negotiates a smaller ATT MTU, each of those packets is sent as a long write, which the device can reject with ATT error 0x0E (UNLIKELY_ERROR). This sizes packets to fit a single write at each connection's negotiated MTU.

  • New _packet_size(client) reads max_write_without_response_size of the Blanco characteristic, capped at the 200 byte protocol target. For a write with response that is the same limit (ATT MTU − 3). On BlueZ bleak takes it from the MTU property BlueZ publishes (5.62+); on an ESPHome proxy from the MTU the proxy reports. No private bleak API is used.
  • Pairing sets the session protocol's packet size on every connection, including the reconnect after a stuck-GATT recovery. validate_pin without a protocol (config flow, random-MAC scan) does the same.
  • When the characteristic is not resolved, 200 bytes are kept, as before.
  • When the MTU is not published, 200 bytes are kept too. bleak then reports the 20 bytes of the minimum MTU (MIN_WRITE_SIZE). Besides BlueZ before 5.62, that is the case for every bonded device whose GATT objects BlueZ cached on an earlier connection: BlueZ only signals the MTU on the connection that created them (att_exchange in src/gatt-client.c), and the reporter log in Fix BLE connection leaks and pair during setup #24 shows no MTU property at all. Shrinking to 20 bytes there would change the framing on setups that work today, without knowing the real MTU.
  • README: new "Requests Are Slow" troubleshooting section.
  • manifest.json: version 0.0.15-mtu-sized-packages for the test prerelease (valid SemVer for Home Assistant, sorts between 0.0.14 and 0.0.15).

Behaviour change to verify

Packets only shrink when a connection publishes an MTU above the 23 byte minimum, for example an ESPHome proxy or a local adapter on a fresh (not cached) connection. A request then takes more, smaller writes. Each is a plain write, and the busy-write retry from #25 covers them on BlueZ. Where the MTU is unknown, the 200 byte long writes from before stay. No log so far shows the device rejecting those with 0x0E, so this mainly matters once one does.

Test plan

  • pytest tests on the rebased branch (HA 2026.6, Python 3.14): 391 passed, 68 snapshots passed
  • New tests: cap at 200, unpublished MTU keeps 200, missing characteristic, services not resolved, validate_pin writes MTU-sized packets, pairing sizes the session protocol
  • On hardware, local BlueZ adapter: check the "Using N byte packets" or "MTU not published" debug line, setup and a settings change
  • On hardware, ESPHome Bluetooth proxy: same checks
  • Prerelease 0.0.15-mtu-sized-packages tested by reporters
  • Version set back to the next release before merging

🤖 Generated with Claude Code

Base automatically changed from fix/stuck-gatt-recovery to main October 17, 2026 21:12
Nailik and others added 3 commits October 17, 2026 23:24
A request was always split into 200 byte packets. When the connection
negotiated a smaller MTU, each packet became a long write, which the
device can reject with ATT error 0x0e. Size the packets to a single write
at the MTU of each connection instead, read from the characteristic's
max_write_without_response_size, and keep 200 bytes when the
characteristic is not resolved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bleak reports the 20 bytes of the minimum MTU when BlueZ does not publish
the MTU. Besides BlueZ before 5.62, that is the case for a bonded device
whose GATT objects are cached from an earlier connection, since BlueZ only
signals the MTU on the connection that created them. Every bonded unit on a
local adapter then got 20 byte packets regardless of the real MTU.

Treat 20 bytes as unknown and keep the 200 byte packets that worked so far.
Only a published MTU above the minimum shrinks the packets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nailik
Nailik force-pushed the fix/mtu-sized-packets branch from 7f5cc40 to 8f26300 Compare October 17, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant