Skip to content

Commit d4f7f17

Browse files
authored
feat(hardware-testing): enable multi sensor processing in liquid probe (#14883)
<!-- Thanks for taking the time to open a pull request! Please make sure you've read the "Opening Pull Requests" section of our Contributing Guide: https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests To ensure your code is reviewed quickly and thoroughly, please fill out the sections below to the best of your ability! --> # Overview This allows the liquid level detection script to tell the pipette to buffer the data from both pipettes and fetch them afterwards, it will now spit out seprate CSVs for each sensor. post processing not yet updated so the final report just grabs one from each trial, will implement in EXEC-268 <!-- Use this section to describe your pull-request at a high level. If the PR addresses any open issues, please tag the issues here. --> # Test Plan <!-- Use this section to describe the steps that you took to test your Pull Request. If you did not perform any testing provide justification why. OT-3 Developers: You should default to testing on actual physical hardware. Once again, if you did not perform testing against hardware, justify why. Note: It can be helpful to write a test plan before doing development Example Test Plan (HTTP API Change) - Verified that new optional argument `dance-party` causes the robot to flash its lights, move the pipettes, then home. - Verified that when you omit the `dance-party` option the robot homes normally - Added protocol that uses `dance-party` argument to G-Code Testing Suite - Ran protocol that did not use `dance-party` argument and everything was successful - Added unit tests to validate that changes to pydantic model are correct --> # Changelog <!-- List out the changes to the code in this PR. Please try your best to categorize your changes and describe what has changed and why. Example changelog: - Fixed app crash when trying to calibrate an illegal pipette - Added state to API to track pipette usage - Updated API docs to mention only two pipettes are supported IMPORTANT: MAKE SURE ANY BREAKING CHANGES ARE PROPERLY COMMUNICATED --> # Review requests <!-- Describe any requests for your reviewers here. --> # Risk assessment <!-- Carefully go over your pull request and look at the other parts of the codebase it may affect. Look for the possibility, even if you think it's small, that your change may affect some other part of the system - for instance, changing return tip behavior in protocol may also change the behavior of labware calibration. Identify the other parts of the system your codebase may affect, so that in addition to your own review and testing, other people who may not have the system internalized as much as you can focus their attention and testing there. -->
1 parent 2532997 commit d4f7f17

File tree

21 files changed

+277
-138
lines changed

21 files changed

+277
-138
lines changed

api/src/opentrons/config/defaults_ot3.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from typing import Any, Dict, cast, List, Iterable, Tuple
1+
from typing import Any, Dict, cast, List, Iterable, Tuple, Optional
22
from typing_extensions import Final
33
from dataclasses import asdict
44

5-
from opentrons.hardware_control.types import OT3AxisKind
5+
from opentrons.hardware_control.types import OT3AxisKind, InstrumentProbeType
66
from .types import (
77
OT3Config,
88
ByGantryLoad,
@@ -34,7 +34,7 @@
3434
aspirate_while_sensing=False,
3535
auto_zero_sensor=True,
3636
num_baseline_reads=10,
37-
data_file="/var/pressure_sensor_data.csv",
37+
data_files={InstrumentProbeType.PRIMARY: "/data/pressure_sensor_data.csv"},
3838
)
3939

4040
DEFAULT_CALIBRATION_SETTINGS: Final[OT3CalibrationSettings] = OT3CalibrationSettings(
@@ -194,6 +194,49 @@
194194
)
195195

196196

197+
def _build_output_option_with_default(
198+
from_conf: Any, default: OutputOptions
199+
) -> OutputOptions:
200+
if from_conf is None:
201+
return default
202+
else:
203+
if isinstance(from_conf, OutputOptions):
204+
return from_conf
205+
else:
206+
try:
207+
enumval = OutputOptions[from_conf]
208+
except KeyError: # not an enum entry
209+
return default
210+
else:
211+
return enumval
212+
213+
214+
def _build_log_files_with_default(
215+
from_conf: Any,
216+
default: Optional[Dict[InstrumentProbeType, str]],
217+
) -> Optional[Dict[InstrumentProbeType, str]]:
218+
print(f"from_conf {from_conf} default {default}")
219+
if not isinstance(from_conf, dict):
220+
if default is None:
221+
return None
222+
else:
223+
return {k: v for k, v in default.items()}
224+
else:
225+
validated: Dict[InstrumentProbeType, str] = {}
226+
for k, v in from_conf.items():
227+
if isinstance(k, InstrumentProbeType):
228+
validated[k] = v
229+
else:
230+
try:
231+
enumval = InstrumentProbeType[k]
232+
except KeyError: # not an enum entry
233+
pass
234+
else:
235+
validated[enumval] = v
236+
print(f"result {validated}")
237+
return validated
238+
239+
197240
def _build_dict_with_default(
198241
from_conf: Any,
199242
default: Dict[OT3AxisKind, float],
@@ -278,6 +321,17 @@ def _build_default_cap_pass(
278321
def _build_default_liquid_probe(
279322
from_conf: Any, default: LiquidProbeSettings
280323
) -> LiquidProbeSettings:
324+
output_option = _build_output_option_with_default(
325+
from_conf.get("output_option", None), default.output_option
326+
)
327+
data_files: Optional[Dict[InstrumentProbeType, str]] = None
328+
if (
329+
output_option is OutputOptions.sync_buffer_to_csv
330+
or output_option is OutputOptions.stream_to_csv
331+
):
332+
data_files = _build_log_files_with_default(
333+
from_conf.get("data_files", {}), default.data_files
334+
)
281335
return LiquidProbeSettings(
282336
starting_mount_height=from_conf.get(
283337
"starting_mount_height", default.starting_mount_height
@@ -302,7 +356,7 @@ def _build_default_liquid_probe(
302356
num_baseline_reads=from_conf.get(
303357
"num_baseline_reads", default.num_baseline_reads
304358
),
305-
data_file=from_conf.get("data_file", default.data_file),
359+
data_files=data_files,
306360
)
307361

308362

@@ -412,7 +466,7 @@ def build_with_defaults(robot_settings: Dict[str, Any]) -> OT3Config:
412466
def serialize(config: OT3Config) -> Dict[str, Any]:
413467
def _build_dict(pairs: Iterable[Tuple[Any, Any]]) -> Dict[str, Any]:
414468
def _normalize_key(key: Any) -> Any:
415-
if isinstance(key, OT3AxisKind):
469+
if isinstance(key, OT3AxisKind) or isinstance(key, InstrumentProbeType):
416470
return key.name
417471
return key
418472

api/src/opentrons/config/types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from dataclasses import dataclass, asdict, fields
33
from typing import Dict, Tuple, TypeVar, Generic, List, cast, Optional
44
from typing_extensions import TypedDict, Literal
5-
from opentrons.hardware_control.types import OT3AxisKind
5+
from opentrons.hardware_control.types import OT3AxisKind, InstrumentProbeType
66

77

88
class AxisDict(TypedDict):
@@ -139,7 +139,7 @@ class LiquidProbeSettings:
139139
aspirate_while_sensing: bool
140140
auto_zero_sensor: bool
141141
num_baseline_reads: int
142-
data_file: Optional[str]
142+
data_files: Optional[Dict[InstrumentProbeType, str]]
143143

144144

145145
@dataclass(frozen=True)

api/src/opentrons/hardware_control/backends/flex_protocol.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ async def liquid_probe(
147147
plunger_speed: float,
148148
threshold_pascals: float,
149149
output_format: OutputOptions = OutputOptions.can_bus_only,
150-
data_file: Optional[str] = None,
150+
data_files: Optional[Dict[InstrumentProbeType, str]] = None,
151151
auto_zero_sensor: bool = True,
152152
num_baseline_reads: int = 10,
153153
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,

api/src/opentrons/hardware_control/backends/ot3controller.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,7 +1351,7 @@ async def liquid_probe(
13511351
plunger_speed: float,
13521352
threshold_pascals: float,
13531353
output_option: OutputOptions = OutputOptions.can_bus_only,
1354-
data_file: Optional[str] = None,
1354+
data_files: Optional[Dict[InstrumentProbeType, str]] = None,
13551355
auto_zero_sensor: bool = True,
13561356
num_baseline_reads: int = 10,
13571357
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
@@ -1372,6 +1372,14 @@ async def liquid_probe(
13721372
can_bus_only_output = bool(
13731373
output_option.value & OutputOptions.can_bus_only.value
13741374
)
1375+
data_files_transposed = (
1376+
None
1377+
if data_files is None
1378+
else {
1379+
sensor_id_for_instrument(probe): data_files[probe]
1380+
for probe in data_files.keys()
1381+
}
1382+
)
13751383
positions = await liquid_probe(
13761384
messenger=self._messenger,
13771385
tool=tool,
@@ -1383,7 +1391,7 @@ async def liquid_probe(
13831391
csv_output=csv_output,
13841392
sync_buffer_output=sync_buffer_output,
13851393
can_bus_only_output=can_bus_only_output,
1386-
data_file=data_file,
1394+
data_files=data_files_transposed,
13871395
auto_zero_sensor=auto_zero_sensor,
13881396
num_baseline_reads=num_baseline_reads,
13891397
sensor_id=sensor_id_for_instrument(probe),

api/src/opentrons/hardware_control/backends/ot3simulator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ async def liquid_probe(
346346
plunger_speed: float,
347347
threshold_pascals: float,
348348
output_format: OutputOptions = OutputOptions.can_bus_only,
349-
data_file: Optional[str] = None,
349+
data_files: Optional[Dict[InstrumentProbeType, str]] = None,
350350
auto_zero_sensor: bool = True,
351351
num_baseline_reads: int = 10,
352352
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,

api/src/opentrons/hardware_control/backends/ot3utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ def sensor_node_for_pipette(mount: OT3Mount) -> PipetteProbeTarget:
544544
_instr_sensor_id_lookup: Dict[InstrumentProbeType, SensorId] = {
545545
InstrumentProbeType.PRIMARY: SensorId.S0,
546546
InstrumentProbeType.SECONDARY: SensorId.S1,
547+
InstrumentProbeType.BOTH: SensorId.BOTH,
547548
}
548549

549550

api/src/opentrons/hardware_control/ot3api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2601,7 +2601,7 @@ async def liquid_probe(
26012601
(probe_settings.plunger_speed * plunger_direction),
26022602
probe_settings.sensor_threshold_pascals,
26032603
probe_settings.output_option,
2604-
probe_settings.data_file,
2604+
probe_settings.data_files,
26052605
probe_settings.auto_zero_sensor,
26062606
probe_settings.num_baseline_reads,
26072607
probe=probe if probe else InstrumentProbeType.PRIMARY,

api/src/opentrons/hardware_control/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ class GripperJawState(enum.Enum):
624624
class InstrumentProbeType(enum.Enum):
625625
PRIMARY = enum.auto()
626626
SECONDARY = enum.auto()
627+
BOTH = enum.auto()
627628

628629

629630
class GripperProbe(enum.Enum):

api/tests/opentrons/config/ot3_settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
"aspirate_while_sensing": False,
130130
"auto_zero_sensor": True,
131131
"num_baseline_reads": 10,
132-
"data_file": "/var/pressure_sensor_data.csv",
132+
"data_files": {"PRIMARY": "/data/pressure_sensor_data.csv"},
133133
},
134134
"calibration": {
135135
"z_offset": {

api/tests/opentrons/hardware_control/backends/test_ot3_controller.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
UpdateState,
6262
EstopState,
6363
CurrentConfig,
64+
InstrumentProbeType,
6465
)
6566
from opentrons.hardware_control.errors import (
6667
InvalidPipetteName,
@@ -185,7 +186,7 @@ def fake_liquid_settings() -> LiquidProbeSettings:
185186
aspirate_while_sensing=False,
186187
auto_zero_sensor=False,
187188
num_baseline_reads=8,
188-
data_file="fake_data_file",
189+
data_files={InstrumentProbeType.PRIMARY: "fake_file_name"},
189190
)
190191

191192

0 commit comments

Comments
 (0)