@@ -356,6 +356,37 @@ async def test_blerpc_call_corrupted_frame_length_valid_json(
356356 assert result == {"name" : "Test Device" }
357357
358358
359+ @pytest .mark .asyncio
360+ @pytest .mark .usefixtures ("mock_establish_connection" )
361+ async def test_blerpc_call_first_chunk_empty_retry (
362+ ble_device : BLEDevice , mock_ble_client : MagicMock
363+ ) -> None :
364+ """Test BLE RPC call with multiple empty chunks, then data on retry."""
365+ ble_rpc = BleRPC (ble_device )
366+
367+ mock_ble_client .write_gatt_char = AsyncMock ()
368+ mock_ble_client .read_gatt_char = AsyncMock ()
369+
370+ response = {"id" : 1 , "src" : "test" , "result" : {"name" : "Test Device" }}
371+ response_bytes = json .dumps (response ).encode ()
372+
373+ # Multiple empty reads (device not ready), then data arrives
374+ mock_ble_client .read_gatt_char .side_effect = [
375+ len (response_bytes ).to_bytes (4 , "big" ), # Frame length
376+ b"" , # First chunk empty - device not ready
377+ b"" , # Second chunk empty - still not ready
378+ b"" , # Third chunk empty - still not ready
379+ response_bytes , # Fourth chunk has data after retries
380+ b"" , # End of data
381+ ]
382+
383+ await ble_rpc .connect ()
384+
385+ # Should succeed after multiple retries
386+ result = await ble_rpc .call ("Shelly.GetDeviceInfo" )
387+ assert result == {"name" : "Test Device" }
388+
389+
359390@pytest .mark .asyncio
360391@pytest .mark .usefixtures ("mock_establish_connection" )
361392async def test_blerpc_call_invalid_json (
@@ -597,6 +628,35 @@ async def test_blerpc_call_zero_frame_length_then_success(
597628 assert result == {"name" : "Test Device" }
598629
599630
631+ @pytest .mark .asyncio
632+ @pytest .mark .usefixtures ("mock_establish_connection" )
633+ async def test_blerpc_call_first_chunk_empty_timeout (
634+ ble_device : BLEDevice , mock_ble_client : MagicMock
635+ ) -> None :
636+ """Test BLE RPC call times out after max empty chunk retries."""
637+ ble_rpc = BleRPC (ble_device )
638+
639+ mock_ble_client .write_gatt_char = AsyncMock ()
640+ mock_ble_client .read_gatt_char = AsyncMock ()
641+
642+ # Frame length indicates data, but reads always return empty
643+ mock_ble_client .read_gatt_char .side_effect = [
644+ (100 ).to_bytes (4 , "big" ), # Frame length = 100 bytes
645+ * [b"" for _ in range (60 )], # More than RX_POLL_MAX_ATTEMPTS empty reads
646+ ]
647+
648+ await ble_rpc .connect ()
649+
650+ # Patch RX_POLL_INTERVAL to speed up test
651+ with (
652+ patch ("aioshelly.rpc_device.blerpc.RX_POLL_INTERVAL" , 0.001 ),
653+ pytest .raises (
654+ DeviceConnectionError , match = "Incomplete data received: expected 100 bytes"
655+ ),
656+ ):
657+ await ble_rpc .call ("Shelly.GetDeviceInfo" )
658+
659+
600660@pytest .mark .asyncio
601661@pytest .mark .usefixtures ("mock_establish_connection" )
602662async def test_blerpc_call_invalid_frame_length_data (
0 commit comments