11"""Registry for use with a Pyro Daemon client and server to allow serialization of Opentrons Hardware types and classes."""
22
3+ import datetime
34from typing import Dict
45
56import opentrons .config .types
67import opentrons .hardware_control .dev_types
8+ import opentrons .hardware_control .instruments .ot3 .instrument_calibration
9+ import opentrons .hardware_control .protocols .types
710import opentrons .hardware_control .types
811import opentrons .types
912from opentrons .util .pyro .pyro_serialization import (
@@ -40,6 +43,135 @@ def _estop_overall_status_class_to_dict(obj) -> Dict: # type: ignore
4043 }
4144
4245
46+ # GRIPPER CALIBRATION
47+ # todo(chb, 04-08-2026): This should be consumed into an automated registry process
48+ def _GripperCalibrationOffset_dict_to_class ( # type: ignore
49+ classname , d
50+ ) -> opentrons .hardware_control .instruments .ot3 .instrument_calibration .GripperCalibrationOffset :
51+ modified = (
52+ None
53+ if d ["last_modified" ] is None
54+ else datetime .datetime .fromisoformat (d ["last_modified" ])
55+ )
56+ markedAt = (
57+ None
58+ if d ["status_markedAt" ] is None
59+ else datetime .datetime .fromisoformat (d ["status_markedAt" ])
60+ )
61+ status_source = (
62+ None
63+ if d ["status_source" ] is None
64+ else opentrons .hardware_control .instruments .ot3 .instrument_calibration .SourceType (
65+ d ["status_source" ]
66+ )
67+ )
68+ return opentrons .hardware_control .instruments .ot3 .instrument_calibration .GripperCalibrationOffset (
69+ offset = opentrons .types .Point (x = d ["offset_x" ], y = d ["offset_y" ], z = d ["offset_z" ]),
70+ source = opentrons .hardware_control .instruments .ot3 .instrument_calibration .SourceType (
71+ d ["source" ]
72+ ),
73+ status = opentrons .hardware_control .instruments .ot3 .instrument_calibration .CalibrationStatus (
74+ markedBad = (d ["status_markedBad" ] == "True" ),
75+ source = status_source ,
76+ markedAt = markedAt ,
77+ ),
78+ last_modified = modified ,
79+ )
80+
81+
82+ def _GripperCalibrationOffset_class_to_dict (obj ) -> Dict : # type: ignore
83+ if isinstance (obj .last_modified , datetime .datetime ):
84+ modified = obj .last_modified .isoformat ()
85+ else :
86+ modified = None
87+ if isinstance (obj .status .markedAt , datetime .datetime ):
88+ markedAt = obj .status .markedAt .isoformat ()
89+ else :
90+ markedAt = None
91+ return {
92+ "__class__" : "opentrons.hardware_control.instruments.ot3.instrument_calibration.GripperCalibrationOffset" ,
93+ "offset_x" : obj .offset .x ,
94+ "offset_y" : obj .offset .y ,
95+ "offset_z" : obj .offset .z ,
96+ "source" : obj .source ,
97+ "status_markedBad" : obj .status .markedBad ,
98+ "status_source" : obj .status .source ,
99+ "status_markedAt" : markedAt ,
100+ "last_modified" : modified ,
101+ }
102+
103+
104+ # PIPETTER CALIBRATION
105+ # todo(chb, 04-08-2026): This should be consumed into an automated registry process
106+ def _PipetteOffsetSummary_dict_to_class ( # type: ignore
107+ classname , d
108+ ) -> opentrons .hardware_control .instruments .ot3 .instrument_calibration .PipetteOffsetSummary :
109+ modified = (
110+ None
111+ if d ["last_modified" ] is None
112+ else datetime .datetime .fromisoformat (d ["last_modified" ])
113+ )
114+ markedAt = (
115+ None
116+ if d ["status_markedAt" ] is None
117+ else datetime .datetime .fromisoformat (d ["status_markedAt" ])
118+ )
119+ status_source = (
120+ None
121+ if d ["status_source" ] is None
122+ else opentrons .hardware_control .instruments .ot3 .instrument_calibration .SourceType (
123+ d ["status_source" ]
124+ )
125+ )
126+ return opentrons .hardware_control .instruments .ot3 .instrument_calibration .PipetteOffsetSummary (
127+ offset = opentrons .types .Point (x = d ["offset_x" ], y = d ["offset_y" ], z = d ["offset_z" ]),
128+ source = opentrons .hardware_control .instruments .ot3 .instrument_calibration .SourceType (
129+ d ["source" ]
130+ ),
131+ status = opentrons .hardware_control .instruments .ot3 .instrument_calibration .CalibrationStatus (
132+ markedBad = (d ["status_markedBad" ] == "True" ),
133+ source = status_source ,
134+ markedAt = markedAt ,
135+ ),
136+ last_modified = modified ,
137+ reasonability_check_failures = [], # todo(chb: 04-09-2026): These are skipped for integration simplicity, they should be handled by automatic process
138+ )
139+
140+
141+ def _PipetteOffsetSummary_class_to_dict (obj ) -> Dict : # type: ignore
142+ if isinstance (obj .last_modified , datetime .datetime ):
143+ modified = obj .last_modified .isoformat ()
144+ else :
145+ modified = None
146+ if isinstance (obj .status .markedAt , datetime .datetime ):
147+ markedAt = obj .status .markedAt .isoformat ()
148+ else :
149+ markedAt = None
150+ return {
151+ "__class__" : "opentrons.hardware_control.instruments.ot3.instrument_calibration.PipetteOffsetSummary" ,
152+ "offset_x" : obj .offset .x ,
153+ "offset_y" : obj .offset .y ,
154+ "offset_z" : obj .offset .z ,
155+ "source" : obj .source ,
156+ "status_markedBad" : obj .status .markedBad ,
157+ "status_source" : obj .status .source ,
158+ "status_markedAt" : markedAt ,
159+ "last_modified" : modified ,
160+ "reasonability_check_failures" : None , # todo(chb: 04-09-2026): These are skipped for integration simplicity, they should be handled by automatic process
161+ }
162+
163+
164+ # Robot type registry - of note, this is meant to return a "pure" type
165+ def _robot_type_class_to_dict (obj ) -> Dict : # type: ignore
166+ return {"__class__" : "." .join ((obj .__module__ , obj .__class__ .__name__ ))}
167+
168+
169+ def _robot_type_dict_to_class ( # type: ignore
170+ classname , d
171+ ) -> type [opentrons .hardware_control .protocols .types .FlexRobotType ]:
172+ return opentrons .hardware_control .protocols .types .FlexRobotType
173+
174+
43175# Handy function to map all registries for the Hardware controller
44176def register_hardware_types () -> None :
45177 """Registers serialize and deserialize behavior for Opentrons Hardware types and classes.
@@ -59,7 +191,12 @@ def register_hardware_types() -> None:
59191 OpentronsPyroSerializer .register_enum (enum_type )
60192
61193 opentrons_pydantic_types = find_pydantic_classes_in_packages (
62- [opentrons .types , opentrons .config .types , opentrons .hardware_control .types ]
194+ [
195+ opentrons .types ,
196+ opentrons .config .types ,
197+ opentrons .hardware_control .types ,
198+ opentrons .hardware_control .protocols .types ,
199+ ]
63200 )
64201 for pydantic_type in opentrons_pydantic_types :
65202 OpentronsPyroSerializer .register_pydantic_model (pydantic_type )
@@ -72,9 +209,32 @@ def register_hardware_types() -> None:
72209
73210 OpentronsPyroSerializer .register_unhashable_dicts ()
74211
212+ # Specialized registrations:
213+ register_type_to_serpent (
214+ class_type = opentrons .hardware_control .protocols .types .FlexRobotType ,
215+ dict_to_class = _robot_type_dict_to_class ,
216+ class_to_dict = _robot_type_class_to_dict ,
217+ )
218+
219+ # todo(chb, 04-03-2026): This one should probably be removed and classes like it converted to an appropriate, automated format
75220 # E-Stop Overall registration
76221 register_type_to_serpent (
77222 class_type = opentrons .hardware_control .types .EstopOverallStatus ,
78223 dict_to_class = _estop_overall_status_dict_to_class ,
79224 class_to_dict = _estop_overall_status_class_to_dict ,
80225 )
226+
227+ # todo(chb: 04-09-2026): These are termporary direct serializations to support the initial robot server intergration, replace with automated solution
228+ # gripper calibration
229+ register_type_to_serpent (
230+ class_type = opentrons .hardware_control .instruments .ot3 .instrument_calibration .GripperCalibrationOffset ,
231+ dict_to_class = _GripperCalibrationOffset_dict_to_class ,
232+ class_to_dict = _GripperCalibrationOffset_class_to_dict ,
233+ )
234+
235+ # pipette calibration
236+ register_type_to_serpent (
237+ class_type = opentrons .hardware_control .instruments .ot3 .instrument_calibration .PipetteOffsetSummary ,
238+ dict_to_class = _PipetteOffsetSummary_dict_to_class ,
239+ class_to_dict = _PipetteOffsetSummary_class_to_dict ,
240+ )
0 commit comments