Skip to content

Commit 79778e7

Browse files
authored
feat(api): Tempdeck G-Code Parsing (#8160)
1 parent 0a1851b commit 79778e7

File tree

8 files changed

+249
-4
lines changed

8 files changed

+249
-4
lines changed

api/src/opentrons/hardware_control/g_code_parsing/g_code.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
from .errors import UnparsableGCodeError
77
from opentrons.drivers.smoothie_drivers.driver_3_0 import GCODE as SMOOTHIE_G_CODE
88
from opentrons.drivers.mag_deck.driver import GCODE as MAGDECK_G_CODE
9+
from opentrons.drivers.temp_deck.driver import GCODE as TEMPDECK_G_CODE
910
from opentrons.hardware_control.g_code_parsing.utils import reverse_enum
1011
from opentrons.hardware_control.emulation.parser import Parser
1112
from opentrons.hardware_control.g_code_parsing.g_code_functionality_defs.\
1213
g_code_functionality_def_base import Explanation
13-
from .g_code_functionality_defs import smoothie, magdeck
14+
from .g_code_functionality_defs import smoothie, magdeck, tempdeck
1415

1516

1617
class GCode:
@@ -101,6 +102,17 @@ class GCode:
101102
magdeck.DeviceInfoGCodeFunctionalityDef
102103
}
103104

105+
TEMPDECK_G_CODE_EXPLANATION_MAPPING = {
106+
TEMPDECK_G_CODE.DISENGAGE.name:
107+
tempdeck.DisengageGCodeFunctionalityDef,
108+
TEMPDECK_G_CODE.SET_TEMP.name:
109+
tempdeck.SetTempGCodeFunctionalityDef,
110+
TEMPDECK_G_CODE.GET_TEMP.name:
111+
tempdeck.GetTempGCodeFunctionalityDef,
112+
TEMPDECK_G_CODE.DEVICE_INFO.name:
113+
tempdeck.DeviceInfoGCodeFunctionalityDef
114+
}
115+
104116
# Smoothie G-Code Parsing Characters
105117
SET_SPEED_CHARACTER = 'F'
106118
MOVE_CHARACTERS = ['X', 'Y', 'Z', 'A', 'B', 'C']
@@ -113,9 +125,13 @@ class GCode:
113125
MAGDECK_IDENT = 'magdeck'
114126
MAGDECK_G_CODE_LOOKUP = reverse_enum(MAGDECK_G_CODE)
115127

128+
TEMPDECK_IDENT = 'tempdeck'
129+
TEMPDECK_G_CODE_LOOKUP = reverse_enum(TEMPDECK_G_CODE)
130+
116131
DEVICE_GCODE_LOOKUP = {
117132
SMOOTHIE_IDENT: SMOOTHIE_G_CODE_LOOKUP,
118133
MAGDECK_IDENT: MAGDECK_G_CODE_LOOKUP,
134+
TEMPDECK_IDENT: TEMPDECK_G_CODE_LOOKUP,
119135
}
120136

121137
SPECIAL_HANDLING_REQUIRED_G_CODES = [
@@ -126,6 +142,7 @@ class GCode:
126142
EXPLANATION_LOOKUP = {
127143
SMOOTHIE_IDENT: SMOOTHIE_G_CODE_EXPLANATION_MAPPING,
128144
MAGDECK_IDENT: MAGDECK_G_CODE_EXPLANATION_MAPPING,
145+
TEMPDECK_IDENT: TEMPDECK_G_CODE_EXPLANATION_MAPPING
129146
}
130147

131148
@classmethod
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from .device_info_g_code_functionality_def import DeviceInfoGCodeFunctionalityDef
2+
from .disengage_g_code_functionality_def import DisengageGCodeFunctionalityDef
3+
from .get_temp_g_code_functionality_def import GetTempGCodeFunctionalityDef
4+
from .set_temp_g_code_functionality_def import SetTempGCodeFunctionalityDef
5+
6+
__all__ = [
7+
'DeviceInfoGCodeFunctionalityDef',
8+
'DisengageGCodeFunctionalityDef',
9+
'GetTempGCodeFunctionalityDef',
10+
'SetTempGCodeFunctionalityDef'
11+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import re
2+
from typing import Dict
3+
from opentrons.hardware_control.g_code_parsing.g_code_functionality_defs.\
4+
g_code_functionality_def_base import GCodeFunctionalityDefBase
5+
6+
7+
class DeviceInfoGCodeFunctionalityDef(GCodeFunctionalityDefBase):
8+
RESPONSE_RE = re.compile(r'serial:(.*?)model:(.*?)version:(.*?)$')
9+
10+
@classmethod
11+
def _generate_command_explanation(cls, g_code_args: Dict[str, str]) -> str:
12+
return 'Getting tempdeck device info'
13+
14+
@classmethod
15+
def _generate_response_explanation(cls, response: str) -> str:
16+
match = cls.RESPONSE_RE.match(response)
17+
message = ''
18+
if match is not None:
19+
serial_number, model, fw_version = match.groups()
20+
message = f'Tempdeck info:' \
21+
f'\n\tSerial Number: {serial_number.strip()}' \
22+
f'\n\tModel: {model.strip()}' \
23+
f'\n\tFirmware Version: {fw_version.strip()}'
24+
25+
return message
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from typing import Dict
2+
from opentrons.hardware_control.g_code_parsing.g_code_functionality_defs.\
3+
g_code_functionality_def_base import GCodeFunctionalityDefBase
4+
5+
6+
class DisengageGCodeFunctionalityDef(GCodeFunctionalityDefBase):
7+
8+
@classmethod
9+
def _generate_command_explanation(cls, g_code_args: Dict[str, str]) -> str:
10+
return 'Halting holding temperature. Reducing temperature to ' \
11+
'55C if it is greater than 55C'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import re
2+
from typing import Dict
3+
from opentrons.hardware_control.g_code_parsing.g_code_functionality_defs.\
4+
g_code_functionality_def_base import GCodeFunctionalityDefBase
5+
6+
7+
class GetTempGCodeFunctionalityDef(GCodeFunctionalityDefBase):
8+
RESPONSE_RE = re.compile(r'T:(?P<set_temp>.*?)C:(?P<current_temp>\d+.\d+)')
9+
10+
@classmethod
11+
def _generate_command_explanation(cls, g_code_args: Dict[str, str]) -> str:
12+
return 'Getting temperature'
13+
14+
@classmethod
15+
def _generate_response_explanation(cls, response: str) -> str:
16+
match = cls.RESPONSE_RE.match(response)
17+
message = ''
18+
19+
if match is not None:
20+
current_temp = match.groupdict()['current_temp'].strip()
21+
set_temp = match.groupdict()['set_temp'].strip()
22+
if set_temp == 'none':
23+
message = f'Temp deck is disengaged. ' \
24+
f'Current temperature is {current_temp}C'
25+
else:
26+
message = f'Set temperature is {set_temp}C. ' \
27+
f'Current temperature is {current_temp}C'
28+
return message
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from typing import Dict
2+
from string import Template
3+
from enum import Enum
4+
from opentrons.hardware_control.g_code_parsing.g_code_functionality_defs.\
5+
g_code_functionality_def_base import GCodeFunctionalityDefBase
6+
7+
8+
class SetTempGCodeFunctionalityDef(GCodeFunctionalityDefBase):
9+
# Using this list to output string in specific order
10+
EXPECTED_ARGS = ['S', 'P', 'I', 'D']
11+
12+
class ValDefinedMessage(str, Enum):
13+
S = 'Temperature: ${val}C'
14+
P = 'Kp: $val'
15+
I = 'Ki: $val' # noqa: E741
16+
D = 'Kd: $val'
17+
18+
@classmethod
19+
def _generate_command_explanation(cls, g_code_args: Dict[str, str]) -> str:
20+
message_list = []
21+
for arg in cls.EXPECTED_ARGS:
22+
g_code_arg_val = g_code_args.get(arg)
23+
if g_code_arg_val is not None:
24+
message_temp = Template(cls.ValDefinedMessage[arg].value)
25+
message = message_temp.substitute(val=g_code_arg_val)
26+
message_list.append(message)
27+
28+
return 'Setting temperature values to the following:\n\t' \
29+
+ '\n\t'.join(message_list)

api/src/opentrons/hardware_control/g_code_parsing/utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
from typing import Dict, Type, Union
33
from opentrons.drivers.smoothie_drivers.driver_3_0 import GCODE as SMOOTHIE_G_CODE
44
from opentrons.drivers.mag_deck.driver import GCODE as MAGDECK_G_CODE
5+
from opentrons.drivers.temp_deck.driver import GCODE as TEMPDECK_G_CODE
56

67

78
WRITE_REGEX = re.compile(r"(.*?) \| (.*?) \|(.*?)$")
89

910

1011
def reverse_enum(
11-
enum_to_reverse: Union[Type[SMOOTHIE_G_CODE], Type[MAGDECK_G_CODE]]
12+
enum_to_reverse: Union[
13+
Type[SMOOTHIE_G_CODE], Type[MAGDECK_G_CODE], Type[TEMPDECK_G_CODE]
14+
]
1215
) -> Dict:
1316
"""
1417
Returns dictionary with keys and values switched from passed Enum

api/tests/opentrons/hardware_control/g_code_parsing/test_g_code.py

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,48 @@ def magdeck_g_codes() -> List[GCode]:
243243
return [code[0] for code in g_code_list]
244244

245245

246+
def tempdeck_g_codes() -> List[GCode]:
247+
raw_codes = [
248+
# Test 48
249+
['M18', 'ok\r\nok\r\n'],
250+
251+
# Test 49
252+
['M104 S99.6 P0.4 I0.2 D0.3', 'ok\r\nok\r\n'],
253+
254+
# Test 50
255+
['M104 S102.5', '\r\nok\r\nok\r\n'],
256+
257+
# Test 51
258+
['M105', 'T:86.500 C:66.223\r\nok\r\nok\r\n'],
259+
260+
# Test 52
261+
['M105', 'T:none C:45.32\r\nok\r\nok\r\n'],
262+
263+
# Test 53
264+
[
265+
'M115', 'serial:TDV0118052801 model:temp_deck_v1 '
266+
'version:edge-11aa22b\r\nok\r\nok\r\n'
267+
],
268+
]
269+
g_code_list = [
270+
GCode.from_raw_code(code, 'tempdeck', response)
271+
for code, response in raw_codes
272+
]
273+
274+
for g_code in g_code_list:
275+
if len(g_code) > 1:
276+
tempdeck_g_codes = ', '.join([code.g_code for code in g_code])
277+
raise Exception('Hey, you forgot to put a comma between the G-Codes for '
278+
f'{tempdeck_g_codes}')
279+
280+
return [code[0] for code in g_code_list]
281+
282+
246283
def all_g_codes() -> List[GCode]:
247284
g_codes = []
248285
g_codes.extend(smoothie_g_codes())
249286
g_codes.extend(magdeck_g_codes())
287+
g_codes.extend(tempdeck_g_codes())
250288

251289
return g_codes
252290

@@ -296,13 +334,22 @@ def expected_function_name_values() -> List[str]:
296334
'MICROSTEPPING_C_DISABLE', # Test 39
297335
'READ_INSTRUMENT_ID', # Test 40
298336
'READ_INSTRUMENT_MODEL', # Test 41
299-
# Magdeck
337+
338+
# Magdeck
300339
'HOME', # Test 42
301340
'MOVE', # Test 43
302341
'GET_CURRENT_POSITION', # Test 44
303342
'PROBE_PLATE', # Test 45
304343
'GET_PLATE_HEIGHT', # Test 46
305344
'DEVICE_INFO', # Test 47
345+
346+
# Tempdeck
347+
'DISENGAGE', # Test 48
348+
'SET_TEMP', # Test 49
349+
'SET_TEMP', # Test 50
350+
'GET_TEMP', # Test 51
351+
'GET_TEMP', # Test 52
352+
'DEVICE_INFO' # Test 53
306353
]
307354

308355

@@ -465,6 +512,20 @@ def expected_arg_values() -> List[Dict[str, int]]:
465512
{},
466513
# Test 47
467514
{},
515+
516+
# Tempdeck
517+
# Test 48
518+
{},
519+
# Test 49
520+
{'S': 99.6, 'P': 0.4, 'I': 0.2, 'D': 0.3},
521+
# Test 50
522+
{'S': 102.5},
523+
# Test 51
524+
{},
525+
# Test 52
526+
{},
527+
# Test 53
528+
{},
468529
]
469530

470531

@@ -869,7 +930,7 @@ def explanations() -> List[Explanation]:
869930
command_explanation='Reading instrument model for Left pipette'
870931
),
871932

872-
# Magdeck
933+
# Magdeck
873934
# Test 42
874935
Explanation(
875936
code='G28.2',
@@ -921,6 +982,66 @@ def explanations() -> List[Explanation]:
921982
provided_args={},
922983
command_explanation='Getting magdeck device info'
923984
),
985+
986+
# Tempdeck
987+
# Test 48
988+
Explanation(
989+
code='M18',
990+
command_name='DISENGAGE',
991+
response='',
992+
provided_args={},
993+
command_explanation='Halting holding temperature. Reducing temperature to '
994+
'55C if it is greater than 55C'
995+
),
996+
# Test 49
997+
Explanation(
998+
code='M104',
999+
command_name='SET_TEMP',
1000+
response='',
1001+
provided_args={'S': 99.6, 'P': 0.4, 'I': 0.2, 'D': 0.3},
1002+
command_explanation='Setting temperature values to the following:'
1003+
'\n\tTemperature: 99.6C'
1004+
'\n\tKp: 0.4'
1005+
'\n\tKi: 0.2'
1006+
'\n\tKd: 0.3'
1007+
),
1008+
# Test 50
1009+
Explanation(
1010+
code='M104',
1011+
command_name='SET_TEMP',
1012+
response='',
1013+
provided_args={'S': 102.5},
1014+
command_explanation='Setting temperature values to the following:'
1015+
'\n\tTemperature: 102.5C'
1016+
1017+
),
1018+
# Test 51
1019+
Explanation(
1020+
code='M105',
1021+
command_name='GET_TEMP',
1022+
response='Set temperature is 86.500C. Current temperature is 66.223C',
1023+
provided_args={},
1024+
command_explanation='Getting temperature',
1025+
),
1026+
# Test 52
1027+
Explanation(
1028+
code='M105',
1029+
command_name='GET_TEMP',
1030+
response='Temp deck is disengaged. Current temperature is 45.32C',
1031+
provided_args={},
1032+
command_explanation='Getting temperature',
1033+
),
1034+
# Test 53
1035+
Explanation(
1036+
code='M115',
1037+
command_name='DEVICE_INFO',
1038+
response='Tempdeck info:'
1039+
'\n\tSerial Number: TDV0118052801'
1040+
'\n\tModel: temp_deck_v1'
1041+
'\n\tFirmware Version: edge-11aa22b',
1042+
provided_args={},
1043+
command_explanation='Getting tempdeck device info'
1044+
)
9241045
]
9251046

9261047

0 commit comments

Comments
 (0)