From 41e42937d59b58b5ad2e3bfc5277d41c31022c66 Mon Sep 17 00:00:00 2001 From: Dinuka Jayalath Date: Wed, 24 Jun 2026 13:18:15 +0200 Subject: [PATCH] fix(liconic): wait for the z-lift before scanning a barcode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scan_barcode sent ST 1910 to move the lift to the read position and then fired the scan immediately, without waiting for the move to finish. The beam latched on while the lift was still travelling and decoded whichever plate it passed en route — requesting a scan of level 5 with a plate at level 10 returned level 10's barcode. Await _wait_ready() after ST 1910, matching every other motion method in the backend (open_door, take_in_plate, move_position_to_position, and the sibling read_barcode_inline). Fixes #1115 Co-Authored-By: Claude Opus 4.8 (1M context) --- pylabrobot/storage/liconic/liconic_backend.py | 1 + .../storage/liconic/liconic_backend_tests.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/pylabrobot/storage/liconic/liconic_backend.py b/pylabrobot/storage/liconic/liconic_backend.py index f9b770f727f..ad6859af3bc 100644 --- a/pylabrobot/storage/liconic/liconic_backend.py +++ b/pylabrobot/storage/liconic/liconic_backend.py @@ -573,6 +573,7 @@ async def scan_barcode(self, site: PlateHolder) -> Barcode: await self._send_command(f"WR DM25 {pos_num}") # plate await self._send_command(f"WR DM5 {n}") # plate position in carousel await self._send_command("ST 1910") # move shovel to barcode reading position + await self._wait_ready() # block until the lift reaches the read position barcode = await self.barcode_scanner.scan() logger.info(f"Scanned barcode: {barcode.data}") diff --git a/pylabrobot/storage/liconic/liconic_backend_tests.py b/pylabrobot/storage/liconic/liconic_backend_tests.py index b3ed09dccfa..590ab66af98 100644 --- a/pylabrobot/storage/liconic/liconic_backend_tests.py +++ b/pylabrobot/storage/liconic/liconic_backend_tests.py @@ -371,6 +371,24 @@ async def test_initialize(self): self.backend._wait_ready.assert_awaited() +class TestScanBarcode(unittest.IsolatedAsyncioTestCase): + """scan_barcode must wait for the z-lift to reach the read position (ST 1910) + before triggering the scan, otherwise the beam fires while the lift is still + travelling and reads a plate it passes en route, not the target.""" + + def setUp(self): + self.backend = ExperimentalLiconicBackend(model=LiconicType.STX44_IC, port="/dev/null") + self.backend._racks = [liconic_rack_17mm_22("rack1")] + self.backend._send_command = AsyncMock(return_value="OK") + self.backend._wait_ready = AsyncMock() + self.backend.barcode_scanner = AsyncMock() + + async def test_scan_barcode_waits_for_lift(self): + await self.backend.scan_barcode(self.backend._racks[0].sites[0]) + self.backend._send_command.assert_any_call("ST 1910") + self.backend._wait_ready.assert_awaited() + + class TestSerialization(unittest.TestCase): def test_serialize_roundtrip(self): backend = ExperimentalLiconicBackend(model=LiconicType.STX44_IC, port="/dev/ttyUSB0")