Skip to content

Commit d2319c6

Browse files
committed
sdk/python: adopt remaining fixes from PR #1 (nrathaus)
PR #1 was opened against the original root-level scripts in 2023 and no longer applies cleanly to the refactored sdk/python/ tree, but two of its fixes are still real and weren't ported during the refactor. - ClientSettings: connect_retries (default 5) and connect_backoff_seconds (default 1.0). Mirrors the discovery_retries / discovery_backoff_seconds pattern already in place. - ClientSocketManager.connect(): wrap the connect call in a retry loop with the configured backoff. Bluetooth connect() is genuinely flaky in practice; the original PR did this with a hardcoded attempt > 5 guard, here it is config-driven and logs per attempt. Last error is preserved as the raised BluetoothServerError's cause. - install_dependencies.sh + sdk/python/README: install pybluez from github.com/pybluez/pybluez instead of PyPI. The PyPI distribution hasn't been updated in years and fails to build on Python 3.10+. Not adopted from PR #1: - Binary 2-byte little-endian length framing — caps payloads at 64KB and would regress the current decimal-string framing on both sides. - bare-Exception catches and sys.exit(0) on failure — the current module raises typed BluetoothClientError / BluetoothServerError, which is strictly better for callers and tests. Smoke-tested the retry path with a stubbed bluetooth module (3 attempts with 0.01s backoff completed in ~20ms; each attempt logged; final exception carried the BluetoothError cause).
1 parent b8ec87b commit d2319c6

4 files changed

Lines changed: 56 additions & 21 deletions

File tree

sdk/python/README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ python3 run_client.py # sender
2121

2222
Run from within `sdk/python` or adjust your `PYTHONPATH` if launching elsewhere.
2323

24+
> **PyBluez install note.** PyPI's `pybluez` package hasn't been updated
25+
> in years and no longer builds on Python 3.10+. Install from the GitHub
26+
> source instead (the helper script does this for you):
27+
>
28+
> ```bash
29+
> sudo python3 -m pip install git+https://github.com/pybluez/pybluez.git
30+
> ```
31+
2432
## Tests
2533
2634
```bash
@@ -32,7 +40,7 @@ pytest tests/
3240

3341
- Update `bluetooth_service/config.py` (`ServerSettings`) for server behavior.
3442
- Update `bluetooth_service/client_config.py` (`ClientSettings`) for discovery,
35-
retries, and payload source.
43+
retries (both `discovery_retries` and `connect_retries`), and payload source.
3644
- Swap serializers/sinks/sources by injecting your own implementations when
3745
constructing `BluetoothServer` / `BluetoothClient`.
3846

sdk/python/bluetooth_service/client_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class ClientSettings:
1616
buffer_size: int = 1024
1717
discovery_retries: int = 3
1818
discovery_backoff_seconds: float = 0.5
19+
# Bluetooth connect() is flaky in practice — retry the connect step
20+
# with a short backoff before giving up. (Adapted from PR #1.)
21+
connect_retries: int = 5
22+
connect_backoff_seconds: float = 1.0
1923
resend_empty_message: str = "EmptyBufferResend"
2024
resend_corrupt_message: str = "CorruptedBufferResend"
2125
delimiter_missing_message: str = "DelimiterMissingBufferResend"

sdk/python/bluetooth_service/client_socket.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,45 @@ def connect(self) -> None:
4747
if not self._service_info:
4848
raise BluetoothServerError("Service discovery must run before connect")
4949

50-
try:
51-
self._socket = BluetoothSocket(RFCOMM)
52-
if self._settings.connect_timeout is not None:
53-
self._socket.settimeout(self._settings.connect_timeout)
54-
endpoint = (self._service_info["host"], self._service_info["port"])
55-
logger.info("Connecting to %s:%s", *endpoint)
56-
self._socket.connect(endpoint)
57-
logger.info("Connected to %s", self._service_info["name"])
58-
except (BluetoothError, OSError) as exc:
59-
if self._socket is not None:
60-
try:
61-
self._socket.close()
62-
except BluetoothError as close_exc:
63-
logger.warning("Failed to close socket after connect error: %s", close_exc)
64-
finally:
65-
self._socket = None
66-
raise BluetoothServerError("Unable to connect to Bluetooth service", cause=exc)
67-
finally:
68-
if self._socket is not None:
50+
endpoint = (self._service_info["host"], self._service_info["port"])
51+
retries = max(1, self._settings.connect_retries)
52+
last_exc: Optional[BaseException] = None
53+
54+
for attempt in range(1, retries + 1):
55+
try:
56+
self._socket = BluetoothSocket(RFCOMM)
57+
if self._settings.connect_timeout is not None:
58+
self._socket.settimeout(self._settings.connect_timeout)
59+
logger.info(
60+
"Connecting to %s:%s (attempt %s/%s)",
61+
*endpoint,
62+
attempt,
63+
retries,
64+
)
65+
self._socket.connect(endpoint)
66+
logger.info("Connected to %s", self._service_info["name"])
6967
self._socket.settimeout(None)
68+
return
69+
except (BluetoothError, OSError) as exc:
70+
last_exc = exc
71+
logger.warning(
72+
"Connect attempt %s/%s to %s:%s failed: %s",
73+
attempt,
74+
retries,
75+
*endpoint,
76+
exc,
77+
)
78+
if self._socket is not None:
79+
try:
80+
self._socket.close()
81+
except BluetoothError as close_exc:
82+
logger.warning("Failed to close socket after connect error: %s", close_exc)
83+
finally:
84+
self._socket = None
85+
if attempt < retries:
86+
time.sleep(self._settings.connect_backoff_seconds)
87+
88+
raise BluetoothServerError("Unable to connect to Bluetooth service", cause=last_exc)
7089

7190
def send(self, payload: bytes) -> None:
7291
if not self._socket:

sdk/python/scripts/install_dependencies.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ apt-get install -y \
2020
blueman
2121

2222
python3 -m pip install --upgrade pip
23-
python3 -m pip install pybluez pytest
23+
# pybluez on PyPI hasn't been updated in years and fails to build on
24+
# modern Python (3.10+). Install from the GitHub source instead — this
25+
# is the workaround maintained by the pybluez project itself. (PR #1.)
26+
python3 -m pip install pytest
27+
python3 -m pip install git+https://github.com/pybluez/pybluez.git
2428

2529
echo "Dependencies installed. You can now run run_server.py and run_client.py"
2630

0 commit comments

Comments
 (0)