Add BLE retry/backoff for connection timeouts and bump to 0.1.10
Some checks failed
Deploy to GitHub Pages / build (push) Has been cancelled
Deploy to GitHub Pages / deploy (push) Has been cancelled

This commit is contained in:
paul2212
2026-03-07 14:29:44 +01:00
parent 54ba6795c0
commit 8513afe831
6 changed files with 85 additions and 31 deletions

View File

@@ -4,6 +4,11 @@ All notable changes to this project are documented in this file.
The format is based on Keep a Changelog and this project uses Semantic Versioning. The format is based on Keep a Changelog and this project uses Semantic Versioning.
## [0.1.10] - 2026-03-07
### Changed
- Added automatic BLE reconnect retry with linear backoff for transient timeout errors (including `br-connection-timeout`) before returning a failure.
## [0.1.9] - 2026-03-07 ## [0.1.9] - 2026-03-07
### Added ### Added

View File

@@ -53,6 +53,8 @@ DELAY_CHUNK_GAP = 0.02 # inter-chunk pacing for BLE throughput
DELAY_RASTER_SETTLE = 0.50 # wait for printhead after raster transfer DELAY_RASTER_SETTLE = 0.50 # wait for printhead after raster transfer
DELAY_AFTER_FEED = 0.30 # wait after form feed before stop command DELAY_AFTER_FEED = 0.30 # wait after form feed before stop command
DELAY_NOTIFY_EXTRA = 0.05 # extra wait for trailing BLE notification fragments DELAY_NOTIFY_EXTRA = 0.05 # extra wait for trailing BLE notification fragments
BLE_CONNECT_RETRIES = 3 # retry transient BLE connect failures
BLE_CONNECT_BACKOFF = 0.7 # base backoff in seconds (linear: n * base)
# --- Exceptions --- # --- Exceptions ---
@@ -405,17 +407,38 @@ async def connect(
raise PrinterError(f"Classic Bluetooth connection failed for '{address}'.") raise PrinterError(f"Classic Bluetooth connection failed for '{address}'.")
else: else:
addr = address or await find_printer() addr = address or await find_printer()
try: def _is_retryable_ble_error(exc: Exception) -> bool:
async with BleakClient(addr) as client: msg = str(exc).lower()
pc = PrinterClient(client) return any(token in msg for token in ("timeout", "timed out", "br-connection-timeout"))
await pc.start()
yield pc last_exc: Exception | None = None
except BleakDBusError as exc: for attempt in range(1, BLE_CONNECT_RETRIES + 1):
if "br-connection-not-supported" in str(exc).lower(): try:
raise PrinterError( async with BleakClient(addr) as client:
"BLE connection failed (br-connection-not-supported). " pc = PrinterClient(client)
"Try Classic Bluetooth with classic=true and channel=1." await pc.start()
) from exc yield pc
raise PrinterError(f"BLE connection failed: {exc}") from exc return
except BleakError as exc: except BleakDBusError as exc:
raise PrinterError(f"BLE error: {exc}") from exc msg = str(exc).lower()
if "br-connection-not-supported" in msg:
raise PrinterError(
"BLE connection failed (br-connection-not-supported). "
"Try Classic Bluetooth with classic=true and channel=1."
) from exc
last_exc = exc
if _is_retryable_ble_error(exc) and attempt < BLE_CONNECT_RETRIES:
await asyncio.sleep(BLE_CONNECT_BACKOFF * attempt)
continue
raise PrinterError(f"BLE connection failed: {exc}") from exc
except BleakError as exc:
last_exc = exc
if _is_retryable_ble_error(exc) and attempt < BLE_CONNECT_RETRIES:
await asyncio.sleep(BLE_CONNECT_BACKOFF * attempt)
continue
raise PrinterError(f"BLE error: {exc}") from exc
if last_exc is not None:
raise PrinterError(
f"BLE connection failed after {BLE_CONNECT_RETRIES} attempts: {last_exc}"
) from last_exc
raise PrinterError("BLE connection failed for unknown reason.")

View File

@@ -1,5 +1,9 @@
# Changelog # Changelog
## 0.1.10
- Added automatic BLE reconnect retry with backoff for transient timeout errors (`br-connection-timeout`).
## 0.1.9 ## 0.1.9
- Added add-on local changelog file so Home Assistant can display release notes. - Added add-on local changelog file so Home Assistant can display release notes.
@@ -29,4 +33,3 @@
## 0.1.3 ## 0.1.3
- Added ingress/webui metadata updates. - Added ingress/webui metadata updates.

View File

@@ -1,5 +1,5 @@
name: "Fichero Printer" name: "Fichero Printer"
version: "0.1.9" version: "0.1.10"
slug: "fichero_printer" slug: "fichero_printer"
description: "REST API for the Fichero D11s (AiYin) thermal label printer over Bluetooth" description: "REST API for the Fichero D11s (AiYin) thermal label printer over Bluetooth"
url: "https://git.leuschner.dev/Tobias/Fichero" url: "https://git.leuschner.dev/Tobias/Fichero"

View File

@@ -53,6 +53,8 @@ DELAY_CHUNK_GAP = 0.02 # inter-chunk pacing for BLE throughput
DELAY_RASTER_SETTLE = 0.50 # wait for printhead after raster transfer DELAY_RASTER_SETTLE = 0.50 # wait for printhead after raster transfer
DELAY_AFTER_FEED = 0.30 # wait after form feed before stop command DELAY_AFTER_FEED = 0.30 # wait after form feed before stop command
DELAY_NOTIFY_EXTRA = 0.05 # extra wait for trailing BLE notification fragments DELAY_NOTIFY_EXTRA = 0.05 # extra wait for trailing BLE notification fragments
BLE_CONNECT_RETRIES = 3 # retry transient BLE connect failures
BLE_CONNECT_BACKOFF = 0.7 # base backoff in seconds (linear: n * base)
# --- Exceptions --- # --- Exceptions ---
@@ -405,17 +407,38 @@ async def connect(
raise PrinterError(f"Classic Bluetooth connection failed for '{address}'.") raise PrinterError(f"Classic Bluetooth connection failed for '{address}'.")
else: else:
addr = address or await find_printer() addr = address or await find_printer()
try: def _is_retryable_ble_error(exc: Exception) -> bool:
async with BleakClient(addr) as client: msg = str(exc).lower()
pc = PrinterClient(client) return any(token in msg for token in ("timeout", "timed out", "br-connection-timeout"))
await pc.start()
yield pc last_exc: Exception | None = None
except BleakDBusError as exc: for attempt in range(1, BLE_CONNECT_RETRIES + 1):
if "br-connection-not-supported" in str(exc).lower(): try:
raise PrinterError( async with BleakClient(addr) as client:
"BLE connection failed (br-connection-not-supported). " pc = PrinterClient(client)
"Try Classic Bluetooth with classic=true and channel=1." await pc.start()
) from exc yield pc
raise PrinterError(f"BLE connection failed: {exc}") from exc return
except BleakError as exc: except BleakDBusError as exc:
raise PrinterError(f"BLE error: {exc}") from exc msg = str(exc).lower()
if "br-connection-not-supported" in msg:
raise PrinterError(
"BLE connection failed (br-connection-not-supported). "
"Try Classic Bluetooth with classic=true and channel=1."
) from exc
last_exc = exc
if _is_retryable_ble_error(exc) and attempt < BLE_CONNECT_RETRIES:
await asyncio.sleep(BLE_CONNECT_BACKOFF * attempt)
continue
raise PrinterError(f"BLE connection failed: {exc}") from exc
except BleakError as exc:
last_exc = exc
if _is_retryable_ble_error(exc) and attempt < BLE_CONNECT_RETRIES:
await asyncio.sleep(BLE_CONNECT_BACKOFF * attempt)
continue
raise PrinterError(f"BLE error: {exc}") from exc
if last_exc is not None:
raise PrinterError(
f"BLE connection failed after {BLE_CONNECT_RETRIES} attempts: {last_exc}"
) from last_exc
raise PrinterError("BLE connection failed for unknown reason.")

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "fichero-printer" name = "fichero-printer"
version = "0.1.9" version = "0.1.10"
description = "Fichero D11s thermal label printer - BLE CLI tool" description = "Fichero D11s thermal label printer - BLE CLI tool"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [