Skip to content

Commit bc9d0aa

Browse files
authored
fix(api): Use the get_histogram retry mechanism instead of the AsyncResponseSerial one. (#19679)
1 parent 1e5b289 commit bc9d0aa

File tree

4 files changed

+66
-10
lines changed

4 files changed

+66
-10
lines changed

api/src/opentrons/drivers/asyncio/communication/serial_connection.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,10 @@ def __init__(
462462
self._async_error_ack = async_error_ack.lower()
463463

464464
async def send_command(
465-
self, command: CommandBuilder, retries: int = 0, timeout: Optional[float] = None
465+
self,
466+
command: CommandBuilder,
467+
retries: int | None = None,
468+
timeout: float | None = None,
466469
) -> str:
467470
"""
468471
Send a command and return the response.
@@ -478,12 +481,12 @@ async def send_command(
478481
"""
479482
return await self.send_data(
480483
data=command.build(),
481-
retries=retries or self._number_of_retries,
484+
retries=retries if retries is not None else self._number_of_retries,
482485
timeout=timeout,
483486
)
484487

485488
async def send_data(
486-
self, data: str, retries: int = 0, timeout: Optional[float] = None
489+
self, data: str, retries: int | None = None, timeout: float | None = None
487490
) -> str:
488491
"""
489492
Send data and return the response.
@@ -501,7 +504,8 @@ async def send_data(
501504
"timeout", timeout
502505
):
503506
return await self._send_data(
504-
data=data, retries=retries or self._number_of_retries
507+
data=data,
508+
retries=retries if retries is not None else self._number_of_retries,
505509
)
506510

507511
async def _send_data(self, data: str, retries: int = 0) -> str:
@@ -517,7 +521,6 @@ async def _send_data(self, data: str, retries: int = 0) -> str:
517521
Raises: SerialException
518522
"""
519523
data_encode = data.encode()
520-
retries = retries or self._number_of_retries
521524

522525
for retry in range(retries + 1):
523526
log.debug(f"{self._name}: Write -> {data_encode!r}")

api/src/opentrons/drivers/flex_stacker/driver.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,12 @@ async def _get_tof_histogram_frame(
461461
command = GCODE.GET_TOF_MEASUREMENT.build_command().add_element(sensor.name)
462462
if resend:
463463
command.add_element("R")
464-
resp = await self._connection.send_command(command)
464+
465+
# Note: We DONT want to auto resend the request if it fails, because the
466+
# firmware will send the next frame id instead of the current one missed.
467+
# So lets set `retries=0` so we only send the frame once and we can
468+
# use the retry mechanism of the `get_tof_histogram` method instead.
469+
resp = await self._connection.send_command(command, retries=0)
465470
return self.parse_get_tof_measurement(resp)
466471

467472
async def get_tof_histogram(self, sensor: TOFSensor) -> TOFMeasurementResult:

api/tests/opentrons/drivers/asyncio/communication/test_serial_connection.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,27 @@ async def test_send_command_with_retry(
129129
)
130130

131131

132+
async def test_send_command_with_zero_retries(
133+
mock_serial_port: AsyncMock, async_subject: AsyncResponseSerialConnection, ack: str
134+
) -> None:
135+
"""It should a command once"""
136+
mock_serial_port.read_until.side_effect = (b"", b"")
137+
138+
# Set the default number of retries to 1, we want to overide this with
139+
# the retries from the subject.send_data(data, retries=0) method call.
140+
async_subject._number_of_retries = 1
141+
142+
with pytest.raises(NoResponse):
143+
# We want this to overwrite the internal `_number_of_retries`
144+
await async_subject.send_data(data="send data", retries=0)
145+
146+
mock_serial_port.timeout_override.assert_called_once_with("timeout", None)
147+
mock_serial_port.write.assert_called_once_with(data=b"send data")
148+
mock_serial_port.read_until.assert_called_once_with(match=ack.encode())
149+
mock_serial_port.close.assert_called_once()
150+
mock_serial_port.open.assert_called_once()
151+
152+
132153
async def test_send_command_with_retry_exhausted(
133154
mock_serial_port: AsyncMock, subject: SerialKind
134155
) -> None:

api/tests/opentrons/drivers/flex_stacker/test_driver.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from binascii import Error as BinError
55
from decoy import Decoy
66
from mock import AsyncMock, MagicMock
7+
from opentrons.drivers.asyncio.communication.errors import NoResponse
78
from opentrons.drivers.asyncio.communication.serial_connection import (
89
AsyncResponseSerialConnection,
910
)
@@ -591,7 +592,7 @@ async def test_get_tof_histogram_frame(
591592
get_measurement = types.GCODE.GET_TOF_MEASUREMENT.build_command().add_element(
592593
types.TOFSensor.X.name
593594
)
594-
connection.send_command.assert_any_call(get_measurement)
595+
connection.send_command.assert_any_call(get_measurement, retries=0)
595596
connection.reset_mock()
596597

597598
# Test cancel transfer
@@ -611,7 +612,7 @@ async def test_get_tof_histogram_frame(
611612
.add_element(types.TOFSensor.X.name)
612613
.add_element("R")
613614
)
614-
connection.send_command.assert_any_call(get_measurement)
615+
connection.send_command.assert_any_call(get_measurement, retries=0)
615616
connection.reset_mock()
616617

617618
# Test invalid index response
@@ -675,7 +676,7 @@ async def test_get_tof_histogram(
675676
types.TOFSensor.X.name
676677
)
677678
connection.send_command.assert_any_call(manage_measurement)
678-
connection.send_command.assert_any_call(get_measurement)
679+
connection.send_command.assert_any_call(get_measurement, retries=0)
679680
connection.reset_mock()
680681

681682
# Test invalid frame_id
@@ -699,7 +700,33 @@ async def test_get_tof_histogram(
699700
types.TOFSensor.X.name
700701
)
701702
connection.send_command.assert_any_call(manage_measurement)
702-
connection.send_command.assert_any_call(get_measurement)
703+
connection.send_command.assert_any_call(get_measurement, retries=0)
704+
connection.reset_mock()
705+
706+
# Test resend mechanism
707+
get_measurement = (
708+
types.GCODE.GET_TOF_MEASUREMENT.build_command()
709+
.add_element(types.TOFSensor.X.name)
710+
.add_element("R")
711+
)
712+
payload = [p for p in get_histogram_payload(30)]
713+
connection.send_command.side_effect = [
714+
"M215 X:1 T:2 M:3",
715+
"M225 X K:0 C:1 L:3840",
716+
payload[0],
717+
payload[1],
718+
# We raise NoResponse on frame 3 to simulate a timeout and force a resend
719+
NoResponse("", "Timeout"),
720+
# After the timeout we expect the same packet to be resent
721+
payload[2]
722+
# Then the rest of the packets
723+
] + payload[3:]
724+
725+
response = await subject.get_tof_histogram(types.TOFSensor.X)
726+
727+
connection.send_command.assert_any_call(manage_measurement)
728+
# Assert that the M226 GCODE with `R` (resend) element was sent
729+
connection.send_command.assert_any_call(get_measurement, retries=0)
703730
connection.reset_mock()
704731

705732

0 commit comments

Comments
 (0)