pychemstation 0.10.0__py3-none-any.whl → 0.10.2__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.
Files changed (33) hide show
  1. pychemstation/control/README.md +132 -0
  2. pychemstation/control/controllers/README.md +1 -0
  3. {pychemstation-0.10.0.dist-info → pychemstation-0.10.2.dist-info}/METADATA +11 -9
  4. {pychemstation-0.10.0.dist-info → pychemstation-0.10.2.dist-info}/RECORD +6 -31
  5. {pychemstation-0.10.0.dist-info → pychemstation-0.10.2.dist-info}/WHEEL +1 -2
  6. pychemstation/analysis/spec_utils.py +0 -304
  7. pychemstation/analysis/utils.py +0 -63
  8. pychemstation/control/comm.py +0 -206
  9. pychemstation/control/controllers/devices/column.py +0 -12
  10. pychemstation/control/controllers/devices/dad.py +0 -0
  11. pychemstation/control/controllers/devices/pump.py +0 -43
  12. pychemstation/control/controllers/method.py +0 -338
  13. pychemstation/control/controllers/sequence.py +0 -190
  14. pychemstation/control/controllers/table_controller.py +0 -266
  15. pychemstation/control/table/__init__.py +0 -3
  16. pychemstation/control/table/method.py +0 -274
  17. pychemstation/control/table/sequence.py +0 -210
  18. pychemstation/control/table/table_controller.py +0 -201
  19. pychemstation-0.10.0.dist-info/top_level.txt +0 -2
  20. tests/__init__.py +0 -0
  21. tests/constants.py +0 -134
  22. tests/test_comb.py +0 -136
  23. tests/test_comm.py +0 -65
  24. tests/test_inj.py +0 -39
  25. tests/test_method.py +0 -99
  26. tests/test_nightly.py +0 -80
  27. tests/test_offline_stable.py +0 -69
  28. tests/test_online_stable.py +0 -275
  29. tests/test_proc_rep.py +0 -52
  30. tests/test_runs_stable.py +0 -225
  31. tests/test_sequence.py +0 -125
  32. tests/test_stable.py +0 -276
  33. {pychemstation-0.10.0.dist-info → pychemstation-0.10.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,206 +0,0 @@
1
- """
2
- Module to provide API for the communication with Agilent HPLC systems.
3
-
4
- HPLCController sends commands to Chemstation software via a command file.
5
- Answers are received via reply file. On the Chemstation side, a custom
6
- Macro monitors the command file, executes commands and writes to the reply file.
7
- Each command is given a number (cmd_no) to keep track of which commands have
8
- been processed.
9
-
10
- Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
11
- """
12
-
13
- import os
14
- import time
15
-
16
- from result import Result, Ok, Err
17
- from ..utils.macro import *
18
- from ..utils.method_types import *
19
-
20
-
21
- class CommunicationController:
22
- """
23
- Class that communicates with Agilent using Macros
24
- """
25
-
26
- # maximum command number
27
- MAX_CMD_NO = 255
28
-
29
- def __init__(
30
- self,
31
- comm_dir: str,
32
- cmd_file: str = "cmd",
33
- reply_file: str = "reply",
34
- ):
35
- """
36
- :param comm_dir:
37
- :param cmd_file: Name of command file
38
- :param reply_file: Name of reply file
39
- """
40
- if os.path.isdir(comm_dir):
41
- self.cmd_file = os.path.join(comm_dir, cmd_file)
42
- self.reply_file = os.path.join(comm_dir, reply_file)
43
- self.cmd_no = 0
44
- else:
45
- raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
46
- self._most_recent_hplc_status = None
47
-
48
- # Create files for Chemstation to communicate with Python
49
- open(self.cmd_file, "a").close()
50
- open(self.reply_file, "a").close()
51
-
52
- self.reset_cmd_counter()
53
-
54
- def get_num_val(self, cmd: str) -> Union[int, float, Err]:
55
- self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
56
- res = self.receive()
57
- if res.is_ok():
58
- return res.ok_value.num_response
59
- else:
60
- raise RuntimeError("Failed to get number.")
61
-
62
- def get_text_val(self, cmd: str) -> str:
63
- self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
64
- res = self.receive()
65
- if res.is_ok():
66
- return res.ok_value.string_response
67
- else:
68
- raise RuntimeError("Failed to get string")
69
-
70
- def get_status(self) -> Union[HPLCRunningStatus, HPLCAvailStatus, HPLCErrorStatus]:
71
- """Get device status(es).
72
-
73
- :return: list of ChemStation's current status
74
- """
75
- self.send(Command.GET_STATUS_CMD)
76
- time.sleep(1)
77
-
78
- try:
79
- parsed_response = self.receive().value.string_response
80
- self._most_recent_hplc_status = str_to_status(parsed_response)
81
- return self._most_recent_hplc_status
82
- except IOError:
83
- return HPLCErrorStatus.NORESPONSE
84
- except IndexError:
85
- return HPLCErrorStatus.MALFORMED
86
-
87
- def set_status(self):
88
- """Updates current status of HPLC machine"""
89
- self._most_recent_hplc_status = self.get_status()
90
-
91
- def check_if_running(self) -> bool:
92
- """Checks if HPLC machine is in an available state, meaning a state that data is not being written.
93
-
94
- :return: whether the HPLC machine is in a safe state to retrieve data back."""
95
- self.set_status()
96
- hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
97
- time.sleep(30)
98
- self.set_status()
99
- hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
100
- return hplc_avail and hplc_actually_avail
101
-
102
- def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
103
- """Low-level execution primitive. Sends a command string to HPLC.
104
-
105
- :param cmd: string to be sent to HPLC
106
- :param cmd_no: Command number
107
- :param num_attempts: Number of attempts to send the command before raising exception.
108
- :raises IOError: Could not write to command file.
109
- """
110
- err = None
111
- for _ in range(num_attempts):
112
- time.sleep(1)
113
- try:
114
- with open(self.cmd_file, "w", encoding="utf8") as cmd_file:
115
- cmd_file.write(f"{cmd_no} {cmd}")
116
- except IOError as e:
117
- err = e
118
- continue
119
- else:
120
- return
121
- else:
122
- raise IOError(f"Failed to send command #{cmd_no}: {cmd}.") from err
123
-
124
- def _receive(self, cmd_no: int, num_attempts=100) -> Result[str, str]:
125
- """Low-level execution primitive. Recives a response from HPLC.
126
-
127
- :param cmd_no: Command number
128
- :param num_attempts: Number of retries to open reply file
129
- :raises IOError: Could not read reply file.
130
- :return: Potential ChemStation response
131
- """
132
- err = None
133
- for _ in range(num_attempts):
134
- time.sleep(1)
135
-
136
- try:
137
- with open(self.reply_file, "r", encoding="utf_16") as reply_file:
138
- response = reply_file.read()
139
- except OSError as e:
140
- err = e
141
- continue
142
-
143
- try:
144
- first_line = response.splitlines()[0]
145
- response_no = int(first_line.split()[0])
146
- except IndexError as e:
147
- err = e
148
- continue
149
-
150
- # check that response corresponds to sent command
151
- if response_no == cmd_no:
152
- return Ok(response)
153
- else:
154
- continue
155
- else:
156
- return Err(f"Failed to receive reply to command #{cmd_no} due to {err}.")
157
-
158
- def sleepy_send(self, cmd: Union[Command, str]):
159
- self.send("Sleep 0.1")
160
- self.send(cmd)
161
- self.send("Sleep 0.1")
162
-
163
- def send(self, cmd: Union[Command, str]):
164
- """Sends a command to Chemstation.
165
-
166
- :param cmd: Command to be sent to HPLC
167
- """
168
- if self.cmd_no == self.MAX_CMD_NO:
169
- self.reset_cmd_counter()
170
-
171
- cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
172
- self.cmd_no += 1
173
- self._send(cmd_to_send, self.cmd_no)
174
- f = open("out.txt", "a")
175
- f.write(cmd_to_send + "\n")
176
- f.close()
177
-
178
- def receive(self) -> Result[Response, str]:
179
- """Returns messages received in reply file.
180
-
181
- :return: ChemStation response
182
- """
183
- num_response_prefix = "Numerical Responses:"
184
- str_response_prefix = "String Responses:"
185
- possible_response = self._receive(self.cmd_no)
186
- if Ok(possible_response):
187
- lines = possible_response.value.splitlines()
188
- for line in lines:
189
- if str_response_prefix in line and num_response_prefix in line:
190
- string_responses_dirty, _, numerical_responses = line.partition(num_response_prefix)
191
- _, _, string_responses = string_responses_dirty.partition(str_response_prefix)
192
- return Ok(Response(string_response=string_responses.strip(),
193
- num_response=float(numerical_responses.strip())))
194
- return Err(f"Could not retrieve HPLC response")
195
- else:
196
- return Err(f"Could not establish response to HPLC: {possible_response}")
197
-
198
- def reset_cmd_counter(self):
199
- """Resets the command counter."""
200
- self._send(Command.RESET_COUNTER_CMD.value, cmd_no=self.MAX_CMD_NO + 1)
201
- self._receive(cmd_no=self.MAX_CMD_NO + 1)
202
- self.cmd_no = 0
203
-
204
- def stop_macro(self):
205
- """Stops Macro execution. Connection will be lost."""
206
- self.send(Command.STOP_MACRO_CMD)
@@ -1,12 +0,0 @@
1
- from ....control.controllers import CommunicationController
2
- from .device import DeviceController
3
- from ....utils.table_types import Table
4
-
5
-
6
- class ColumnController(DeviceController):
7
-
8
- def __init__(self, controller: CommunicationController, table: Table):
9
- super().__init__(controller, table)
10
-
11
- def get_row(self, row: int):
12
- pass
File without changes
@@ -1,43 +0,0 @@
1
- from ....control.controllers import CommunicationController
2
- from .device import DeviceController
3
- from ....utils.pump_types import Pump
4
- from ....utils.table_types import Table
5
-
6
-
7
- class PumpController(DeviceController):
8
-
9
- def __init__(self, controller: CommunicationController, table: Table):
10
- super().__init__(controller, table)
11
- self.A1 = Pump(in_use=True, solvent="A1")
12
- self.B1 = Pump(in_use=True, solvent="B1")
13
- self.A2 = Pump(in_use=False, solvent="A2")
14
- self.B2 = Pump(in_use=False, solvent="B2")
15
-
16
- def validate_pumps(self):
17
- invalid_A_pump_usage = self.A1.in_use and self.A2.in_use
18
- invalid_B_pump_usage = self.B1.in_use and self.B2.in_use
19
- if invalid_A_pump_usage or invalid_B_pump_usage:
20
- raise AttributeError
21
-
22
- def switch_pump(self, num: int, pump: str):
23
- if pump == "A":
24
- if num == 1:
25
- self.A1.in_use = True
26
- self.A2.in_use = False
27
- elif num == 2:
28
- self.A1.in_use = False
29
- self.A2.in_use = True
30
- elif pump == "B":
31
- if num == 1:
32
- self.B1.in_use = True
33
- self.B2.in_use = False
34
- elif num == 2:
35
- self.B1.in_use = False
36
- self.B2.in_use = True
37
- self.purge()
38
-
39
- def purge(self):
40
- pass
41
-
42
- def get_row(self, row: int):
43
- pass
@@ -1,338 +0,0 @@
1
- import os
2
- import time
3
- from typing import Optional, Union
4
-
5
- from xsdata.formats.dataclass.parsers import XmlParser
6
-
7
- from ...control.controllers.table_controller import TableController
8
- from ...control.controllers.comm import CommunicationController
9
- from ...generated import PumpMethod, DadMethod, SolventElement
10
- from ...utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
11
- from ...utils.macro import Command
12
- from ...utils.method_types import PType, TimeTableEntry, Param, MethodDetails, HPLCMethodParams
13
- from ...utils.table_types import RegisterFlag, TableOperation, Table
14
-
15
-
16
- class MethodController(TableController):
17
- """
18
- Class containing method related logic
19
- """
20
-
21
- def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table):
22
- super().__init__(controller, src, data_dir, table)
23
-
24
- def check(self) -> str:
25
- time.sleep(2)
26
- self.send(Command.GET_METHOD_CMD)
27
- time.sleep(2)
28
- res = self.receive()
29
- if res.is_ok():
30
- return res.ok_value.string_response
31
- return "ERROR"
32
-
33
- def get_method_params(self) -> HPLCMethodParams:
34
- return HPLCMethodParams(organic_modifier=self.controller.get_num_val(
35
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
36
- register=self.table.register,
37
- register_flag=RegisterFlag.SOLVENT_B_COMPOSITION
38
- )
39
- ),
40
- flow=self.controller.get_num_val(
41
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
42
- register=self.table.register,
43
- register_flag=RegisterFlag.FLOW
44
- )
45
- ),
46
- )
47
-
48
- def get_row(self, row: int) -> TimeTableEntry:
49
- return TimeTableEntry(start_time=self.get_num(row, RegisterFlag.TIME),
50
- organic_modifer=self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION),
51
- flow=self.get_num(row, RegisterFlag.TIMETABLE_FLOW))
52
-
53
- def get_timetable(self, rows: int):
54
- uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
55
- timetable_rows = {}
56
- for row in uncoalesced_timetable_rows:
57
- time_key = str(row.start_time)
58
- if time_key not in timetable_rows.keys():
59
- timetable_rows[time_key] = TimeTableEntry(start_time=row.start_time,
60
- flow=row.flow,
61
- organic_modifer=row.organic_modifer)
62
- else:
63
- if row.flow:
64
- timetable_rows[time_key].flow = row.flow
65
- if row.organic_modifer:
66
- timetable_rows[time_key].organic_modifer = row.organic_modifer
67
- entries = list(timetable_rows.values())
68
- entries.sort(key=lambda e: e.start_time)
69
- return entries
70
-
71
- def load(self) -> MethodDetails:
72
- rows = self.get_num_rows()
73
- if rows.is_ok():
74
- self.send(Command.GET_METHOD_CMD)
75
- res = self.receive()
76
- method_name = res.ok_value.string_response
77
- timetable_rows = self.get_timetable(int(rows.ok_value.num_response))
78
- params = self.get_method_params()
79
- stop_time = self.controller.get_num_val(
80
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
81
- register=self.table.register,
82
- register_flag=RegisterFlag.MAX_TIME))
83
- post_time = self.controller.get_num_val(
84
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
85
- register=self.table.register,
86
- register_flag=RegisterFlag.POST_TIME))
87
- self.table_state = MethodDetails(
88
- name=method_name,
89
- timetable=timetable_rows,
90
- stop_time=stop_time,
91
- post_time=post_time,
92
- params=params)
93
- return self.table_state
94
- else:
95
- raise RuntimeError(rows.err_value)
96
-
97
- def current_method(self, method_name: str):
98
- """
99
- Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
100
-
101
- :param method_name: a Chemstation method
102
- :return: True if method is already loaded
103
- """
104
- self.send(Command.GET_METHOD_CMD)
105
- parsed_response = self.receive()
106
- return method_name in parsed_response
107
-
108
- def switch(self, method_name: str):
109
- """
110
- Allows the user to switch between pre-programmed methods. No need to append '.M'
111
- to the end of the method name. For example. for the method named 'General-Poroshell.M',
112
- only 'General-Poroshell' is needed.
113
-
114
- :param method_name: any available method in Chemstation method directory
115
- :raise IndexError: Response did not have expected format. Try again.
116
- :raise AssertionError: The desired method is not selected. Try again.
117
- """
118
- self.send(Command.SWITCH_METHOD_CMD.value.format(method_dir=self.src,
119
- method_name=method_name))
120
-
121
- time.sleep(2)
122
- self.send(Command.GET_METHOD_CMD)
123
- time.sleep(2)
124
- res = self.receive()
125
- if res.is_ok():
126
- parsed_response = res.ok_value.string_response
127
- assert parsed_response == f"{method_name}.M", "Switching Methods failed."
128
- self.table_state = None
129
-
130
- def load_from_disk(self, method_name: str) -> MethodDetails:
131
- """
132
- Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
133
- organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
134
- only two solvents are being used.
135
-
136
- :param method_name: name of method to load details of
137
- :raises FileNotFoundError: Method does not exist
138
- :return: method details
139
- """
140
- method_folder = f"{method_name}.M"
141
- method_path = os.path.join(self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml")
142
- dad_path = os.path.join(self.src, method_folder, "Agilent1200erDadDriver1.RapidControl.MethodXML.xml")
143
-
144
- if os.path.exists(os.path.join(self.src, f"{method_name}.M")):
145
- parser = XmlParser()
146
- method = parser.parse(method_path, PumpMethod)
147
- dad = parser.parse(dad_path, DadMethod)
148
-
149
- organic_modifier: Optional[SolventElement] = None
150
- aq_modifier: Optional[SolventElement] = None
151
-
152
- if len(method.solvent_composition.solvent_element) == 2:
153
- for solvent in method.solvent_composition.solvent_element:
154
- if solvent.channel == "Channel_A":
155
- aq_modifier = solvent
156
- elif solvent.channel == "Channel_B":
157
- organic_modifier = solvent
158
-
159
- self.table_state = MethodDetails(
160
- name=method_name,
161
- params=HPLCMethodParams(organic_modifier=organic_modifier.percentage,
162
- flow=method.flow),
163
- stop_time=method.stop_time.stop_time_value,
164
- post_time=method.post_time.post_time_value,
165
- timetable=[TimeTableEntry(start_time=tte.time,
166
- organic_modifer=tte.percent_b,
167
- flow=method.flow
168
- ) for tte in method.timetable.timetable_entry],
169
- dad_wavelengthes=dad.signals.signal)
170
- return self.table_state
171
- else:
172
- raise FileNotFoundError
173
-
174
- def edit(self, updated_method: MethodDetails, save: bool):
175
- """Updated the currently loaded method in ChemStation with provided values.
176
-
177
- :param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
178
- :param save: if false only modifies the method, otherwise saves to disk
179
- """
180
- self.table_state = updated_method
181
- initial_organic_modifier: Param = Param(val=updated_method.params.organic_modifier,
182
- chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
183
- ptype=PType.NUM)
184
- max_time: Param = Param(val=updated_method.stop_time,
185
- chemstation_key=RegisterFlag.MAX_TIME,
186
- ptype=PType.NUM)
187
- post_time: Param = Param(val=updated_method.post_time,
188
- chemstation_key=RegisterFlag.POST_TIME,
189
- ptype=PType.NUM) # TODO check postime works
190
- flow: Param = Param(val=updated_method.params.flow,
191
- chemstation_key=RegisterFlag.FLOW,
192
- ptype=PType.NUM)
193
-
194
- # Method settings required for all runs
195
- self.update_method_params(flow, initial_organic_modifier, max_time, post_time)
196
- self._update_method_timetable(updated_method.timetable)
197
-
198
- if save:
199
- self.send(Command.SAVE_METHOD_CMD.value.format(
200
- commit_msg=f"saved method at {str(time.time())}"
201
- ))
202
-
203
- def update_method_params(self, flow, initial_organic_modifier, max_time, post_time):
204
- self.delete_table()
205
- self._update_param(initial_organic_modifier)
206
- self._update_param(flow)
207
- if self.table_state.stop_time:
208
- self._update_param(Param(val="Set",
209
- chemstation_key=RegisterFlag.STOPTIME_MODE,
210
- ptype=PType.STR))
211
- self._update_param(max_time)
212
- else:
213
- self._update_param(Param(val="Off",
214
- chemstation_key=RegisterFlag.STOPTIME_MODE,
215
- ptype=PType.STR))
216
- if self.table_state.post_time:
217
- self._update_param(Param(val="Set",
218
- chemstation_key=RegisterFlag.POSTIME_MODE,
219
- ptype=PType.STR))
220
- self._update_param(post_time)
221
- self.download()
222
-
223
- def _update_param(self, method_param: Param):
224
- """Change a method parameter, changes what is visibly seen in Chemstation GUI.
225
- (changes the first row in the timetable)
226
-
227
- :param method_param: a parameter to update for currently loaded method.
228
- """
229
- register = self.table.register
230
- setting_command = TableOperation.UPDATE_OBJ_HDR_VAL if method_param.ptype == PType.NUM else TableOperation.UPDATE_OBJ_HDR_TEXT
231
- if isinstance(method_param.chemstation_key, list):
232
- for register_flag in method_param.chemstation_key:
233
- self.send(setting_command.value.format(register=register,
234
- register_flag=register_flag,
235
- val=method_param.val))
236
- else:
237
- self.send(setting_command.value.format(register=register,
238
- register_flag=method_param.chemstation_key,
239
- val=method_param.val))
240
- time.sleep(2)
241
-
242
- def download(self):
243
- self.send('Sleep 1')
244
- self.sleepy_send("DownloadRCMethod PMP1")
245
- self.send('Sleep 1')
246
-
247
- def edit_row(self, row: TimeTableEntry, first_row: bool = False):
248
- if first_row:
249
- self.add_row()
250
- self.add_new_col_text(col_name=RegisterFlag.FUNCTION,
251
- val=RegisterFlag.SOLVENT_COMPOSITION.value)
252
- self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
253
- self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
254
- val=row.organic_modifer)
255
- self.add_row()
256
- self.get_num_rows()
257
- self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
258
- self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
259
- self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
260
- self.download()
261
- else:
262
- self.add_row()
263
- self.get_num_rows()
264
- self.edit_row_text(col_name=RegisterFlag.FUNCTION,
265
- val=RegisterFlag.SOLVENT_COMPOSITION.value)
266
- self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
267
- self.edit_row_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
268
- val=row.organic_modifer)
269
- self.download()
270
-
271
- self.add_row()
272
- self.get_num_rows()
273
- self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
274
- self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
275
- self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
276
- self.download()
277
-
278
- def _update_method_timetable(self, timetable_rows: list[TimeTableEntry]):
279
- self.get_num_rows()
280
-
281
- self.delete_table()
282
- res = self.get_num_rows()
283
- while not res.is_err():
284
- self.delete_table()
285
- res = self.get_num_rows()
286
-
287
- self.new_table()
288
- self.get_num_rows()
289
-
290
- for i, row in enumerate(timetable_rows):
291
- self.edit_row(row=row, first_row=i == 0)
292
-
293
- def stop(self):
294
- """
295
- Stops the method run. A dialog window will pop up and manual intervention may be required.\
296
- """
297
- self.send(Command.STOP_METHOD_CMD)
298
-
299
- def run(self, experiment_name: str, stall_while_running: bool = True):
300
- """
301
- This is the preferred method to trigger a run.
302
- Starts the currently selected method, storing data
303
- under the <data_dir>/<experiment_name>.D folder.
304
- The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
305
- Device must be ready.
306
-
307
- :param experiment_name: Name of the experiment
308
- """
309
- if not self.table_state:
310
- self.table_state = self.load()
311
-
312
- timestamp = time.strftime(TIME_FORMAT)
313
- self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dir,
314
- experiment_name=experiment_name,
315
- timestamp=timestamp))
316
-
317
- if self.check_hplc_is_running():
318
- folder_name = f"{experiment_name}_{timestamp}.D"
319
- self.data_files.append(os.path.join(self.data_dir, folder_name))
320
-
321
- if stall_while_running:
322
- run_completed = self.check_hplc_done_running(method=self.table_state)
323
- if run_completed.is_ok():
324
- self.data_files[-1] = run_completed.value
325
- else:
326
- raise RuntimeError("Run error has occurred.")
327
- else:
328
- self.data_files[-1].dir = self.fuzzy_match_most_recent_folder(folder_name).ok_value
329
-
330
- def retrieve_recent_data_files(self) -> str:
331
- return self.data_files[-1]
332
-
333
- def get_data(self, custom_path: Optional[str] = None) -> AgilentChannelChromatogramData:
334
- if not custom_path:
335
- self.get_spectrum(self.data_files[-1])
336
- else:
337
- self.get_spectrum(custom_path)
338
- return AgilentChannelChromatogramData(**self.spectra)