Howdyyyyy
I’m trying to make this work for our Synergy HT and it doesn’t quite do the trick for me
When I set up a Synergy HT backend with the changes for open and close, the open and close do work for me
However when I run (mostly) the same code as above:
import asyncio
from pylabrobot.plate_reading.agilent import SynergyHTBackend
from pylabrobot.resources import Cor_96_wellplate_360ul_Fb
# Setup
backend = SynergyHTBackend()
await backend.setup()
print(f"Firmware: {backend.version}")
print(f"Temperature: {await backend.get_current_temperature():.1f}°C")
# Create plate and get wells
plate = Cor_96_wellplate_360ul_Fb("test_plate")
wells = [plate.get_item(i) for i in range(plate.num_items)]
# Read OD600
result = await backend.read_absorbance(plate, wells, wavelength=600)
print(result)
It times out trying to run the read command:
2026-02-17 21:38:01,066 - pylabrobot.plate_reading.agilent.biotek_backend - INFO - SynergyHTBackend setting up
2026-02-17 21:38:01,164 - pylabrobot.io.ftdi - INFO - Successfully opened FTDI device: FTNE6HGA
Firmware: 2.24
Temperature: 22.4°C
---------------------------------------------------------------------------
TimeoutError Traceback (most recent call last)
Cell In[1], line 20
17 await backend.close()
18 await backend.open()
---> 20 result = await backend.read_absorbance(plate, wells, wavelength=600)
22 print(result)
File ~/oppy_scripts/pylabrobot/pylabrobot/plate_reading/agilent/biotek_backend.py:371, in BioTekPlateReaderBackend.read_absorbance(self, plate, wells, wavelength)
368 cmd = cmd + checksum + "\x03"
369 await self.send_command("D", cmd)
--> 371 resp = await self.send_command("O")
372 assert resp == b"\x060000\x03"
374 # read data
File ~/oppy_scripts/pylabrobot/pylabrobot/plate_reading/agilent/biotek_backend.py:190, in BioTekPlateReaderBackend.send_command(self, command, parameter, wait_for_response, timeout)
188 response: Optional[bytes] = None
189 if wait_for_response or parameter is not None:
--> 190 response = await self._read_until(
191 b"\x06" if parameter is not None else b"\x03", timeout=timeout
192 )
194 if parameter is not None:
195 await self.io.write(parameter.encode())
File ~/oppy_scripts/pylabrobot/pylabrobot/plate_reading/agilent/biotek_synergyht_backend.py:65, in SynergyHTBackend._read_until(self, terminator, timeout, chunk_size)
61 if time.time() > deadline:
62 logger.debug(
63 f"{self.__class__.__name__} _read_until timed out; partial buffer (hex): %s", buf.hex()
64 )
---> 65 raise TimeoutError(
66 f"{self.__class__.__name__} _read_until timed out waiting for {terminator!r}; partial={buf.hex()}"
67 )
69 try:
70 data = await self.io.read(chunk_size)
TimeoutError: SynergyHTBackend _read_until timed out waiting for b'\x03'; partial=15
I can see the firmware versions are different so maybe that’s involved somehow but I mostly feel a bit stuck on how to approach fixing this
so any suggestions would be MUCH appreciated
My SynergyHT Backend for reference:
import asyncio
import logging
import time
from typing import Optional
from pylibftdi import FtdiError
from pylabrobot.plate_reading.agilent.biotek_backend import BioTekPlateReaderBackend
from pylabrobot.resources import Plate
logger = logging.getLogger(__name__)
class SynergyHTBackend(BioTekPlateReaderBackend):
"""Backend for Agilent BioTek Synergy HT plate readers."""
@property
def supports_heating(self):
return True
@property
def supports_cooling(self):
return False
@property
def focal_height_range(self):
return (4.5, 10.68)
async def open(self, slow: bool = False) -> None:
"""Open the plate reader door / eject plate.
Note: slow parameter is ignored on Synergy HT (not supported).
"""
# Synergy HT doesn't support slow mode command (&), so skip it
return await self.send_command("J")
async def close(self, plate: Optional[Plate] = None, slow: bool = False) -> None:
"""Close the plate reader door / load plate.
Note: slow parameter is ignored on Synergy HT (not supported).
"""
# Synergy HT doesn't support slow mode command (&), so skip it
self._plate = None
if plate is not None:
await self.set_plate(plate)
return await self.send_command("A")
async def _read_until(
self, terminator: bytes, timeout: Optional[float] = None, chunk_size: int = 512
) -> bytes:
if timeout is None:
timeout = self.timeout
deadline = time.time() + timeout
buf = bytearray()
retries = 0
max_retries = 3
while True:
if time.time() > deadline:
logger.debug(
f"{self.__class__.__name__} _read_until timed out; partial buffer (hex): %s", buf.hex()
)
raise TimeoutError(
f"{self.__class__.__name__} _read_until timed out waiting for {terminator!r}; partial={buf.hex()}"
)
try:
data = await self.io.read(chunk_size)
if len(data) == 0:
await asyncio.sleep(0.02)
continue
buf.extend(data)
if terminator in buf:
idx = buf.index(terminator) + len(terminator)
full = bytes(buf[:idx])
logger.debug(
f"{self.__class__.__name__} _read_until received %d bytes (hex prefix): %s",
len(full),
full[:200].hex(),
)
return full
except FtdiError as e:
retries += 1
logger.warning(
f"{self.__class__.__name__} transient FtdiError while reading: %s — retrying", e
)
if retries >= max_retries:
logger.warning(
f"{self.__class__.__name__} too many FtdiError retries ({max_retries}) — stopping", e
)
raise
await asyncio.sleep(0.05)
continue
except Exception:
raise