Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(api): Allow simulator to load multiple of a module #14628

Merged
merged 31 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e3b748c
change structure of loadedModules
TamarZanzouri Feb 29, 2024
8c9d1fe
change id to sn
TamarZanzouri Mar 1, 2024
26d057e
change id to sn
TamarZanzouri Mar 1, 2024
756cf51
SimulatingModuleAtPort just started
TamarZanzouri Mar 2, 2024
9b6ebd4
continue - WIP
TamarZanzouri Mar 2, 2024
880db71
union of simulating port
TamarZanzouri Mar 4, 2024
79e7b9a
update simulating modules
TamarZanzouri Mar 4, 2024
64dc4f6
started tests
TamarZanzouri Mar 4, 2024
0c52312
fixed failing test module control
TamarZanzouri Mar 11, 2024
58e3b0b
fix up structure - WIP
TamarZanzouri Mar 11, 2024
3be706b
fixed simulator_setup.py structure and test
TamarZanzouri Mar 11, 2024
01aee67
Update api/src/opentrons/hardware_control/module_control.py
TamarZanzouri Mar 13, 2024
49aaff7
Update api/src/opentrons/hardware_control/modules/heater_shaker.py
TamarZanzouri Mar 13, 2024
a5e359a
Update api/src/opentrons/hardware_control/modules/utils.py
TamarZanzouri Mar 13, 2024
c5220d4
fixed typo, sim_serial_number, and looping through calls
TamarZanzouri Mar 13, 2024
b106f5f
changed attached_modules to accepts sn
TamarZanzouri Mar 13, 2024
f882ae7
fixed failing tests
TamarZanzouri Mar 19, 2024
63de4f2
fixed ot3 full path to set sn
TamarZanzouri Mar 19, 2024
0b95d7c
fixed ot2 full sn assigments
TamarZanzouri Mar 19, 2024
56f7f82
Update api/src/opentrons/hardware_control/simulator_setup.py
TamarZanzouri Mar 19, 2024
e7f468f
Update robot-server/simulators/test-flex.json
TamarZanzouri Mar 19, 2024
901d162
wip pr feedback
TamarZanzouri Mar 19, 2024
a339f6c
wip pr feedback
TamarZanzouri Mar 19, 2024
e5d313e
fixed failing test with port name change and sim_serial_number in mod…
TamarZanzouri Mar 20, 2024
ffc4ca1
fixed all failing tests
TamarZanzouri Mar 20, 2024
ec71a42
check sn when making calls on module
TamarZanzouri Mar 21, 2024
fdcb2ad
formatting json files, ot-2 config and simulator for ot2
TamarZanzouri Mar 21, 2024
358f5c4
Update api/src/opentrons/hardware_control/module_control.py
TamarZanzouri Mar 21, 2024
5aae58e
Update api/src/opentrons/hardware_control/simulator_setup.py
TamarZanzouri Mar 21, 2024
bbbf174
fixed missing hs in tests
TamarZanzouri Mar 21, 2024
880c86f
changed items to calls in config
TamarZanzouri Mar 21, 2024
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
7 changes: 4 additions & 3 deletions api/src/opentrons/drivers/heater_shaker/simulator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict
from typing import Dict, Optional
from opentrons.util.async_helpers import ensure_yield
from opentrons.drivers.heater_shaker.abstract import AbstractHeaterShakerDriver
from opentrons.drivers.types import Temperature, RPM, HeaterShakerLabwareLatchStatus
Expand All @@ -7,12 +7,13 @@
class SimulatingDriver(AbstractHeaterShakerDriver):
DEFAULT_TEMP = 23

def __init__(self) -> None:
def __init__(self, serial_number: Optional[str] = None) -> None:
self._labware_latch_state = HeaterShakerLabwareLatchStatus.IDLE_UNKNOWN
self._current_temperature = self.DEFAULT_TEMP
self._temperature = Temperature(current=self.DEFAULT_TEMP, target=None)
self._rpm = RPM(current=0, target=None)
self._homing_status = True
self._serial_number = serial_number

@ensure_yield
async def connect(self) -> None:
Expand Down Expand Up @@ -83,7 +84,7 @@ async def deactivate(self) -> None:
@ensure_yield
async def get_device_info(self) -> Dict[str, str]:
return {
"serial": "dummySerialHS",
"serial": self._serial_number if self._serial_number else "dummySerialHS",
"model": "dummyModelHS",
"version": "dummyVersionHS",
}
Expand Down
7 changes: 5 additions & 2 deletions api/src/opentrons/drivers/mag_deck/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@


class SimulatingDriver(AbstractMagDeckDriver):
def __init__(self, sim_model: Optional[str] = None) -> None:
def __init__(
self, sim_model: Optional[str] = None, serial_number: Optional[str] = None
) -> None:
self._height = 0.0
self._model = MAG_DECK_MODELS[sim_model] if sim_model else "mag_deck_v1.1"
self._serial_number = serial_number

@ensure_yield
async def probe_plate(self) -> None:
Expand All @@ -30,7 +33,7 @@ async def move(self, location: float) -> None:
@ensure_yield
async def get_device_info(self) -> Dict[str, str]:
return {
"serial": "dummySerialMD",
"serial": self._serial_number if self._serial_number else "dummySerialMD",
"model": self._model,
"version": "dummyVersionMD",
}
Expand Down
11 changes: 7 additions & 4 deletions api/src/opentrons/drivers/rpi_drivers/interfaces.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from typing import List
from typing import List, Union
from typing_extensions import Protocol

from opentrons.hardware_control.modules.types import ModuleAtPort
from opentrons.hardware_control.modules.types import (
ModuleAtPort,
SimulatingModuleAtPort,
)


class USBDriverInterface(Protocol):
def match_virtual_ports(
self,
virtual_port: List[ModuleAtPort],
) -> List[ModuleAtPort]:
virtual_port: Union[List[ModuleAtPort], List[SimulatingModuleAtPort]],
) -> Union[List[ModuleAtPort], List[SimulatingModuleAtPort]]:
...
13 changes: 8 additions & 5 deletions api/src/opentrons/drivers/rpi_drivers/usb.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import subprocess
import re
import os
from typing import List
from typing import List, Union

from opentrons.hardware_control.modules.types import ModuleAtPort
from opentrons.hardware_control.modules.types import (
ModuleAtPort,
SimulatingModuleAtPort,
)
from opentrons.hardware_control.types import BoardRevision

from .interfaces import USBDriverInterface
Expand Down Expand Up @@ -79,8 +82,8 @@ def _read_usb_bus(self) -> List[USBPort]:

def match_virtual_ports(
self,
virtual_ports: List[ModuleAtPort],
) -> List[ModuleAtPort]:
virtual_ports: Union[List[ModuleAtPort], List[SimulatingModuleAtPort]],
) -> Union[List[ModuleAtPort], List[SimulatingModuleAtPort]]:
"""
Match Virtual Ports

Expand All @@ -89,7 +92,7 @@ def match_virtual_ports(
the physical usb port information.
The virtual port path looks something like:
dev/ot_module_[MODULE NAME]
:param virtual_ports: A list of ModuleAtPort objects
:param virtual_ports: A list of ModuleAtPort or SimulatingModuleAtPort objects
that hold the name and virtual port of each module
connected to the robot.

Expand Down
12 changes: 8 additions & 4 deletions api/src/opentrons/drivers/rpi_drivers/usb_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
A class to convert info from the usb bus into a
more readable format.
"""
from typing import List
from typing import List, Union

from opentrons.hardware_control.modules.types import ModuleAtPort
from opentrons.hardware_control.modules.types import (
ModuleAtPort,
SimulatingModuleAtPort,
)

from .interfaces import USBDriverInterface


class USBBusSimulator(USBDriverInterface):
def match_virtual_ports(
self, virtual_port: List[ModuleAtPort]
) -> List[ModuleAtPort]:
self,
virtual_port: Union[List[ModuleAtPort], List[SimulatingModuleAtPort]],
) -> Union[List[ModuleAtPort], List[SimulatingModuleAtPort]]:
return virtual_port
7 changes: 5 additions & 2 deletions api/src/opentrons/drivers/temp_deck/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@


class SimulatingDriver(AbstractTempDeckDriver):
def __init__(self, sim_model: Optional[str] = None):
def __init__(
self, sim_model: Optional[str] = None, serial_number: Optional[str] = None
):
self._temp = Temperature(target=None, current=0)
self._port: Optional[str] = None
self._model = TEMP_DECK_MODELS[sim_model] if sim_model else "temp_deck_v1.1"
self._serial_number = serial_number

@ensure_yield
async def set_temperature(self, celsius: float) -> None:
Expand Down Expand Up @@ -48,7 +51,7 @@ async def enter_programming_mode(self) -> None:
@ensure_yield
async def get_device_info(self) -> Dict[str, str]:
return {
"serial": "dummySerialTD",
"serial": self._serial_number if self._serial_number else "dummySerialTD",
"model": self._model,
"version": "dummyVersionTD",
}
7 changes: 5 additions & 2 deletions api/src/opentrons/drivers/thermocycler/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
class SimulatingDriver(AbstractThermocyclerDriver):
DEFAULT_TEMP = 23

def __init__(self, model: Optional[str] = None) -> None:
def __init__(
self, model: Optional[str] = None, serial_number: Optional[str] = None
) -> None:
self._ramp_rate: Optional[float] = None
self._lid_status = ThermocyclerLidStatus.OPEN
self._lid_temperature = Temperature(current=self.DEFAULT_TEMP, target=None)
self._plate_temperature = PlateTemperature(
current=self.DEFAULT_TEMP, target=None, hold=None
)
self._model = model if model else "thermocyclerModuleV1"
self._serial_number = serial_number

def model(self) -> str:
return self._model
Expand Down Expand Up @@ -103,7 +106,7 @@ async def deactivate_all(self) -> None:
@ensure_yield
async def get_device_info(self) -> Dict[str, str]:
return {
"serial": "dummySerialTC",
"serial": self._serial_number if self._serial_number else "dummySerialTC",
"model": "dummyModelTC",
"version": "dummyVersionTC",
}
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/hardware_control/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ async def build_hardware_simulator(
attached_instruments: Optional[
Dict[top_types.Mount, Dict[str, Optional[str]]]
] = None,
attached_modules: Optional[List[str]] = None,
attached_modules: Optional[Dict[str, List[str]]] = None,
config: Optional[Union[RobotConfig, OT3Config]] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
strict_attached_instruments: bool = True,
Expand All @@ -271,7 +271,7 @@ async def build_hardware_simulator(
attached_instruments = {}

if None is attached_modules:
attached_modules = []
attached_modules = {}

checked_loop = use_or_initialize_loop(loop)
if isinstance(config, RobotConfig):
Expand Down
18 changes: 12 additions & 6 deletions api/src/opentrons/hardware_control/backends/ot3simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class OT3Simulator(FlexBackend):
async def build(
cls,
attached_instruments: Dict[OT3Mount, Dict[str, Optional[str]]],
attached_modules: List[str],
attached_modules: Dict[str, List[str]],
config: OT3Config,
loop: asyncio.AbstractEventLoop,
strict_attached_instruments: bool = True,
Expand All @@ -126,7 +126,7 @@ async def build(
def __init__(
self,
attached_instruments: Dict[OT3Mount, Dict[str, Optional[str]]],
attached_modules: List[str],
attached_modules: Dict[str, List[str]],
config: OT3Config,
loop: asyncio.AbstractEventLoop,
strict_attached_instruments: bool = True,
Expand Down Expand Up @@ -588,10 +588,16 @@ async def increase_z_l_hold_current(self) -> AsyncIterator[None]:

@ensure_yield
async def watch(self, loop: asyncio.AbstractEventLoop) -> None:
new_mods_at_ports = [
modules.ModuleAtPort(port=f"/dev/ot_module_sim_{mod}{str(idx)}", name=mod)
for idx, mod in enumerate(self._stubbed_attached_modules)
]
new_mods_at_ports = []
for mod, serials in self._stubbed_attached_modules.items():
for serial in serials:
new_mods_at_ports.append(
modules.SimulatingModuleAtPort(
port=f"/dev/ot_module_sim_{mod}{str(serial)}",
name=mod,
serial_number=serial,
)
)
await self.module_controls.register_modules(new_mods_at_ports=new_mods_at_ports)

@property
Expand Down
18 changes: 12 additions & 6 deletions api/src/opentrons/hardware_control/backends/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Simulator:
async def build(
cls,
attached_instruments: Dict[types.Mount, Dict[str, Optional[str]]],
attached_modules: List[str],
attached_modules: Dict[str, List[str]],
config: RobotConfig,
loop: asyncio.AbstractEventLoop,
strict_attached_instruments: bool = True,
Expand Down Expand Up @@ -105,7 +105,7 @@ async def build(
def __init__(
self,
attached_instruments: Dict[types.Mount, Dict[str, Optional[str]]],
attached_modules: List[str],
attached_modules: Dict[str, List[str]],
config: RobotConfig,
loop: asyncio.AbstractEventLoop,
gpio_chardev: GPIODriverLike,
Expand Down Expand Up @@ -332,10 +332,16 @@ def set_active_current(self, axis_currents: Dict[Axis, float]) -> None:

@ensure_yield
async def watch(self) -> None:
new_mods_at_ports = [
modules.ModuleAtPort(port=f"/dev/ot_module_sim_{mod}{str(idx)}", name=mod)
for idx, mod in enumerate(self._stubbed_attached_modules)
]
new_mods_at_ports = []
for mod, serials in self._stubbed_attached_modules.items():
for serial in serials:
new_mods_at_ports.append(
modules.SimulatingModuleAtPort(
port=f"/dev/ot_module_sim_{mod}{str(serial)}",
name=mod,
serial_number=serial,
)
)
await self.module_controls.register_modules(new_mods_at_ports=new_mods_at_ports)

@contextmanager
Expand Down
16 changes: 14 additions & 2 deletions api/src/opentrons/hardware_control/module_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
save_module_calibration_offset,
)
from opentrons.hardware_control.modules.types import ModuleType
from opentrons.hardware_control.modules import SimulatingModuleAtPort

from opentrons.types import Point
from .types import AionotifyEvent, BoardRevision, OT3Mount
from . import modules
Expand Down Expand Up @@ -84,6 +86,7 @@ async def build_module(
usb_port: types.USBPort,
type: modules.ModuleType,
sim_model: Optional[str] = None,
sim_serial_number: Optional[str] = None,
) -> modules.AbstractModule:
return await modules.build(
port=port,
Expand All @@ -93,10 +96,14 @@ async def build_module(
hw_control_loop=self._api.loop,
execution_manager=self._api._execution_manager,
sim_model=sim_model,
sim_serial_number=sim_serial_number,
)

async def unregister_modules(
self, mods_at_ports: List[modules.ModuleAtPort]
self,
mods_at_ports: Union[
List[modules.ModuleAtPort], List[modules.SimulatingModuleAtPort]
],
) -> None:
"""
De-register Modules.
Expand Down Expand Up @@ -126,7 +133,9 @@ async def unregister_modules(

async def register_modules(
self,
new_mods_at_ports: Optional[List[modules.ModuleAtPort]] = None,
new_mods_at_ports: Optional[
Union[List[modules.ModuleAtPort], List[modules.SimulatingModuleAtPort]]
] = None,
removed_mods_at_ports: Optional[List[modules.ModuleAtPort]] = None,
) -> None:
"""
Expand All @@ -152,6 +161,9 @@ async def register_modules(
port=mod.port,
usb_port=mod.usb_port,
type=modules.MODULE_TYPE_BY_NAME[mod.name],
sim_serial_number=mod.serial_number
if isinstance(mod, SimulatingModuleAtPort)
else None,
)
self._available_modules.append(new_instance)
log.info(
Expand Down
2 changes: 2 additions & 0 deletions api/src/opentrons/hardware_control/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
BundledFirmware,
UpdateError,
ModuleAtPort,
SimulatingModuleAtPort,
ModuleType,
ModuleModel,
TemperatureStatus,
Expand All @@ -33,6 +34,7 @@
"BundledFirmware",
"UpdateError",
"ModuleAtPort",
"SimulatingModuleAtPort",
"HeaterShaker",
"ModuleType",
"ModuleModel",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ async def build(
poll_interval_seconds: Optional[float] = None,
simulating: bool = False,
sim_model: Optional[str] = None,
sim_serial_number: Optional[str] = None,
) -> "HeaterShaker":
"""
Build a HeaterShaker
Expand All @@ -71,7 +72,7 @@ async def build(
driver = await HeaterShakerDriver.create(port=port, loop=hw_control_loop)
poll_interval_seconds = poll_interval_seconds or POLL_PERIOD
else:
driver = SimulatingDriver()
driver = SimulatingDriver(serial_number=sim_serial_number)
poll_interval_seconds = poll_interval_seconds or SIMULATING_POLL_PERIOD

reader = HeaterShakerReader(driver=driver)
Expand Down
5 changes: 4 additions & 1 deletion api/src/opentrons/hardware_control/modules/magdeck.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ async def build(
poll_interval_seconds: Optional[float] = None,
simulating: bool = False,
sim_model: Optional[str] = None,
sim_serial_number: Optional[str] = None,
) -> "MagDeck":
"""Factory function."""
driver: AbstractMagDeckDriver
if not simulating:
driver = await MagDeckDriver.create(port=port, loop=hw_control_loop)
else:
driver = SimulatingDriver(sim_model=sim_model)
driver = SimulatingDriver(
sim_model=sim_model, serial_number=sim_serial_number
)

mod = cls(
port=port,
Expand Down
1 change: 1 addition & 0 deletions api/src/opentrons/hardware_control/modules/mod_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async def build(
poll_interval_seconds: Optional[float] = None,
simulating: bool = False,
sim_model: Optional[str] = None,
sim_serial_number: Optional[str] = None,
) -> "AbstractModule":
"""Modules should always be created using this factory.

Expand Down
Loading
Loading