Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions pylabrobot/formulatrix/mantis/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def __init__(
size_z: float = SIZE_Z,
declared_configuration_json: Optional[str] = None,
oem_config_directory: Optional[str] = None,
ftdi_library: Optional[str] = None,
simulation: bool = False,
):
"""
Expand All @@ -56,10 +57,13 @@ def __init__(
``RECORDING_MANTIS_V4``.
oem_config_directory: the unit's vendor ``Data/Device/Configs`` folder; where it and the
declared file both hold a unit value, they must agree.
ftdi_library: "libftdi" or "d2xx"; None picks D2XX on Windows when FTDI's D2XX library
loads, else libftdi.
simulation: build a simulated Mantis, with no link.

Raises:
ValueError: if ``simulation`` is given with ``device_id``, ``io`` or ``rfid_io``.
ValueError: if ``simulation`` is given with ``device_id``, ``io``, ``rfid_io`` or
``ftdi_library``.
"""
super().__init__(
name=name,
Expand All @@ -71,8 +75,10 @@ def __init__(
)
self.driver: MantisDriver
if simulation:
if device_id is not None or io is not None or rfid_io is not None:
raise ValueError("a simulated Mantis has no link: no device_id, io or rfid_io")
if any(link is not None for link in (device_id, io, rfid_io, ftdi_library)):
raise ValueError(
"a simulated Mantis has no link: no device_id, io, rfid_io or ftdi_library"
)
self.driver = MantisSimulationDriver(
declared_configuration_json=declared_configuration_json or RECORDING_MANTIS_V4,
name=name,
Expand All @@ -86,6 +92,7 @@ def __init__(
name=name,
declared_configuration_json=declared_configuration_json,
oem_config_directory=oem_config_directory,
ftdi_library=ftdi_library,
)
self.mesh = {
"path": os.path.join(os.path.dirname(__file__), "resource_model", MANIFEST["base"]),
Expand Down Expand Up @@ -146,6 +153,7 @@ def Mantis(
size_z: float = SIZE_Z,
declared_configuration_json: Optional[str] = None,
oem_config_directory: Optional[str] = None,
ftdi_library: Optional[str] = None,
simulation: bool = False,
) -> MantisDevice:
"""A Formulatrix Mantis, v3 or v4.
Expand All @@ -162,10 +170,13 @@ def Mantis(
disagrees on what is fitted. Simulated, the configuration; None uses ``RECORDING_MANTIS_V4``.
oem_config_directory: the unit's vendor ``Data/Device/Configs`` folder; where it and the
declared file both hold a unit value, they must agree.
ftdi_library: "libftdi" or "d2xx"; None picks D2XX on Windows when FTDI's D2XX library loads,
else libftdi.
simulation: build a simulated Mantis, with no link.

Raises:
ValueError: if ``simulation`` is given with ``device_id``, ``io`` or ``rfid_io``.
ValueError: if ``simulation`` is given with ``device_id``, ``io``, ``rfid_io`` or
``ftdi_library``.
"""
return MantisDevice(
name=name,
Expand All @@ -177,5 +188,6 @@ def Mantis(
size_z=size_z,
declared_configuration_json=declared_configuration_json,
oem_config_directory=oem_config_directory,
ftdi_library=ftdi_library,
simulation=simulation,
)
4 changes: 1 addition & 3 deletions pylabrobot/formulatrix/mantis/device_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ def make_device(self) -> MantisDevice:

def test_every_part_has_its_model_file(self):
device = self.make_device()
drawn = [r for r in [device, *device.get_all_children()] if hasattr(r, "mesh")]
self.assertIn(device.left_chip_rack, drawn)
for resource in drawn:
for resource in (device, device.left_chip_rack, device.right_chip_rack):
with self.subTest(resource=resource.name):
self.assertTrue(os.path.isfile(os.path.join(RESOURCE_MODEL, f"{resource.model}.glb")))

Expand Down
19 changes: 15 additions & 4 deletions pylabrobot/formulatrix/mantis/driver/master.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,28 @@
class MantisDriver:
"""Talks to a Mantis: one command in flight, events logged as they come."""

def _build_io(self, *, device_id: Optional[str]) -> FTDI:
def _build_io(self, *, device_id: Optional[str], ftdi_library: Optional[str]) -> FTDI:
"""Build the link to the Mantis ``device_id`` names, or to the first one found."""
return FTDI(
human_readable_device_name="Formulatrix Mantis",
device_id=device_id,
vid=0x0403,
pid=0x6010,
interface_select=2, # interface B carries the command link
ftdi_library=ftdi_library,
)

def _build_rfid_io(self, *, device_id: Optional[str]) -> Optional[FTDI]:
def _build_rfid_io(
self, *, device_id: Optional[str], ftdi_library: Optional[str]
) -> Optional[FTDI]:
"""Build the link to the RFID reader, interface A of the same FTDI bridge."""
return FTDI(
human_readable_device_name="Formulatrix Mantis RFID reader",
device_id=device_id,
vid=0x0403,
pid=0x6010,
interface_select=RFID_FTDI_INTERFACE,
ftdi_library=ftdi_library,
)

def __init__(
Expand All @@ -84,6 +88,7 @@ def __init__(
name: str = "Mantis",
declared_configuration_json: Optional[str] = None,
oem_config_directory: Optional[str] = None,
ftdi_library: Optional[str] = None,
):
"""
Args:
Expand All @@ -95,12 +100,16 @@ def __init__(
declared_configuration_json: a file ``save_configuration`` wrote. Setup refuses a device
that disagrees with its ``device`` block on what is fitted (``FITTED_FIELDS``).
oem_config_directory: the unit's vendor ``Data/Device/Configs`` folder, read at every setup.
ftdi_library: "libftdi" or "d2xx", for both links; None picks D2XX on Windows when FTDI's
D2XX library loads, else libftdi. Ignored for a link given as ``io`` or ``rfid_io``.

The unit's own values (``UNIT_BLOCKS``) come from ``oem_config_directory`` and the declared
file; where both have a block, they must agree. A block neither has comes from the
generation's recording, with a warning: another unit's values, less accurate on this one.
"""
self.io: FTDI = io if io is not None else self._build_io(device_id=device_id)
self.io: FTDI = (
io if io is not None else self._build_io(device_id=device_id, ftdi_library=ftdi_library)
)
self.name = name
self.declared_configuration_json = declared_configuration_json
self.oem_config_directory = oem_config_directory
Expand All @@ -115,7 +124,9 @@ def __init__(
self._event_waiters: List[Tuple[int, int, "asyncio.Future[bytes]"]] = []
self._reader: Optional["asyncio.Task[None]"] = None
self.rfid_io: Optional[FTDI] = (
rfid_io if rfid_io is not None or io is not None else self._build_rfid_io(device_id=device_id)
rfid_io
if rfid_io is not None or io is not None
else self._build_rfid_io(device_id=device_id, ftdi_library=ftdi_library)
)
self.plate_clamp: Optional[PlateClamp] = None
self.rfid: Optional[Rfid] = None
Expand Down
6 changes: 4 additions & 2 deletions pylabrobot/formulatrix/mantis/driver/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,13 @@ def __init__(
oem_config_directory=oem_config_directory,
)

def _build_io(self, *, device_id: Optional[str]) -> FTDI:
def _build_io(self, *, device_id: Optional[str], ftdi_library: Optional[str]) -> FTDI:
"""Build no link."""
return None # type: ignore[return-value] # no link: nothing reaches a wire

def _build_rfid_io(self, *, device_id: Optional[str]) -> Optional[FTDI]:
def _build_rfid_io(
self, *, device_id: Optional[str], ftdi_library: Optional[str]
) -> Optional[FTDI]:
"""Build no link."""
return None

Expand Down
2 changes: 2 additions & 0 deletions pylabrobot/io/executor_setup_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ async def _cancel_while_worker_is_running(
async def test_cancelled_ftdi_setup_closes_device_before_executor_shutdown(self) -> None:
io = ftdi_module.FTDI.__new__(ftdi_module.FTDI)
io.human_readable_device_name = "mock FTDI"
io.ftdi_library = "libftdi"
io._device_id = "mock"
io._dev = None
io._d2xx_dev = None
io._executor = None
device = _ClosableDevice()
started = threading.Event()
Expand Down
Loading
Loading