opentrons 8.3.2__py2.py3-none-any.whl → 8.4.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +102 -5
- opentrons/legacy_commands/helpers.py +74 -1
- opentrons/legacy_commands/types.py +33 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1356 -107
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +6 -14
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
- opentrons/protocol_api/core/engine/well.py +73 -5
- opentrons/protocol_api/core/instrument.py +71 -21
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +23 -2
- opentrons/protocol_api/instrument_context.py +454 -150
- opentrons/protocol_api/labware.py +98 -50
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +163 -19
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +66 -36
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +102 -33
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +27 -13
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/move_to_well.py +5 -11
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +159 -8
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
- opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
- opentrons/protocol_engine/errors/__init__.py +10 -0
- opentrons/protocol_engine/errors/exceptions.py +62 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +235 -25
- opentrons/protocol_engine/execution/tip_handler.py +82 -32
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +36 -14
- opentrons/protocol_engine/state/geometry.py +892 -227
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +210 -67
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +55 -9
- opentrons/protocol_engine/types/__init__.py +300 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +111 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +301 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +27 -23
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +20 -7
- opentrons/util/logging_config.py +94 -25
- opentrons/util/logging_queue_handler.py +61 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,40 +1,138 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import re
|
|
3
|
-
|
|
3
|
+
import base64
|
|
4
|
+
from typing import List, Optional
|
|
4
5
|
|
|
6
|
+
from opentrons.drivers.asyncio.communication.errors import NoResponse
|
|
5
7
|
from opentrons.drivers.command_builder import CommandBuilder
|
|
6
8
|
from opentrons.drivers.asyncio.communication import AsyncResponseSerialConnection
|
|
7
9
|
|
|
8
|
-
from .abstract import
|
|
10
|
+
from .abstract import AbstractFlexStackerDriver
|
|
11
|
+
from .errors import StackerErrorCodes, MotorStallDetected
|
|
9
12
|
from .types import (
|
|
10
13
|
GCODE,
|
|
14
|
+
ActiveRange,
|
|
15
|
+
LEDPattern,
|
|
16
|
+
MeasurementKind,
|
|
17
|
+
MoveResult,
|
|
18
|
+
SpadMapID,
|
|
11
19
|
StackerAxis,
|
|
12
20
|
PlatformStatus,
|
|
13
21
|
Direction,
|
|
14
22
|
StackerInfo,
|
|
15
23
|
HardwareRevision,
|
|
16
24
|
MoveParams,
|
|
25
|
+
AxisParams,
|
|
17
26
|
LimitSwitchStatus,
|
|
18
27
|
LEDColor,
|
|
28
|
+
StallGuardParams,
|
|
29
|
+
TOFConfiguration,
|
|
30
|
+
TOFMeasurement,
|
|
31
|
+
TOFMeasurementFrame,
|
|
32
|
+
TOFMeasurementResult,
|
|
33
|
+
TOFSensor,
|
|
34
|
+
TOFSensorMode,
|
|
35
|
+
TOFSensorState,
|
|
36
|
+
TOFSensorStatus,
|
|
19
37
|
)
|
|
38
|
+
from .utils import validate_histogram_frame
|
|
20
39
|
|
|
21
40
|
|
|
22
41
|
FS_BAUDRATE = 115200
|
|
23
|
-
DEFAULT_FS_TIMEOUT =
|
|
42
|
+
DEFAULT_FS_TIMEOUT = 1
|
|
43
|
+
FS_MOVE_TIMEOUT = 20
|
|
44
|
+
FS_TOF_TIMEOUT = 20
|
|
24
45
|
FS_ACK = "OK\n"
|
|
25
46
|
FS_ERROR_KEYWORD = "err"
|
|
26
47
|
FS_ASYNC_ERROR_ACK = "async"
|
|
27
48
|
DEFAULT_COMMAND_RETRIES = 0
|
|
28
49
|
GCODE_ROUNDING_PRECISION = 2
|
|
29
50
|
|
|
51
|
+
# LED animation range values
|
|
52
|
+
MIN_DURATION_MS = 25 # 25ms
|
|
53
|
+
MAX_DURATION_MS = 10000 # 10s
|
|
54
|
+
MAX_REPS = 10
|
|
55
|
+
|
|
56
|
+
# TOF Sensor
|
|
57
|
+
TOF_FRAME_RETRIES = 1
|
|
58
|
+
NUMBER_OF_BINS = 128
|
|
59
|
+
|
|
60
|
+
# Stallguard defaults
|
|
61
|
+
STALLGUARD_CONFIG = {
|
|
62
|
+
StackerAxis.X: StallGuardParams(StackerAxis.X, True, 2),
|
|
63
|
+
StackerAxis.Z: StallGuardParams(StackerAxis.Z, True, 2),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
STACKER_MOTION_CONFIG = {
|
|
67
|
+
StackerAxis.X: {
|
|
68
|
+
"home": AxisParams(
|
|
69
|
+
run_current=1.5, # mAmps
|
|
70
|
+
hold_current=0.75,
|
|
71
|
+
move_params=MoveParams(
|
|
72
|
+
max_speed=10.0, # mm/s
|
|
73
|
+
acceleration=100.0, # mm/s^2
|
|
74
|
+
max_speed_discont=40.0, # mm/s
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
"move": AxisParams(
|
|
78
|
+
run_current=1.0,
|
|
79
|
+
hold_current=0.75,
|
|
80
|
+
move_params=MoveParams(
|
|
81
|
+
max_speed=200.0,
|
|
82
|
+
acceleration=1500.0,
|
|
83
|
+
max_speed_discont=40.0,
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
},
|
|
87
|
+
StackerAxis.Z: {
|
|
88
|
+
"home": AxisParams(
|
|
89
|
+
run_current=1.5,
|
|
90
|
+
hold_current=1.8,
|
|
91
|
+
move_params=MoveParams(
|
|
92
|
+
max_speed=10.0,
|
|
93
|
+
acceleration=100.0,
|
|
94
|
+
max_speed_discont=25.0,
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
"move": AxisParams(
|
|
98
|
+
run_current=1.5,
|
|
99
|
+
hold_current=0.5,
|
|
100
|
+
move_params=MoveParams(
|
|
101
|
+
max_speed=150.0,
|
|
102
|
+
acceleration=500.0,
|
|
103
|
+
max_speed_discont=25.0,
|
|
104
|
+
),
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
StackerAxis.L: {
|
|
108
|
+
"home": AxisParams(
|
|
109
|
+
run_current=0.8,
|
|
110
|
+
hold_current=0.15,
|
|
111
|
+
move_params=MoveParams(
|
|
112
|
+
max_speed=100.0,
|
|
113
|
+
acceleration=800.0,
|
|
114
|
+
max_speed_discont=40.0,
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
"move": AxisParams(
|
|
118
|
+
run_current=0.6,
|
|
119
|
+
hold_current=0.15,
|
|
120
|
+
move_params=MoveParams(
|
|
121
|
+
max_speed=100.0,
|
|
122
|
+
acceleration=800.0,
|
|
123
|
+
max_speed_discont=40.0,
|
|
124
|
+
),
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
30
129
|
|
|
31
|
-
class FlexStackerDriver(
|
|
130
|
+
class FlexStackerDriver(AbstractFlexStackerDriver):
|
|
32
131
|
"""FLEX Stacker driver."""
|
|
33
132
|
|
|
34
133
|
@classmethod
|
|
35
134
|
def parse_device_info(cls, response: str) -> StackerInfo:
|
|
36
135
|
"""Parse stacker info."""
|
|
37
|
-
# TODO: Validate serial number format once established
|
|
38
136
|
_RE = re.compile(
|
|
39
137
|
f"^{GCODE.DEVICE_INFO} FW:(?P<fw>\\S+) HW:Opentrons-flex-stacker-(?P<hw>\\S+) SerialNo:(?P<sn>\\S+)$"
|
|
40
138
|
)
|
|
@@ -45,6 +143,15 @@ class FlexStackerDriver(AbstractStackerDriver):
|
|
|
45
143
|
m.group("fw"), HardwareRevision(m.group("hw")), m.group("sn")
|
|
46
144
|
)
|
|
47
145
|
|
|
146
|
+
@classmethod
|
|
147
|
+
def parse_reset_reason(cls, response: str) -> int:
|
|
148
|
+
"""Parse the reset reason"""
|
|
149
|
+
_RE = re.compile(rf"^{GCODE.GET_RESET_REASON} R:(?P<R>\d)$")
|
|
150
|
+
match = _RE.match(response)
|
|
151
|
+
if not match:
|
|
152
|
+
raise ValueError(f"Incorrect Response for reset reason: {response}")
|
|
153
|
+
return int(match.group("R"))
|
|
154
|
+
|
|
48
155
|
@classmethod
|
|
49
156
|
def parse_limit_switch_status(cls, response: str) -> LimitSwitchStatus:
|
|
50
157
|
"""Parse limit switch statuses."""
|
|
@@ -70,26 +177,154 @@ class FlexStackerDriver(AbstractStackerDriver):
|
|
|
70
177
|
@classmethod
|
|
71
178
|
def parse_door_closed(cls, response: str) -> bool:
|
|
72
179
|
"""Parse door closed."""
|
|
73
|
-
_RE = re.compile(
|
|
180
|
+
_RE = re.compile(rf"^{GCODE.GET_DOOR_SWITCH} D:(\d)$")
|
|
74
181
|
match = _RE.match(response)
|
|
75
182
|
if not match:
|
|
76
183
|
raise ValueError(f"Incorrect Response for door closed: {response}")
|
|
77
184
|
return bool(int(match.group(1)))
|
|
78
185
|
|
|
186
|
+
@classmethod
|
|
187
|
+
def parse_installation_detected(cls, response: str) -> bool:
|
|
188
|
+
"""Parse install detection."""
|
|
189
|
+
_RE = re.compile(rf"^{GCODE.GET_INSTALL_DETECTED} I:(\d)$")
|
|
190
|
+
match = _RE.match(response)
|
|
191
|
+
if not match:
|
|
192
|
+
raise ValueError(
|
|
193
|
+
f"Incorrect Response for installation detected: {response}"
|
|
194
|
+
)
|
|
195
|
+
return bool(int(match.group(1)))
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def parse_move_params(cls, response: str) -> MoveParams:
|
|
199
|
+
"""Parse move params."""
|
|
200
|
+
field_names = MoveParams.get_fields()
|
|
201
|
+
pattern = r"\s".join(rf"{f}:(?P<{f}>(\d*\.)?\d+)" for f in field_names)
|
|
202
|
+
_RE = re.compile(rf"^{GCODE.GET_MOVE_PARAMS} M:([XZL]) {pattern}$")
|
|
203
|
+
m = _RE.match(response)
|
|
204
|
+
if not m:
|
|
205
|
+
raise ValueError(f"Incorrect Response for move params: {response}")
|
|
206
|
+
return MoveParams(
|
|
207
|
+
max_speed=float(m.group("V")),
|
|
208
|
+
acceleration=float(m.group("A")),
|
|
209
|
+
max_speed_discont=float(m.group("D")),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def parse_stallguard_params(cls, response: str) -> StallGuardParams:
|
|
214
|
+
"""Parse stallguard params."""
|
|
215
|
+
pattern = r"(?P<M>[XZL]):(?P<E>\d) T:(?P<T>\d+)"
|
|
216
|
+
_RE = re.compile(f"^{GCODE.GET_STALLGUARD_THRESHOLD} {pattern}$")
|
|
217
|
+
m = _RE.match(response)
|
|
218
|
+
if not m:
|
|
219
|
+
raise ValueError(f"Incorrect Response for stallfguard params: {response}")
|
|
220
|
+
return StallGuardParams(
|
|
221
|
+
axis=StackerAxis(m.group("M")),
|
|
222
|
+
enabled=bool(int(m.group("E"))),
|
|
223
|
+
threshold=int(m.group("T")),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def parse_get_motor_register(cls, response: str) -> int:
|
|
228
|
+
"""Parse get motor register value."""
|
|
229
|
+
pattern = r"(?P<M>[XZL]):(?P<R>\d+) V:(?P<V>\d+)"
|
|
230
|
+
_RE = re.compile(f"^{GCODE.GET_MOTOR_DRIVER_REGISTER} {pattern}$")
|
|
231
|
+
m = _RE.match(response)
|
|
232
|
+
if not m:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
f"Incorrect Response for get motor driver register: {response}"
|
|
235
|
+
)
|
|
236
|
+
return int(m.group("V"))
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def parse_get_tof_sensor_register(cls, response: str) -> int:
|
|
240
|
+
"""Parse get tof sensor register value."""
|
|
241
|
+
pattern = r"(?P<S>[XZ]):(?P<R>\d+) V:(?P<V>\d+)"
|
|
242
|
+
_RE = re.compile(f"^{GCODE.GET_TOF_DRIVER_REGISTER} {pattern}$")
|
|
243
|
+
m = _RE.match(response)
|
|
244
|
+
if not m:
|
|
245
|
+
raise ValueError(
|
|
246
|
+
f"Incorrect Response for get tof sensor driver register: {response}"
|
|
247
|
+
)
|
|
248
|
+
return int(m.group("V"))
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def parse_tof_sensor_status(cls, response: str) -> TOFSensorStatus:
|
|
252
|
+
"""Parse get tof sensor status response."""
|
|
253
|
+
pattern = r"(?P<S>[XZ]):(?P<O>\d) T:(?P<T>\d) M:(?P<M>\d)"
|
|
254
|
+
_RE = re.compile(f"^{GCODE.GET_TOF_SENSOR_STATUS} {pattern}$")
|
|
255
|
+
m = _RE.match(response)
|
|
256
|
+
if not m:
|
|
257
|
+
raise ValueError(
|
|
258
|
+
f"Incorrect Response for get tof sensor status: {response}"
|
|
259
|
+
)
|
|
260
|
+
return TOFSensorStatus(
|
|
261
|
+
sensor=TOFSensor(m.group("S")),
|
|
262
|
+
state=TOFSensorState(int(m.group("T"))),
|
|
263
|
+
mode=TOFSensorMode(int(m.group("M"))),
|
|
264
|
+
ok=bool(int(m.group("O"))),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
def parse_manage_tof_measurement(cls, response: str) -> TOFMeasurement:
|
|
269
|
+
"""Parse manage tof measurement response."""
|
|
270
|
+
pattern = r"(?P<S>[XZ]) K:(?P<K>\d+) C:(?P<C>\d) L:(?P<L>\d+)"
|
|
271
|
+
_RE = re.compile(f"^{GCODE.MANAGE_TOF_MEASUREMENT} {pattern}$")
|
|
272
|
+
m = _RE.match(response)
|
|
273
|
+
if not m:
|
|
274
|
+
raise ValueError(
|
|
275
|
+
f"Incorrect Response for manage tof measurements: {response}"
|
|
276
|
+
)
|
|
277
|
+
return TOFMeasurement(
|
|
278
|
+
sensor=TOFSensor(m.group("S")),
|
|
279
|
+
kind=MeasurementKind(int(m.group("K"))),
|
|
280
|
+
cancelled=bool(int(m.group("C"))),
|
|
281
|
+
total_bytes=int(m.group("L")),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
@classmethod
|
|
285
|
+
def parse_get_tof_measurement(cls, response: str) -> TOFMeasurementFrame:
|
|
286
|
+
"""Parse get tof measurement response frame."""
|
|
287
|
+
pattern = r"(?P<S>[XZ]) I:(?P<I>\d+) D:(?P<D>.+)"
|
|
288
|
+
_RE = re.compile(f"^{GCODE.GET_TOF_MEASUREMENT} {pattern}$")
|
|
289
|
+
m = _RE.match(response)
|
|
290
|
+
if not m:
|
|
291
|
+
raise ValueError(
|
|
292
|
+
f"Incorrect Response for get tof measurement frame: {response}"
|
|
293
|
+
)
|
|
294
|
+
return TOFMeasurementFrame(
|
|
295
|
+
sensor=TOFSensor(m.group("S")),
|
|
296
|
+
frame_id=int(m.group("I")),
|
|
297
|
+
data=base64.b64decode(m.group("D")),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
@classmethod
|
|
301
|
+
def parse_get_tof_configuration(cls, response: str) -> TOFConfiguration:
|
|
302
|
+
"""Parse get tof sensor configuration response."""
|
|
303
|
+
pattern = r"(?P<S>[XZ]) I:(?P<I>\d+) A:(?P<A>\d+) K:(?P<K>\d+) P:(?P<P>\d+) H:(?P<H>\d)"
|
|
304
|
+
_RE = re.compile(f"^{GCODE.GET_TOF_CONFIGURATION} {pattern}$")
|
|
305
|
+
m = _RE.match(response)
|
|
306
|
+
if not m:
|
|
307
|
+
raise ValueError(
|
|
308
|
+
f"Incorrect Response for get tof sensor configuration: {response}"
|
|
309
|
+
)
|
|
310
|
+
return TOFConfiguration(
|
|
311
|
+
sensor=TOFSensor(m.group("S")),
|
|
312
|
+
spad_map_id=SpadMapID(int(m.group("I"))),
|
|
313
|
+
active_range=ActiveRange(int(m.group("A"))),
|
|
314
|
+
kilo_iterations=int(m.group("K")),
|
|
315
|
+
report_period_ms=int(m.group("P")),
|
|
316
|
+
histogram_dump=bool(m.group("H")),
|
|
317
|
+
)
|
|
318
|
+
|
|
79
319
|
@classmethod
|
|
80
320
|
def append_move_params(
|
|
81
321
|
cls, command: CommandBuilder, params: MoveParams | None
|
|
82
322
|
) -> CommandBuilder:
|
|
83
323
|
"""Append move params."""
|
|
84
324
|
if params is not None:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
command.add_float("A", params.acceleration, GCODE_ROUNDING_PRECISION)
|
|
89
|
-
if params.max_speed_discont is not None:
|
|
90
|
-
command.add_float(
|
|
91
|
-
"D", params.max_speed_discont, GCODE_ROUNDING_PRECISION
|
|
92
|
-
)
|
|
325
|
+
command.add_float("V", params.max_speed, GCODE_ROUNDING_PRECISION)
|
|
326
|
+
command.add_float("A", params.acceleration, GCODE_ROUNDING_PRECISION)
|
|
327
|
+
command.add_float("D", params.max_speed_discont, GCODE_ROUNDING_PRECISION)
|
|
93
328
|
return command
|
|
94
329
|
|
|
95
330
|
@classmethod
|
|
@@ -106,6 +341,8 @@ class FlexStackerDriver(AbstractStackerDriver):
|
|
|
106
341
|
loop=loop,
|
|
107
342
|
error_keyword=FS_ERROR_KEYWORD,
|
|
108
343
|
async_error_ack=FS_ASYNC_ERROR_ACK,
|
|
344
|
+
reset_buffer_before_write=True,
|
|
345
|
+
error_codes=StackerErrorCodes,
|
|
109
346
|
)
|
|
110
347
|
return cls(connection)
|
|
111
348
|
|
|
@@ -135,25 +372,292 @@ class FlexStackerDriver(AbstractStackerDriver):
|
|
|
135
372
|
response = await self._connection.send_command(
|
|
136
373
|
GCODE.DEVICE_INFO.build_command()
|
|
137
374
|
)
|
|
138
|
-
|
|
139
|
-
|
|
375
|
+
device_info = self.parse_device_info(response)
|
|
376
|
+
reason_resp = await self._connection.send_command(
|
|
377
|
+
GCODE.GET_RESET_REASON.build_command()
|
|
378
|
+
)
|
|
379
|
+
reason = self.parse_reset_reason(reason_resp)
|
|
380
|
+
device_info.rr = reason
|
|
381
|
+
return device_info
|
|
140
382
|
|
|
141
|
-
async def set_serial_number(self, sn: str) ->
|
|
383
|
+
async def set_serial_number(self, sn: str) -> None:
|
|
142
384
|
"""Set Serial Number."""
|
|
143
|
-
|
|
385
|
+
if not re.match(r"^FST[\w]{1}[\d]{2}[\d]{8}[\d]+$", sn):
|
|
386
|
+
raise ValueError(
|
|
387
|
+
f"Invalid serial number: ({sn}) expected format: FSTA1020250119001"
|
|
388
|
+
)
|
|
144
389
|
resp = await self._connection.send_command(
|
|
145
390
|
GCODE.SET_SERIAL_NUMBER.build_command().add_element(sn)
|
|
146
391
|
)
|
|
147
392
|
if not re.match(rf"^{GCODE.SET_SERIAL_NUMBER}$", resp):
|
|
148
393
|
raise ValueError(f"Incorrect Response for set serial number: {resp}")
|
|
149
|
-
return True
|
|
150
394
|
|
|
151
|
-
async def
|
|
395
|
+
async def enable_motors(self, axis: List[StackerAxis]) -> None:
|
|
396
|
+
"""Enables the axis motor if present, disables it otherwise."""
|
|
397
|
+
command = GCODE.ENABLE_MOTORS.build_command()
|
|
398
|
+
for a in axis:
|
|
399
|
+
command.add_element(a.name)
|
|
400
|
+
resp = await self._connection.send_command(command)
|
|
401
|
+
if not re.match(rf"^{GCODE.ENABLE_MOTORS}$", resp):
|
|
402
|
+
raise ValueError(f"Incorrect Response for enable motors: {resp}")
|
|
403
|
+
|
|
404
|
+
async def stop_motors(self) -> None:
|
|
152
405
|
"""Stop all motor movement."""
|
|
153
406
|
resp = await self._connection.send_command(GCODE.STOP_MOTORS.build_command())
|
|
154
407
|
if not re.match(rf"^{GCODE.STOP_MOTORS}$", resp):
|
|
155
408
|
raise ValueError(f"Incorrect Response for stop motors: {resp}")
|
|
156
|
-
|
|
409
|
+
|
|
410
|
+
async def set_run_current(self, axis: StackerAxis, current: float) -> None:
|
|
411
|
+
"""Set axis peak run current in amps."""
|
|
412
|
+
resp = await self._connection.send_command(
|
|
413
|
+
GCODE.SET_RUN_CURRENT.build_command().add_float(axis.name, current)
|
|
414
|
+
)
|
|
415
|
+
if not re.match(rf"^{GCODE.SET_RUN_CURRENT}$", resp):
|
|
416
|
+
raise ValueError(f"Incorrect Response for set run current: {resp}")
|
|
417
|
+
|
|
418
|
+
async def set_ihold_current(self, axis: StackerAxis, current: float) -> None:
|
|
419
|
+
"""Set axis hold current in amps."""
|
|
420
|
+
resp = await self._connection.send_command(
|
|
421
|
+
GCODE.SET_IHOLD_CURRENT.build_command().add_float(axis.name, current)
|
|
422
|
+
)
|
|
423
|
+
if not re.match(rf"^{GCODE.SET_IHOLD_CURRENT}$", resp):
|
|
424
|
+
raise ValueError(f"Incorrect Response for set ihold current: {resp}")
|
|
425
|
+
|
|
426
|
+
async def set_stallguard_threshold(
|
|
427
|
+
self, axis: StackerAxis, enable: bool, threshold: int
|
|
428
|
+
) -> None:
|
|
429
|
+
"""Enables and sets the stallguard threshold for the given axis motor."""
|
|
430
|
+
assert axis != StackerAxis.L, "Stallguard not supported for L axis"
|
|
431
|
+
if not -64 < threshold < 63:
|
|
432
|
+
raise ValueError(
|
|
433
|
+
f"Threshold value ({threshold}) should be between -64 and 63."
|
|
434
|
+
)
|
|
435
|
+
resp = await self._connection.send_command(
|
|
436
|
+
GCODE.SET_STALLGUARD.build_command()
|
|
437
|
+
.add_int(axis.name, int(enable))
|
|
438
|
+
.add_int("T", threshold)
|
|
439
|
+
)
|
|
440
|
+
if not re.match(rf"^{GCODE.SET_STALLGUARD}$", resp):
|
|
441
|
+
raise ValueError(f"Incorrect Response for set stallguard threshold: {resp}")
|
|
442
|
+
|
|
443
|
+
async def enable_tof_sensor(self, sensor: TOFSensor, enable: bool) -> None:
|
|
444
|
+
"""Enable or disable the TOF sensor."""
|
|
445
|
+
# Enabling the TOF sensor takes a while, so give extra timeout.
|
|
446
|
+
timeout = FS_TOF_TIMEOUT if enable else DEFAULT_FS_TIMEOUT
|
|
447
|
+
resp = await self._connection.send_command(
|
|
448
|
+
GCODE.ENABLE_TOF_SENSOR.build_command().add_int(sensor.name, int(enable)),
|
|
449
|
+
timeout=timeout,
|
|
450
|
+
)
|
|
451
|
+
if not re.match(rf"^{GCODE.ENABLE_TOF_SENSOR}$", resp):
|
|
452
|
+
raise ValueError(f"Incorrect Response for enable TOF sensor: {resp}")
|
|
453
|
+
|
|
454
|
+
async def set_tof_configuration(
|
|
455
|
+
self,
|
|
456
|
+
sensor: TOFSensor,
|
|
457
|
+
spad_map_id: SpadMapID,
|
|
458
|
+
active_range: Optional[ActiveRange] = None,
|
|
459
|
+
kilo_iterations: Optional[int] = None,
|
|
460
|
+
report_period_ms: Optional[int] = None,
|
|
461
|
+
histogram_dump: Optional[bool] = None,
|
|
462
|
+
) -> None:
|
|
463
|
+
"""Set the configuration of the TOF sensor.
|
|
464
|
+
|
|
465
|
+
:param sensor: The TOF sensor to configure.
|
|
466
|
+
:param spad_map_id: The pre-defined SPAD map which sets the fov and focus area (14 default).
|
|
467
|
+
:active_range: The operating mode Short-range high-accuracy (default) or long range.
|
|
468
|
+
:kilo_iterations: The Measurement iterations times 1024 (4000 default).
|
|
469
|
+
:report_period_ms: The reporting period before each measurement (500 default).
|
|
470
|
+
:histogram_dump: Enables/Disables histogram measurements (True default).
|
|
471
|
+
:return: None
|
|
472
|
+
"""
|
|
473
|
+
command = (
|
|
474
|
+
GCODE.SET_TOF_CONFIGURATION.build_command()
|
|
475
|
+
.add_element(sensor.name)
|
|
476
|
+
.add_int("I", spad_map_id.value)
|
|
477
|
+
)
|
|
478
|
+
if active_range:
|
|
479
|
+
command.add_int("A", active_range.value)
|
|
480
|
+
if kilo_iterations:
|
|
481
|
+
command.add_int("K", kilo_iterations)
|
|
482
|
+
if report_period_ms:
|
|
483
|
+
command.add_int("P", report_period_ms)
|
|
484
|
+
if histogram_dump:
|
|
485
|
+
command.add_int("H", int(histogram_dump))
|
|
486
|
+
resp = await self._connection.send_command(command)
|
|
487
|
+
if not re.match(rf"^{GCODE.SET_TOF_CONFIGURATION}$", resp):
|
|
488
|
+
raise ValueError(f"Incorrect Response for set TOF configuration: {resp}")
|
|
489
|
+
|
|
490
|
+
async def get_tof_configuration(self, sensor: TOFSensor) -> TOFConfiguration:
|
|
491
|
+
"""Get the configuration of the TOF sensor."""
|
|
492
|
+
resp = await self._connection.send_command(
|
|
493
|
+
GCODE.GET_TOF_CONFIGURATION.build_command().add_element(sensor.value)
|
|
494
|
+
)
|
|
495
|
+
return self.parse_get_tof_configuration(resp)
|
|
496
|
+
|
|
497
|
+
async def manage_tof_measurement(
|
|
498
|
+
self,
|
|
499
|
+
sensor: TOFSensor,
|
|
500
|
+
kind: MeasurementKind = MeasurementKind.HISTOGRAM,
|
|
501
|
+
start: bool = True,
|
|
502
|
+
) -> TOFMeasurement:
|
|
503
|
+
"""Start or stop Measurements from the TOF sensor."""
|
|
504
|
+
command = (
|
|
505
|
+
GCODE.MANAGE_TOF_MEASUREMENT.build_command()
|
|
506
|
+
.add_element(sensor.name)
|
|
507
|
+
.add_int("K", kind.value)
|
|
508
|
+
)
|
|
509
|
+
if not start:
|
|
510
|
+
command.add_element("C")
|
|
511
|
+
resp = await self._connection.send_command(command)
|
|
512
|
+
return self.parse_manage_tof_measurement(resp)
|
|
513
|
+
|
|
514
|
+
async def _get_tof_histogram_frame(
|
|
515
|
+
self, sensor: TOFSensor, resend: bool = False
|
|
516
|
+
) -> TOFMeasurementFrame:
|
|
517
|
+
"""Get the next measurement frame from TOF sensor or resend previous."""
|
|
518
|
+
command = GCODE.GET_TOF_MEASUREMENT.build_command().add_element(sensor.name)
|
|
519
|
+
if resend:
|
|
520
|
+
command.add_element("R")
|
|
521
|
+
resp = await self._connection.send_command(command)
|
|
522
|
+
return self.parse_get_tof_measurement(resp)
|
|
523
|
+
|
|
524
|
+
async def get_tof_histogram(self, sensor: TOFSensor) -> TOFMeasurementResult:
|
|
525
|
+
"""Get the full histogram measurement from the TOF sensor."""
|
|
526
|
+
data = []
|
|
527
|
+
data_len = 0
|
|
528
|
+
next_frame_id = 0
|
|
529
|
+
resend = False
|
|
530
|
+
retries = TOF_FRAME_RETRIES
|
|
531
|
+
|
|
532
|
+
# Cancel any ongoing measurements
|
|
533
|
+
status = await self.get_tof_sensor_status(sensor)
|
|
534
|
+
if status.state == TOFSensorState.MEASURING:
|
|
535
|
+
await self.manage_tof_measurement(sensor, start=False)
|
|
536
|
+
|
|
537
|
+
# Put the TOF sensor into histogram measurement mode
|
|
538
|
+
start = await self.manage_tof_measurement(
|
|
539
|
+
sensor, MeasurementKind.HISTOGRAM, start=True
|
|
540
|
+
)
|
|
541
|
+
# Request frames until the full histogram is transfered
|
|
542
|
+
while data_len < start.total_bytes:
|
|
543
|
+
try:
|
|
544
|
+
# Request next histogram frame
|
|
545
|
+
frame = await self._get_tof_histogram_frame(sensor, resend=resend)
|
|
546
|
+
# Validate histogram frame
|
|
547
|
+
validate_histogram_frame(frame.data, next_frame_id)
|
|
548
|
+
# append frame data
|
|
549
|
+
channel = frame.data[7:]
|
|
550
|
+
data.append(channel)
|
|
551
|
+
data_len += len(channel)
|
|
552
|
+
assert (
|
|
553
|
+
not data_len > start.total_bytes
|
|
554
|
+
), f"Invalid number of bytes, expected {start.total_bytes} got {data_len}."
|
|
555
|
+
retries = TOF_FRAME_RETRIES
|
|
556
|
+
next_frame_id += 1
|
|
557
|
+
resend = False
|
|
558
|
+
except (ValueError, NoResponse):
|
|
559
|
+
# There was a timeout or timing error, request previous frame.
|
|
560
|
+
if retries <= 0:
|
|
561
|
+
# Cancel the measurement
|
|
562
|
+
await self.manage_tof_measurement(sensor, start.kind, start=False)
|
|
563
|
+
raise RuntimeError(f"Exceded frame {next_frame_id} retries")
|
|
564
|
+
retries -= 1
|
|
565
|
+
resend = True
|
|
566
|
+
continue
|
|
567
|
+
except Exception as e:
|
|
568
|
+
# Cancel the histogram request
|
|
569
|
+
await self.manage_tof_measurement(sensor, start.kind, start=False)
|
|
570
|
+
raise RuntimeError(f"Could not get {sensor} Histogram", e)
|
|
571
|
+
|
|
572
|
+
# Parse channel bin measurements from data
|
|
573
|
+
#
|
|
574
|
+
# There are 10 channels (0 - 9), each channel is comprised of 3 * 128 bytes.
|
|
575
|
+
# The data starts with all the 128 LSB bytes, then all the 128 MID bytes, and
|
|
576
|
+
# finally all the 128 MSB bytes for all the channels as seen below.
|
|
577
|
+
#
|
|
578
|
+
# ch0 lsb = data[0], mid = data[10], msb = data[20]
|
|
579
|
+
# ch1 lsb = data[1], mid = data[11], msb = data[21]
|
|
580
|
+
# ch2 lsb = data[2], mid = data[12], msb = data[22]
|
|
581
|
+
# ...
|
|
582
|
+
# ch9 lsb = data[9], mid = data[19], msb = data[29]
|
|
583
|
+
#
|
|
584
|
+
# Each channel has 128 bins comprised of the lsb, mid, and msb payload from above.
|
|
585
|
+
# We can get the bins for each channel by combining the lsb, mid, and msb payload
|
|
586
|
+
# for that specific channel.
|
|
587
|
+
bins = {}
|
|
588
|
+
for ch in range(10): # 0-9 channels
|
|
589
|
+
# Get the lsb, mid, and msb payload for the ch
|
|
590
|
+
lsb = data[ch]
|
|
591
|
+
mid = data[ch + 10]
|
|
592
|
+
msb = data[ch + 20]
|
|
593
|
+
# combine lsb, mid, and msb bytes to generate bin count for the ch
|
|
594
|
+
bins[ch] = [
|
|
595
|
+
(msb[b] << 16) | (mid[b] << 8) | lsb[b] for b in range(NUMBER_OF_BINS)
|
|
596
|
+
]
|
|
597
|
+
return TOFMeasurementResult(start.sensor, start.kind, bins)
|
|
598
|
+
|
|
599
|
+
async def set_motor_driver_register(
|
|
600
|
+
self, axis: StackerAxis, reg: int, value: int
|
|
601
|
+
) -> None:
|
|
602
|
+
"""Set the register of the given motor axis driver to the given value."""
|
|
603
|
+
resp = await self._connection.send_command(
|
|
604
|
+
GCODE.SET_MOTOR_DRIVER_REGISTER.build_command()
|
|
605
|
+
.add_int(axis.name, reg)
|
|
606
|
+
.add_element(str(value))
|
|
607
|
+
)
|
|
608
|
+
if not re.match(rf"^{GCODE.SET_MOTOR_DRIVER_REGISTER}$", resp):
|
|
609
|
+
raise ValueError(
|
|
610
|
+
f"Incorrect Response for set motor driver register: {resp}"
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
async def get_motor_driver_register(self, axis: StackerAxis, reg: int) -> int:
|
|
614
|
+
"""Gets the register value of the given motor axis driver."""
|
|
615
|
+
response = await self._connection.send_command(
|
|
616
|
+
GCODE.GET_MOTOR_DRIVER_REGISTER.build_command().add_int(axis.name, reg)
|
|
617
|
+
)
|
|
618
|
+
return self.parse_get_motor_register(response)
|
|
619
|
+
|
|
620
|
+
async def set_tof_driver_register(
|
|
621
|
+
self, sensor: TOFSensor, reg: int, value: int
|
|
622
|
+
) -> None:
|
|
623
|
+
"""Set the register of the given tof sensor driver to the given value."""
|
|
624
|
+
resp = await self._connection.send_command(
|
|
625
|
+
GCODE.SET_TOF_DRIVER_REGISTER.build_command()
|
|
626
|
+
.add_int(sensor.name, reg)
|
|
627
|
+
.add_element(str(value))
|
|
628
|
+
)
|
|
629
|
+
if not re.match(rf"^{GCODE.SET_TOF_DRIVER_REGISTER}$", resp):
|
|
630
|
+
raise ValueError(
|
|
631
|
+
f"Incorrect Response for set tof sensor driver register: {resp}"
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
async def get_tof_driver_register(self, sensor: TOFSensor, reg: int) -> int:
|
|
635
|
+
"""Gets the register value of the given tof sensor driver."""
|
|
636
|
+
response = await self._connection.send_command(
|
|
637
|
+
GCODE.GET_TOF_DRIVER_REGISTER.build_command().add_int(sensor.name, reg)
|
|
638
|
+
)
|
|
639
|
+
return self.parse_get_tof_sensor_register(response)
|
|
640
|
+
|
|
641
|
+
async def get_tof_sensor_status(self, sensor: TOFSensor) -> TOFSensorStatus:
|
|
642
|
+
"""Get the status of the tof sensor."""
|
|
643
|
+
response = await self._connection.send_command(
|
|
644
|
+
GCODE.GET_TOF_SENSOR_STATUS.build_command().add_element(sensor.name)
|
|
645
|
+
)
|
|
646
|
+
return self.parse_tof_sensor_status(response)
|
|
647
|
+
|
|
648
|
+
async def get_motion_params(self, axis: StackerAxis) -> MoveParams:
|
|
649
|
+
"""Get the motion parameters used by the given axis motor."""
|
|
650
|
+
response = await self._connection.send_command(
|
|
651
|
+
GCODE.GET_MOVE_PARAMS.build_command().add_element(axis.name)
|
|
652
|
+
)
|
|
653
|
+
return self.parse_move_params(response)
|
|
654
|
+
|
|
655
|
+
async def get_stallguard_threshold(self, axis: StackerAxis) -> StallGuardParams:
|
|
656
|
+
"""Get the stallguard parameters by the given axis motor."""
|
|
657
|
+
response = await self._connection.send_command(
|
|
658
|
+
GCODE.GET_STALLGUARD_THRESHOLD.build_command().add_element(axis.name)
|
|
659
|
+
)
|
|
660
|
+
return self.parse_stallguard_params(response)
|
|
157
661
|
|
|
158
662
|
async def get_limit_switch(self, axis: StackerAxis, direction: Direction) -> bool:
|
|
159
663
|
"""Get limit switch status.
|
|
@@ -195,51 +699,81 @@ class FlexStackerDriver(AbstractStackerDriver):
|
|
|
195
699
|
)
|
|
196
700
|
return self.parse_door_closed(response)
|
|
197
701
|
|
|
702
|
+
async def get_installation_detected(self) -> bool:
|
|
703
|
+
"""Get whether or not installation is detected.
|
|
704
|
+
|
|
705
|
+
:return: True if installation is detected, False otherwise
|
|
706
|
+
"""
|
|
707
|
+
response = await self._connection.send_command(
|
|
708
|
+
GCODE.GET_INSTALL_DETECTED.build_command()
|
|
709
|
+
)
|
|
710
|
+
return self.parse_installation_detected(response)
|
|
711
|
+
|
|
198
712
|
async def move_in_mm(
|
|
199
713
|
self, axis: StackerAxis, distance: float, params: MoveParams | None = None
|
|
200
|
-
) ->
|
|
201
|
-
"""Move axis."""
|
|
714
|
+
) -> MoveResult:
|
|
715
|
+
"""Move axis by the given distance in mm."""
|
|
202
716
|
command = self.append_move_params(
|
|
203
717
|
GCODE.MOVE_TO.build_command().add_float(
|
|
204
718
|
axis.name, distance, GCODE_ROUNDING_PRECISION
|
|
205
719
|
),
|
|
206
720
|
params,
|
|
207
721
|
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
722
|
+
try:
|
|
723
|
+
resp = await self._connection.send_command(command, timeout=FS_MOVE_TIMEOUT)
|
|
724
|
+
if not re.match(rf"^{GCODE.MOVE_TO}$", resp):
|
|
725
|
+
raise ValueError(f"Incorrect Response for move to: {resp}")
|
|
726
|
+
except MotorStallDetected:
|
|
727
|
+
self.reset_serial_buffers()
|
|
728
|
+
return MoveResult.STALL_ERROR
|
|
729
|
+
return MoveResult.NO_ERROR
|
|
212
730
|
|
|
213
731
|
async def move_to_limit_switch(
|
|
214
732
|
self, axis: StackerAxis, direction: Direction, params: MoveParams | None = None
|
|
215
|
-
) ->
|
|
733
|
+
) -> MoveResult:
|
|
216
734
|
"""Move until limit switch is triggered."""
|
|
217
735
|
command = self.append_move_params(
|
|
218
736
|
GCODE.MOVE_TO_SWITCH.build_command().add_int(axis.name, direction.value),
|
|
219
737
|
params,
|
|
220
738
|
)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
739
|
+
try:
|
|
740
|
+
resp = await self._connection.send_command(command, timeout=FS_MOVE_TIMEOUT)
|
|
741
|
+
if not re.match(rf"^{GCODE.MOVE_TO_SWITCH}$", resp):
|
|
742
|
+
raise ValueError(f"Incorrect Response for move to switch: {resp}")
|
|
743
|
+
except MotorStallDetected:
|
|
744
|
+
self.reset_serial_buffers()
|
|
745
|
+
return MoveResult.STALL_ERROR
|
|
746
|
+
return MoveResult.NO_ERROR
|
|
747
|
+
|
|
748
|
+
async def home_axis(self, axis: StackerAxis, direction: Direction) -> MoveResult:
|
|
227
749
|
"""Home axis."""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
750
|
+
command = GCODE.HOME_AXIS.build_command().add_int(axis.name, direction.value)
|
|
751
|
+
try:
|
|
752
|
+
resp = await self._connection.send_command(command, timeout=FS_MOVE_TIMEOUT)
|
|
753
|
+
except MotorStallDetected:
|
|
754
|
+
self.reset_serial_buffers()
|
|
755
|
+
return MoveResult.STALL_ERROR
|
|
231
756
|
if not re.match(rf"^{GCODE.HOME_AXIS}$", resp):
|
|
232
757
|
raise ValueError(f"Incorrect Response for home axis: {resp}")
|
|
233
|
-
return
|
|
758
|
+
return MoveResult.NO_ERROR
|
|
234
759
|
|
|
235
760
|
async def set_led(
|
|
236
|
-
self,
|
|
237
|
-
|
|
238
|
-
|
|
761
|
+
self,
|
|
762
|
+
power: float,
|
|
763
|
+
color: Optional[LEDColor] = None,
|
|
764
|
+
external: Optional[bool] = None,
|
|
765
|
+
pattern: Optional[LEDPattern] = None,
|
|
766
|
+
duration: Optional[int] = None,
|
|
767
|
+
reps: Optional[int] = None,
|
|
768
|
+
) -> None:
|
|
769
|
+
"""Set LED Status bar color and pattern.
|
|
239
770
|
|
|
240
771
|
:param power: Power of the LED (0-1.0), 0 is off, 1 is full power
|
|
241
772
|
:param color: Color of the LED
|
|
242
773
|
:param external: True if external LED, False if internal LED
|
|
774
|
+
:param pattern: Animation pattern of the LED status bar
|
|
775
|
+
:param duration: Animation duration in milliseconds (25-10000), 10s max
|
|
776
|
+
:param reps: Number of times to repeat the animation (-1 - 10), -1 is forever.
|
|
243
777
|
"""
|
|
244
778
|
power = max(0, min(power, 1.0))
|
|
245
779
|
command = GCODE.SET_LED.build_command().add_float(
|
|
@@ -248,13 +782,25 @@ class FlexStackerDriver(AbstractStackerDriver):
|
|
|
248
782
|
if color is not None:
|
|
249
783
|
command.add_int("C", color.value)
|
|
250
784
|
if external is not None:
|
|
251
|
-
command.add_int("
|
|
785
|
+
command.add_int("K", int(external))
|
|
786
|
+
if pattern is not None:
|
|
787
|
+
command.add_int("A", pattern.value)
|
|
788
|
+
if duration is not None:
|
|
789
|
+
duration = max(MIN_DURATION_MS, min(duration, MAX_DURATION_MS))
|
|
790
|
+
command.add_int("D", duration)
|
|
791
|
+
if reps is not None:
|
|
792
|
+
command.add_int("R", max(-1, min(reps, MAX_REPS)))
|
|
252
793
|
resp = await self._connection.send_command(command)
|
|
253
794
|
if not re.match(rf"^{GCODE.SET_LED}$", resp):
|
|
254
795
|
raise ValueError(f"Incorrect Response for set led: {resp}")
|
|
255
|
-
return True
|
|
256
796
|
|
|
257
|
-
async def
|
|
258
|
-
"""
|
|
259
|
-
|
|
260
|
-
|
|
797
|
+
async def enter_programming_mode(self) -> None:
|
|
798
|
+
"""Reboot into programming mode"""
|
|
799
|
+
command = GCODE.ENTER_BOOTLOADER.build_command()
|
|
800
|
+
await self._connection.send_dfu_command(command)
|
|
801
|
+
await self._connection.close()
|
|
802
|
+
|
|
803
|
+
def reset_serial_buffers(self) -> None:
|
|
804
|
+
"""Reset the input and output serial buffers."""
|
|
805
|
+
self._connection._serial.reset_input_buffer()
|
|
806
|
+
self._connection._serial.reset_output_buffer()
|