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

chore(api): allow specifying module models to sim #15104

Merged
merged 5 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion api/src/opentrons/hardware_control/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@

mod_log = logging.getLogger(__name__)

AttachedModuleSpec = Dict[str, List[Union[str, Tuple[str, str]]]]


class API(
ExecutionManagerProvider,
Expand Down Expand Up @@ -255,7 +257,7 @@ async def build_hardware_simulator(
attached_instruments: Optional[
Dict[top_types.Mount, Dict[str, Optional[str]]]
] = None,
attached_modules: Optional[Dict[str, List[str]]] = None,
attached_modules: Optional[Dict[str, List[Tuple[str, Optional[str]]]]] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the proper data structure for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, no. i guess this is the point where the dataclasses should get persisted through.

config: Optional[Union[RobotConfig, OT3Config]] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
strict_attached_instruments: bool = True,
Expand Down
9 changes: 5 additions & 4 deletions api/src/opentrons/hardware_control/backends/ot3simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class OT3Simulator(FlexBackend):
async def build(
cls,
attached_instruments: Dict[OT3Mount, Dict[str, Optional[str]]],
attached_modules: Dict[str, List[str]],
attached_modules: Dict[str, List[Tuple[str, Optional[str]]]],
config: OT3Config,
loop: asyncio.AbstractEventLoop,
strict_attached_instruments: bool = True,
Expand All @@ -130,7 +130,7 @@ async def build(
def __init__(
self,
attached_instruments: Dict[OT3Mount, Dict[str, Optional[str]]],
attached_modules: Dict[str, List[str]],
attached_modules: Dict[str, List[Tuple[str, Optional[str]]]],
config: OT3Config,
loop: asyncio.AbstractEventLoop,
strict_attached_instruments: bool = True,
Expand Down Expand Up @@ -605,13 +605,14 @@ async def increase_z_l_hold_current(self) -> AsyncIterator[None]:
@ensure_yield
async def watch(self, loop: asyncio.AbstractEventLoop) -> None:
new_mods_at_ports = []
for mod, serials in self._stubbed_attached_modules.items():
for serial in serials:
for mod, detail_tuple in self._stubbed_attached_modules.items():
for (serial, maybe_model) in detail_tuple:
new_mods_at_ports.append(
modules.SimulatingModuleAtPort(
port=f"/dev/ot_module_sim_{mod}{str(serial)}",
name=mod,
serial_number=serial,
model=maybe_model,
)
)
await self.module_controls.register_modules(new_mods_at_ports=new_mods_at_ports)
Expand Down
14 changes: 8 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: Dict[str, List[str]],
attached_modules: Dict[str, List[Tuple[str, Optional[str]]]],
config: RobotConfig,
loop: asyncio.AbstractEventLoop,
strict_attached_instruments: bool = True,
Expand All @@ -71,8 +71,9 @@ async def build(
This dict should map mounts to either
empty dicts or to dicts containing
'model' and 'id' keys.
:param attached_modules: A list of module model names (e.g.
`'tempdeck'` or `'magdeck'`) representing
:param attached_modules: A map of module type names (e.g.
`'tempdeck'` or `'magdeck'`) to lists of (serial, model)
tuples representing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not tuples anymore

modules the simulator should assume are
attached. Like `attached_instruments`, used
to make the simulator match the setup of the
Expand Down Expand Up @@ -105,7 +106,7 @@ async def build(
def __init__(
self,
attached_instruments: Dict[types.Mount, Dict[str, Optional[str]]],
attached_modules: Dict[str, List[str]],
attached_modules: Dict[str, List[Tuple[str, Optional[str]]]],
config: RobotConfig,
loop: asyncio.AbstractEventLoop,
gpio_chardev: GPIODriverLike,
Expand Down Expand Up @@ -333,13 +334,14 @@ def set_active_current(self, axis_currents: Dict[Axis, float]) -> None:
@ensure_yield
async def watch(self) -> None:
new_mods_at_ports = []
for mod, serials in self._stubbed_attached_modules.items():
for serial in serials:
for mod, detail_pairs in self._stubbed_attached_modules.items():
for (serial, maybe_model) in detail_pairs:
new_mods_at_ports.append(
modules.SimulatingModuleAtPort(
port=f"/dev/ot_module_sim_{mod}{str(serial)}",
name=mod,
serial_number=serial,
model=maybe_model,
)
)
await self.module_controls.register_modules(new_mods_at_ports=new_mods_at_ports)
Expand Down
11 changes: 8 additions & 3 deletions api/src/opentrons/hardware_control/module_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,14 @@ 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,
sim_serial_number=(
mod.serial_number
if isinstance(mod, SimulatingModuleAtPort)
else None
),
sim_model=(
mod.model 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/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Tuple,
Awaitable,
Union,
Optional,
cast,
TYPE_CHECKING,
)
Expand Down Expand Up @@ -129,6 +130,7 @@ class ModuleAtPort:
@dataclass(kw_only=True)
class SimulatingModuleAtPort(ModuleAtPort):
serial_number: str
model: Optional[str]


class BundledFirmware(NamedTuple):
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ async def build_hardware_simulator(
Dict[OT3Mount, Dict[str, Optional[str]]],
Dict[top_types.Mount, Dict[str, Optional[str]]],
] = None,
attached_modules: Optional[Dict[str, List[str]]] = None,
attached_modules: Optional[Dict[str, List[Tuple[str, Optional[str]]]]] = None,
config: Union[RobotConfig, OT3Config, None] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
strict_attached_instruments: bool = True,
Expand Down
11 changes: 7 additions & 4 deletions api/src/opentrons/hardware_control/simulator_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class ModuleCall:
@dataclass(frozen=True)
class ModuleItem:
serial_number: str
model: str
calls: List[ModuleCall] = field(default_factory=list)


Expand Down Expand Up @@ -59,7 +60,7 @@ async def _simulator_for_setup(
return await API.build_hardware_simulator(
attached_instruments=setup.attached_instruments,
attached_modules={
k: [m.serial_number for m in v]
k: [(m.serial_number, m.model) for m in v]
for k, v in setup.attached_modules.items()
},
config=setup.config,
Expand All @@ -73,7 +74,7 @@ async def _simulator_for_setup(
return await OT3API.build_hardware_simulator(
attached_instruments=setup.attached_instruments,
attached_modules={
k: [m.serial_number for m in v]
k: [(m.serial_number, m.model) for m in v]
for k, v in setup.attached_modules.items()
},
config=setup.config,
Expand Down Expand Up @@ -114,7 +115,7 @@ def _thread_manager_for_setup(
API.build_hardware_simulator,
attached_instruments=setup.attached_instruments,
attached_modules={
k: [m.serial_number for m in v]
k: [(m.serial_number, m.model) for m in v]
for k, v in setup.attached_modules.items()
},
config=setup.config,
Expand All @@ -128,7 +129,7 @@ def _thread_manager_for_setup(
OT3API.build_hardware_simulator,
attached_instruments=setup.attached_instruments,
attached_modules={
k: [m.serial_number for m in v]
k: [(m.serial_number, m.model) for m in v]
for k, v in setup.attached_modules.items()
},
config=setup.config,
Expand Down Expand Up @@ -215,6 +216,7 @@ def _prepare_for_simulator_setup(key: str, value: Dict[str, Any]) -> Any:
attached_modules.setdefault(key, []).append(
ModuleItem(
serial_number=obj["serial_number"],
model=obj["model"],
calls=[ModuleCall(**data) for data in obj["calls"]],
)
)
Expand All @@ -236,6 +238,7 @@ def _prepare_for_ot3_simulator_setup(key: str, value: Dict[str, Any]) -> Any:
attached_modules.setdefault(key, []).append(
ModuleItem(
serial_number=obj["serial_number"],
model=obj["model"],
calls=[ModuleCall(**data) for data in obj["calls"]],
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ def subject(
(
[
SimulatingModuleAtPort(
port="/dev/foo", name="bar", serial_number="test-123"
port="/dev/foo",
name="bar",
serial_number="test-123",
model="mymodel",
)
]
),
Expand Down Expand Up @@ -101,6 +104,7 @@ async def test_register_modules(
usb_port=USBPort(name="baz", port_number=0),
type=ModuleType.TEMPERATURE,
sim_serial_number=None,
sim_model=None,
)
).then_return(module)

Expand Down Expand Up @@ -151,6 +155,7 @@ async def test_register_modules_sort(
port=matchers.Anything(),
type=matchers.Anything(),
sim_serial_number=None,
sim_model=None,
)
).then_return(mod)

Expand Down
18 changes: 9 additions & 9 deletions api/tests/opentrons/hardware_control/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ async def test_get_modules_simulating():
import opentrons.hardware_control as hardware_control

mods = {
"tempdeck": ["111"],
"magdeck": ["222"],
"thermocycler": ["333"],
"heatershaker": ["444"],
"tempdeck": [("111", "temperatureModuleV1")],
"magdeck": [("222", "magneticModuleV2")],
"thermocycler": [("333", "thermocyclerModuleV1")],
"heatershaker": [("444", "heaterShakerModuleV1")],
}
api = await hardware_control.API.build_hardware_simulator(attached_modules=mods)
await asyncio.sleep(0.05)
Expand All @@ -47,7 +47,7 @@ async def test_get_modules_simulating():
async def test_module_caching():
import opentrons.hardware_control as hardware_control

mod_names = {"tempdeck": ["111"]}
mod_names = {"tempdeck": [("111", "temperatureModuleV1")]}
api = await hardware_control.API.build_hardware_simulator(
attached_modules=mod_names
)
Expand Down Expand Up @@ -349,10 +349,10 @@ async def test_get_bundled_fw(monkeypatch, tmpdir):
from opentrons.hardware_control import API

mods = {
"tempdeck": ["111"],
"magdeck": ["222"],
"thermocycler": ["333"],
"heatershaker": ["444"],
"tempdeck": [("111", "temperatureModuleV1")],
"magdeck": [("222", "magneticModuleV2")],
"thermocycler": [("333", "thermocyclerModuleV1")],
"heatershaker": [("444", "heaterShakerModuleV1")],
}

api = await API.build_hardware_simulator(attached_modules=mods)
Expand Down
13 changes: 13 additions & 0 deletions api/tests/opentrons/hardware_control/test_simulator_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ async def test_with_magdeck(setup_klass: Type[simulator_setup.SimulatorSetup]) -
attached_modules={
"magdeck": [
simulator_setup.ModuleItem(
model="magneticModuleV1",
serial_number="123",
calls=[simulator_setup.ModuleCall("engage", kwargs={"height": 3})],
),
simulator_setup.ModuleItem(
model="magneticModuleV2",
serial_number="1234",
calls=[simulator_setup.ModuleCall("engage", kwargs={"height": 5})],
),
Expand All @@ -71,11 +73,13 @@ async def test_with_magdeck(setup_klass: Type[simulator_setup.SimulatorSetup]) -
simulator = await simulator_setup.create_simulator(setup)

assert isinstance(simulator.attached_modules[0], MagDeck)
assert simulator.attached_modules[0].model() == "magneticModuleV1"
assert simulator.attached_modules[0].live_data == {
"data": {"engaged": True, "height": 3},
"status": "engaged",
}
assert simulator.attached_modules[0].device_info["serial"] == "123"
assert simulator.attached_modules[1].model() == "magneticModuleV2"
assert simulator.attached_modules[1].live_data == {
"data": {"engaged": True, "height": 5},
"status": "engaged",
Expand All @@ -91,6 +95,7 @@ async def test_with_thermocycler(
attached_modules={
"thermocycler": [
simulator_setup.ModuleItem(
model="thermocyclerModuleV2",
serial_number="123",
calls=[
simulator_setup.ModuleCall(
Expand All @@ -110,6 +115,7 @@ async def test_with_thermocycler(
simulator = await simulator_setup.create_simulator(setup)

assert isinstance(simulator.attached_modules[0], Thermocycler)
assert simulator.attached_modules[0].model() == "thermocyclerModuleV2"
assert simulator.attached_modules[0].live_data == {
"data": {
"currentCycleIndex": None,
Expand All @@ -136,6 +142,7 @@ async def test_with_tempdeck(setup_klass: Type[simulator_setup.SimulatorSetup])
attached_modules={
"tempdeck": [
simulator_setup.ModuleItem(
model="temperatureModuleV2",
serial_number="123",
calls=[
simulator_setup.ModuleCall(
Expand All @@ -152,6 +159,7 @@ async def test_with_tempdeck(setup_klass: Type[simulator_setup.SimulatorSetup])
simulator = await simulator_setup.create_simulator(setup)

assert isinstance(simulator.attached_modules[0], TempDeck)
assert simulator.attached_modules[0].model() == "temperatureModuleV2"
assert simulator.attached_modules[0].live_data == {
"data": {"currentTemp": 23, "targetTemp": 23},
"status": "holding at target",
Expand All @@ -168,12 +176,14 @@ def test_persistence_ot2(tmpdir: str) -> None:
attached_modules={
"magdeck": [
simulator_setup.ModuleItem(
model="magneticModuleV1",
serial_number="111",
calls=[simulator_setup.ModuleCall("engage", kwargs={"height": 3})],
)
],
"tempdeck": [
simulator_setup.ModuleItem(
model="temperatureModuleV2",
serial_number="111",
calls=[
simulator_setup.ModuleCall(
Expand Down Expand Up @@ -205,6 +215,7 @@ def test_persistence_ot3(tmpdir: str) -> None:
attached_modules={
"magdeck": [
simulator_setup.ModuleItem(
model="magneticModuleV2",
serial_number="mag-1",
calls=[
simulator_setup.ModuleCall(
Expand All @@ -216,6 +227,7 @@ def test_persistence_ot3(tmpdir: str) -> None:
],
"tempdeck": [
simulator_setup.ModuleItem(
model="temperatureModuleV2",
serial_number="temp-1",
calls=[
simulator_setup.ModuleCall(
Expand All @@ -229,6 +241,7 @@ def test_persistence_ot3(tmpdir: str) -> None:
],
),
simulator_setup.ModuleItem(
model="temperatureModuleV1",
serial_number="temp-2",
calls=[
simulator_setup.ModuleCall(
Expand Down
7 changes: 5 additions & 2 deletions api/tests/opentrons/hardware_control/test_thread_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_build_fail_raises_exception():
def test_module_cache_add_entry():
"""Test that _cached_modules updates correctly."""

mod_names = {"tempdeck": ["111"]}
mod_names = {"tempdeck": [("111", "temperatureModuleV2")]}
thread_manager = ThreadManager(
API.build_hardware_simulator, attached_modules=mod_names
)
Expand All @@ -49,7 +49,10 @@ def test_module_cache_add_entry():

async def test_module_cache_remove_entry():
"""Test that module entry gets removed from cache when module detaches."""
mod_names = {"tempdeck": ["111"], "magdeck": ["222"]}
mod_names = {
"tempdeck": [("111", "temperatureModuleV2")],
"magdeck": [("222", "magneticModuleV1")],
}
thread_manager = ThreadManager(
API.build_hardware_simulator, attached_modules=mod_names
)
Expand Down
Loading
Loading