Log each failed Bluetooth read with its reason
collect_data printed a failure only with -v, and gave no reason for a failed connection, a failed authentication, or a read with no telemetry. A device that was out of reach showed only as missing data. Each failed read now gives one log line with the reason, at every debug level. The device object keeps the reason for a failed connection in last_error.
This commit is contained in:
@@ -495,7 +495,7 @@ def test_collection_always_disconnects(monkeypatch, connect_ok, authenticated, p
|
||||
connect=AsyncMock(return_value=connect_ok),
|
||||
wait_authenticated=AsyncMock(return_value=authenticated),
|
||||
wait_for_measurements=AsyncMock(return_value=set()),
|
||||
disconnect=AsyncMock(), _pkt_count=packets,
|
||||
disconnect=AsyncMock(), _pkt_count=packets, last_error=None,
|
||||
)
|
||||
monkeypatch.setattr("bmspy.ecoflow_bt.EcoFlowBTDevice", Mock(return_value=fake))
|
||||
result = collect_data(initialise_device(ADDRESS, SERIAL, "test-user", listen_duration=0))
|
||||
@@ -503,6 +503,53 @@ def test_collection_always_disconnects(monkeypatch, connect_ok, authenticated, p
|
||||
fake.disconnect.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("connect,authenticated,packets,reason", [
|
||||
(AsyncMock(return_value=False), False, 0, "connect failed: BleakError: device not found"),
|
||||
(AsyncMock(return_value=True), False, 0, "authentication did not complete in 30 seconds"),
|
||||
(AsyncMock(return_value=True), True, 0, "no telemetry packets received"),
|
||||
(AsyncMock(side_effect=RuntimeError("adapter gone")), False, 0, "RuntimeError: adapter gone"),
|
||||
], ids=["connect", "authentication", "no-packets", "exception"])
|
||||
def test_failed_collection_is_logged_without_a_debug_option(monkeypatch, capsys, connect, authenticated, packets, reason):
|
||||
"""A device that is out of reach must show in the server log, not only as missing data."""
|
||||
fake = Mock(
|
||||
connect=connect, wait_authenticated=AsyncMock(return_value=authenticated),
|
||||
wait_for_measurements=AsyncMock(return_value=set()),
|
||||
disconnect=AsyncMock(), _pkt_count=packets,
|
||||
last_error="connect failed: BleakError: device not found",
|
||||
)
|
||||
monkeypatch.setattr("bmspy.ecoflow_bt.EcoFlowBTDevice", Mock(return_value=fake))
|
||||
config = initialise_device(ADDRESS, SERIAL, "test-user", listen_duration=0)
|
||||
assert collect_data(config, debug=0) is False
|
||||
assert f"ecoflow_bt [{ADDRESS}]: read failed: {reason}" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_successful_collection_logs_no_failure(monkeypatch, capsys):
|
||||
from bmspy.ecoflow_snapshot import EcoFlowSnapshot
|
||||
|
||||
monkeypatch.setattr("bmspy.ecoflow_bt._last_missing", {})
|
||||
fake = Mock(
|
||||
connect=AsyncMock(return_value=True), wait_authenticated=AsyncMock(return_value=True),
|
||||
wait_for_measurements=AsyncMock(return_value=set()),
|
||||
disconnect=AsyncMock(), _pkt_count=3, last_error=None, snapshot=EcoFlowSnapshot(),
|
||||
)
|
||||
monkeypatch.setattr("bmspy.ecoflow_bt.EcoFlowBTDevice", Mock(return_value=fake))
|
||||
config = initialise_device(ADDRESS, SERIAL, "test-user", listen_duration=0)
|
||||
assert collect_data(config) is fake.snapshot
|
||||
assert capsys.readouterr().out == ""
|
||||
|
||||
|
||||
def test_connect_records_the_reason_for_a_failure(monkeypatch):
|
||||
client = Mock(connect=AsyncMock(side_effect=TimeoutError("no answer")), disconnect=AsyncMock(), is_connected=False)
|
||||
monkeypatch.setitem(sys.modules, "bleak", SimpleNamespace(BleakClient=Mock(return_value=client)))
|
||||
|
||||
async def run():
|
||||
dev = device()
|
||||
assert not await dev.connect()
|
||||
return dev.last_error
|
||||
|
||||
assert asyncio.run(run()) == "connect failed: TimeoutError: no answer"
|
||||
|
||||
|
||||
def test_wait_ends_when_the_required_heartbeats_arrive(monkeypatch):
|
||||
monkeypatch.setattr("bmspy.ecoflow_bt._COMPLETION_POLL_SECONDS", 0.01)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user