pychemstation 0.8.3__py3-none-any.whl → 0.10.0__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 (55) hide show
  1. pychemstation/analysis/__init__.py +4 -0
  2. pychemstation/analysis/base_spectrum.py +9 -9
  3. pychemstation/analysis/process_report.py +13 -7
  4. pychemstation/analysis/utils.py +1 -3
  5. pychemstation/control/__init__.py +4 -0
  6. pychemstation/control/comm.py +206 -0
  7. pychemstation/control/controllers/__init__.py +6 -0
  8. pychemstation/control/controllers/comm.py +12 -5
  9. pychemstation/control/controllers/devices/column.py +12 -0
  10. pychemstation/control/controllers/devices/dad.py +0 -0
  11. pychemstation/control/controllers/devices/device.py +10 -7
  12. pychemstation/control/controllers/devices/injector.py +18 -84
  13. pychemstation/control/controllers/devices/pump.py +43 -0
  14. pychemstation/control/controllers/method.py +338 -0
  15. pychemstation/control/controllers/sequence.py +190 -0
  16. pychemstation/control/controllers/table_controller.py +266 -0
  17. pychemstation/control/controllers/tables/method.py +35 -13
  18. pychemstation/control/controllers/tables/sequence.py +46 -37
  19. pychemstation/control/controllers/tables/table.py +46 -30
  20. pychemstation/control/hplc.py +27 -11
  21. pychemstation/control/table/__init__.py +3 -0
  22. pychemstation/control/table/method.py +274 -0
  23. pychemstation/control/table/sequence.py +210 -0
  24. pychemstation/control/table/table_controller.py +201 -0
  25. pychemstation/generated/dad_method.py +1 -1
  26. pychemstation/generated/pump_method.py +1 -1
  27. pychemstation/utils/chromatogram.py +2 -5
  28. pychemstation/utils/injector_types.py +1 -1
  29. pychemstation/utils/macro.py +3 -3
  30. pychemstation/utils/method_types.py +2 -2
  31. pychemstation/utils/num_utils.py +65 -0
  32. pychemstation/utils/parsing.py +1 -0
  33. pychemstation/utils/sequence_types.py +3 -3
  34. pychemstation/utils/spec_utils.py +304 -0
  35. {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/METADATA +19 -8
  36. pychemstation-0.10.0.dist-info/RECORD +62 -0
  37. {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/WHEEL +2 -1
  38. pychemstation-0.10.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +0 -0
  40. tests/constants.py +134 -0
  41. tests/test_comb.py +136 -0
  42. tests/test_comm.py +65 -0
  43. tests/test_inj.py +39 -0
  44. tests/test_method.py +99 -0
  45. tests/test_nightly.py +80 -0
  46. tests/test_offline_stable.py +69 -0
  47. tests/test_online_stable.py +275 -0
  48. tests/test_proc_rep.py +52 -0
  49. tests/test_runs_stable.py +225 -0
  50. tests/test_sequence.py +125 -0
  51. tests/test_stable.py +276 -0
  52. pychemstation/control/README.md +0 -124
  53. pychemstation/control/controllers/README.md +0 -1
  54. pychemstation-0.8.3.dist-info/RECORD +0 -37
  55. {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,24 +3,34 @@ Abstract module containing shared logic for Method and Sequence tables.
3
3
 
4
4
  Authors: Lucy Hao
5
5
  """
6
+ from __future__ import annotations
6
7
 
7
8
  import abc
8
9
  import math
9
10
  import os
10
11
  import time
11
- from typing import Union, Optional, List, Tuple, Dict
12
+ import warnings
13
+ from typing import Dict, List, Optional, Tuple, Union
12
14
 
13
15
  import polling
14
16
  import rainbow as rb
15
- from result import Result, Err
16
-
17
- from ....analysis.process_report import AgilentReport, ReportType, CSVProcessor, TXTProcessor
17
+ from result import Err, Result, Ok
18
+
19
+ from ....analysis.process_report import (
20
+ AgilentReport,
21
+ CSVProcessor,
22
+ ReportType,
23
+ TXTProcessor,
24
+ )
18
25
  from ....control.controllers.comm import CommunicationController
19
- from ....utils.chromatogram import AgilentHPLCChromatogram, AgilentChannelChromatogramData
26
+ from ....utils.chromatogram import (
27
+ AgilentChannelChromatogramData,
28
+ AgilentHPLCChromatogram,
29
+ )
20
30
  from ....utils.macro import Command, HPLCRunningStatus, Response
21
31
  from ....utils.method_types import MethodDetails
22
32
  from ....utils.sequence_types import SequenceDataFiles, SequenceTable
23
- from ....utils.table_types import Table, TableOperation, RegisterFlag, T
33
+ from ....utils.table_types import RegisterFlag, T, Table, TableOperation
24
34
 
25
35
  TableType = Union[MethodDetails, SequenceTable]
26
36
 
@@ -200,21 +210,25 @@ class TableController(abc.ABC):
200
210
  return started_running
201
211
 
202
212
  def check_hplc_run_finished(self) -> Tuple[float, bool]:
203
- time_passed = (time.time() - self.curr_run_starting_time)
204
- if time_passed > self.timeout:
205
- done_running = self.controller.check_if_not_running()
206
- enough_time_passed = time_passed >= self.timeout
207
- run_finished = enough_time_passed and done_running
208
- if run_finished:
209
- self._reset_time()
210
- return 0, run_finished
211
- return (time_passed / self.timeout), self.controller.check_if_not_running()
212
-
213
- def check_hplc_done_running(self) -> Result[Union[SequenceDataFiles, str], str]:
213
+ done_running = self.controller.check_if_not_running()
214
+ if self.curr_run_starting_time and self.timeout:
215
+ time_passed = (time.time() - self.curr_run_starting_time)
216
+ if time_passed > self.timeout:
217
+ enough_time_passed = time_passed >= self.timeout
218
+ run_finished = enough_time_passed and done_running
219
+ if run_finished:
220
+ self._reset_time()
221
+ return 0, run_finished
222
+ else:
223
+ time_left = self.timeout - time_passed
224
+ return time_left, self.controller.check_if_not_running()
225
+ return 0, self.controller.check_if_not_running()
226
+
227
+ def check_hplc_done_running(self) -> Ok[T] | Err[str]:
214
228
  """
215
229
  Checks if ChemStation has finished running and can read data back
216
230
 
217
- :return: Return True if data can be read back, else False.
231
+ :return: Data file object containing most recent run file information.
218
232
  """
219
233
  finished_run = False
220
234
  minutes = math.ceil(self.timeout / 60)
@@ -244,16 +258,18 @@ class TableController(abc.ABC):
244
258
  except Exception:
245
259
  self._reset_time()
246
260
  return check_folder
247
-
248
- else:
249
- return Err("Run did not complete as expected")
261
+ return Err("Run did not complete as expected")
250
262
 
251
263
  @abc.abstractmethod
252
264
  def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
253
265
  pass
254
266
 
255
267
  @abc.abstractmethod
256
- def get_data(self) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
268
+ def get_data(self, custom_path: Optional[str] = None) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
269
+ pass
270
+
271
+ @abc.abstractmethod
272
+ def get_data_uv(self) -> Union[List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]]:
257
273
  pass
258
274
 
259
275
  @abc.abstractmethod
@@ -263,11 +279,11 @@ class TableController(abc.ABC):
263
279
  def get_uv_spectrum(self, path: str):
264
280
  data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
265
281
  times = data_uv.xlabels
266
- wavelengthes = data_uv.ylabels
267
- data = data_uv.data.transpose()
268
- for (i, w) in enumerate(wavelengthes):
282
+ wavelengths = data_uv.ylabels
283
+ absorbances = data_uv.data.transpose()
284
+ for (i, w) in enumerate(wavelengths):
269
285
  self.uv[w] = AgilentHPLCChromatogram()
270
- self.uv[w].attach_spectrum(times, data[i])
286
+ self.uv[w].attach_spectrum(times, absorbances[i])
271
287
 
272
288
  def get_report_details(self, path: str,
273
289
  report_type: ReportType = ReportType.TXT) -> AgilentReport:
@@ -281,18 +297,18 @@ class TableController(abc.ABC):
281
297
  self.report = csv_report.ok_value
282
298
  return self.report
283
299
 
284
- def get_spectrum(self, data_path: str, read_uv: bool = False):
300
+
301
+ def get_spectrum_at_channels(self, data_path: str):
285
302
  """
286
303
  Load chromatogram for any channel in spectra dictionary.
287
304
  """
288
- if read_uv:
289
- self.get_uv_spectrum(data_path)
290
305
  for channel, spec in self.spectra.items():
291
306
  try:
292
307
  spec.load_spectrum(data_path=data_path, channel=channel)
293
308
  except FileNotFoundError:
294
309
  self.spectra[channel] = AgilentHPLCChromatogram()
295
- print(f"No data at channel: {channel}")
310
+ warning = f"No data at channel: {channel}"
311
+ warnings.warn(warning)
296
312
 
297
313
  def _reset_time(self):
298
314
  self.curr_run_starting_time = None
@@ -3,12 +3,20 @@ Module to provide API for higher-level HPLC actions.
3
3
 
4
4
  Authors: Lucy Hao
5
5
  """
6
+ from __future__ import annotations
6
7
 
7
- from typing import Union, Optional, List, Tuple
8
+ from typing import Dict, List, Optional, Tuple, Union
8
9
 
10
+ from pychemstation.utils.chromatogram import (
11
+ AgilentHPLCChromatogram,
12
+ )
9
13
  from .controllers.devices.injector import InjectorController
10
- from ..analysis.process_report import ReportType, AgilentReport
11
- from ..control.controllers import MethodController, SequenceController, CommunicationController
14
+ from ..analysis.process_report import AgilentReport, ReportType
15
+ from ..control.controllers import (
16
+ CommunicationController,
17
+ MethodController,
18
+ SequenceController,
19
+ )
12
20
  from ..utils.chromatogram import AgilentChannelChromatogramData
13
21
  from ..utils.injector_types import InjectorTable
14
22
  from ..utils.macro import Command, Response, Status
@@ -44,7 +52,8 @@ class HPLCController:
44
52
  method_dir: str,
45
53
  sequence_dir: str,
46
54
  data_dirs: List[str],
47
- offline: bool = False):
55
+ offline: bool = False,
56
+ debug: bool = False,):
48
57
  """Initialize HPLC controller. The `hplc_talk.mac` macro file must be loaded in the Chemstation software.
49
58
  `comm_dir` must match the file path in the macro file. All file paths are normal strings, with the left slash
50
59
  double escaped: "C:\\my_folder\\"
@@ -56,7 +65,7 @@ class HPLCController:
56
65
  :param sequence_dir: Name of directory where sequence files are stored.
57
66
  :raises FileNotFoundError: If either `data_dir`, `method_dir`, `sequence_dir`, `sequence_data_dir`or `comm_dir` is not a valid directory.
58
67
  """
59
- self.comm = CommunicationController(comm_dir=comm_dir) if not offline else None
68
+ self.comm = CommunicationController(comm_dir=comm_dir, debug=debug) if not offline else None
60
69
  self.method_controller = MethodController(controller=self.comm,
61
70
  src=method_dir,
62
71
  data_dirs=data_dirs,
@@ -200,14 +209,17 @@ class HPLCController:
200
209
  return self.method_controller.get_report(custom_path=custom_path, report_type=report_type)[0]
201
210
 
202
211
  def get_last_run_method_data(self, read_uv: bool = False,
203
- data: Optional[str] = None) -> AgilentChannelChromatogramData:
212
+ custom_path: Optional[str] = None) -> Dict[str, AgilentHPLCChromatogram] | AgilentChannelChromatogramData:
204
213
  """
205
214
  Returns the last run method data.
206
215
 
207
- :param data: If you want to just load method data but from a file path. This file path must be the complete file path.
216
+ :param custom_path: If you want to just load method data but from a file path. This file path must be the complete file path.
208
217
  :param read_uv: whether to also read the UV file
209
218
  """
210
- return self.method_controller.get_data(custom_path=data, read_uv=read_uv)
219
+ if read_uv:
220
+ return self.method_controller.get_data_uv(custom_path=custom_path)
221
+ else:
222
+ return self.method_controller.get_data(custom_path=custom_path)
211
223
 
212
224
  def get_last_run_sequence_reports(self,
213
225
  custom_path: Optional[str] = None,
@@ -221,14 +233,18 @@ class HPLCController:
221
233
  return self.sequence_controller.get_report(custom_path=custom_path, report_type=report_type)
222
234
 
223
235
  def get_last_run_sequence_data(self, read_uv: bool = False,
224
- data: Optional[str] = None) -> List[AgilentChannelChromatogramData]:
236
+ custom_path: Optional[str] = None) -> List[Dict[str, AgilentHPLCChromatogram]] | \
237
+ List[AgilentChannelChromatogramData]:
225
238
  """
226
239
  Returns data for all rows in the last run sequence data.
227
240
 
228
- :param data: If you want to just load sequence data but from a file path. This file path must be the complete file path.
241
+ :param custom_path: If you want to just load sequence data but from a file path. This file path must be the complete file path.
229
242
  :param read_uv: whether to also read the UV file
230
243
  """
231
- return self.sequence_controller.get_data(custom_path=data, read_uv=read_uv)
244
+ if read_uv:
245
+ return self.sequence_controller.get_data_uv(custom_path=custom_path)
246
+ else:
247
+ return self.sequence_controller.get_data(custom_path=custom_path)
232
248
 
233
249
  def check_loaded_sequence(self) -> str:
234
250
  """Returns the name of the currently loaded sequence."""
@@ -0,0 +1,3 @@
1
+ from .method import MethodController
2
+ from .sequence import SequenceController
3
+ from .table_controller import TableController
@@ -0,0 +1,274 @@
1
+ import os
2
+ import time
3
+ from typing import Optional, Any
4
+
5
+ from xsdata.formats.dataclass.parsers import XmlParser
6
+
7
+ from .. import CommunicationController
8
+ from ...control.table.table_controller import TableController
9
+ from ...generated import PumpMethod, DadMethod, SolventElement
10
+ from ...utils.chromatogram import TIME_FORMAT, AgilentHPLCChromatogram
11
+ from ...utils.macro import Command
12
+ from ...utils.method_types import PType, TimeTableEntry, Param, MethodTimetable, 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 get_method_params(self) -> HPLCMethodParams:
25
+ return HPLCMethodParams(organic_modifier=self.controller.get_num_val(
26
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
27
+ register=self.table.register,
28
+ register_flag=RegisterFlag.SOLVENT_B_COMPOSITION
29
+ )
30
+ ),
31
+ flow=self.controller.get_num_val(
32
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
33
+ register=self.table.register,
34
+ register_flag=RegisterFlag.FLOW
35
+ )
36
+ ),
37
+ maximum_run_time=self.controller.get_num_val(
38
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
39
+ register=self.table.register,
40
+ register_flag=RegisterFlag.MAX_TIME
41
+ )
42
+ ),
43
+ )
44
+
45
+ def get_row(self, row: int) -> TimeTableEntry:
46
+ return TimeTableEntry(start_time=self.get_num(row, RegisterFlag.TIME),
47
+ organic_modifer=self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION),
48
+ flow=None)
49
+
50
+ def load(self) -> MethodTimetable:
51
+ rows = self.get_num_rows()
52
+ if rows.is_ok():
53
+ timetable_rows = [self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))]
54
+ return MethodTimetable(
55
+ first_row=self.get_method_params(),
56
+ subsequent_rows=timetable_rows)
57
+ else:
58
+ raise RuntimeError(rows.err_value)
59
+
60
+ def current_method(self, method_name: str):
61
+ """
62
+ Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
63
+
64
+ :param method_name: a Chemstation method
65
+ :return: True if method is already loaded
66
+ """
67
+ self.send(Command.GET_METHOD_CMD)
68
+ parsed_response = self.receive()
69
+ return method_name in parsed_response
70
+
71
+ def switch(self, method_name: str):
72
+ """
73
+ Allows the user to switch between pre-programmed methods. No need to append '.M'
74
+ to the end of the method name. For example. for the method named 'General-Poroshell.M',
75
+ only 'General-Poroshell' is needed.
76
+
77
+ :param method_name: any available method in Chemstation method directory
78
+ :raise IndexError: Response did not have expected format. Try again.
79
+ :raise AssertionError: The desired method is not selected. Try again.
80
+ """
81
+ self.send(Command.SWITCH_METHOD_CMD.value.format(method_dir=self.src,
82
+ method_name=method_name))
83
+
84
+ time.sleep(2)
85
+ self.send(Command.GET_METHOD_CMD)
86
+ time.sleep(2)
87
+ res = self.receive()
88
+ if res.is_ok():
89
+ parsed_response = res.ok_value.string_response
90
+ assert parsed_response == f"{method_name}.M", "Switching Methods failed."
91
+
92
+ def load_from_disk(self, method_name: str) -> MethodTimetable:
93
+ """
94
+ Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
95
+ organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
96
+ only two solvents are being used.
97
+
98
+ :param method_name: name of method to load details of
99
+ :raises FileNotFoundError: Method does not exist
100
+ :return: method details
101
+ """
102
+ method_folder = f"{method_name}.M"
103
+ method_path = os.path.join(self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml")
104
+ dad_path = os.path.join(self.src, method_folder, "Agilent1200erDadDriver1.RapidControl.MethodXML.xml")
105
+
106
+ if os.path.exists(os.path.join(self.src, f"{method_name}.M")):
107
+ parser = XmlParser()
108
+ method = parser.parse(method_path, PumpMethod)
109
+ dad = parser.parse(dad_path, DadMethod)
110
+
111
+ organic_modifier: Optional[SolventElement] = None
112
+ aq_modifier: Optional[SolventElement] = None
113
+
114
+ if len(method.solvent_composition.solvent_element) == 2:
115
+ for solvent in method.solvent_composition.solvent_element:
116
+ if solvent.channel == "Channel_A":
117
+ aq_modifier = solvent
118
+ elif solvent.channel == "Channel_B":
119
+ organic_modifier = solvent
120
+
121
+ return MethodTimetable(
122
+ first_row=HPLCMethodParams(
123
+ organic_modifier=organic_modifier.percentage,
124
+ flow=method.flow,
125
+ maximum_run_time=method.stop_time,
126
+ temperature=-1),
127
+ subsequent_rows=[
128
+ TimeTableEntry(
129
+ start_time=tte.time,
130
+ organic_modifer=tte.percent_b,
131
+ flow=method.flow
132
+ ) for tte in method.timetable.timetable_entry
133
+ ],
134
+ dad_wavelengthes=dad.signals.signal,
135
+ organic_modifier=organic_modifier,
136
+ modifier_a=aq_modifier
137
+ )
138
+ else:
139
+ raise FileNotFoundError
140
+
141
+ def edit(self, updated_method: MethodTimetable, save: bool):
142
+ """Updated the currently loaded method in ChemStation with provided values.
143
+
144
+ :param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
145
+ """
146
+ initial_organic_modifier: Param = Param(val=updated_method.first_row.organic_modifier,
147
+ chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
148
+ ptype=PType.NUM)
149
+ max_time: Param = Param(val=updated_method.first_row.maximum_run_time,
150
+ chemstation_key=RegisterFlag.MAX_TIME,
151
+ ptype=PType.NUM)
152
+ flow: Param = Param(val=updated_method.first_row.flow,
153
+ chemstation_key=RegisterFlag.FLOW,
154
+ ptype=PType.NUM)
155
+
156
+ # Method settings required for all runs
157
+ self.delete_table()
158
+ self._update_param(initial_organic_modifier)
159
+ self._update_param(flow)
160
+ self._update_param(Param(val="Set",
161
+ chemstation_key=RegisterFlag.STOPTIME_MODE,
162
+ ptype=PType.STR))
163
+ self._update_param(max_time)
164
+ self._update_param(Param(val="Off",
165
+ chemstation_key=RegisterFlag.POSTIME_MODE,
166
+ ptype=PType.STR))
167
+
168
+ self.send("DownloadRCMethod PMP1")
169
+
170
+ self._update_method_timetable(updated_method.subsequent_rows)
171
+
172
+ if save:
173
+ self.send(Command.SAVE_METHOD_CMD.value.format(
174
+ commit_msg=f"saved method at {str(time.time())}"
175
+ ))
176
+
177
+ def _update_method_timetable(self, timetable_rows: list[TimeTableEntry]):
178
+ self.sleepy_send('Local Rows')
179
+ self.get_num_rows()
180
+
181
+ self.sleepy_send('DelTab RCPMP1Method[1], "Timetable"')
182
+ res = self.get_num_rows()
183
+ while not res.is_err():
184
+ self.sleepy_send('DelTab RCPMP1Method[1], "Timetable"')
185
+ res = self.get_num_rows()
186
+
187
+ self.sleepy_send('NewTab RCPMP1Method[1], "Timetable"')
188
+ self.get_num_rows()
189
+
190
+ for i, row in enumerate(timetable_rows):
191
+ if i == 0:
192
+ self.send('Sleep 1')
193
+ self.sleepy_send('InsTabRow RCPMP1Method[1], "Timetable"')
194
+ self.send('Sleep 1')
195
+
196
+ self.sleepy_send('NewColText RCPMP1Method[1], "Timetable", "Function", "SolventComposition"')
197
+ self.sleepy_send(f'NewColVal RCPMP1Method[1], "Timetable", "Time", {row.start_time}')
198
+ self.sleepy_send(
199
+ f'NewColVal RCPMP1Method[1], "Timetable", "SolventCompositionPumpChannel2_Percentage", {row.organic_modifer}')
200
+
201
+ self.send('Sleep 1')
202
+ self.sleepy_send("DownloadRCMethod PMP1")
203
+ self.send('Sleep 1')
204
+ else:
205
+ self.sleepy_send('InsTabRow RCPMP1Method[1], "Timetable"')
206
+ self.get_num_rows()
207
+
208
+ self.sleepy_send(
209
+ f'SetTabText RCPMP1Method[1], "Timetable", Rows, "Function", "SolventComposition"')
210
+ self.sleepy_send(
211
+ f'SetTabVal RCPMP1Method[1], "Timetable", Rows, "Time", {row.start_time}')
212
+ self.sleepy_send(
213
+ f'SetTabVal RCPMP1Method[1], "Timetable", Rows, "SolventCompositionPumpChannel2_Percentage", {row.organic_modifer}')
214
+
215
+ self.send("Sleep 1")
216
+ self.sleepy_send("DownloadRCMethod PMP1")
217
+ self.send("Sleep 1")
218
+ self.get_num_rows()
219
+
220
+ def _update_param(self, method_param: Param):
221
+ """Change a method parameter, changes what is visibly seen in Chemstation GUI.
222
+ (changes the first row in the timetable)
223
+
224
+ :param method_param: a parameter to update for currently loaded method.
225
+ """
226
+ register = self.table.register
227
+ setting_command = TableOperation.UPDATE_OBJ_HDR_VAL if method_param.ptype == PType.NUM else TableOperation.UPDATE_OBJ_HDR_TEXT
228
+ if isinstance(method_param.chemstation_key, list):
229
+ for register_flag in method_param.chemstation_key:
230
+ self.send(setting_command.value.format(register=register,
231
+ register_flag=register_flag,
232
+ val=method_param.val))
233
+ else:
234
+ self.send(setting_command.value.format(register=register,
235
+ register_flag=method_param.chemstation_key,
236
+ val=method_param.val))
237
+ time.sleep(2)
238
+
239
+ def stop(self):
240
+ """
241
+ Stops the method run. A dialog window will pop up and manual intervention may be required.\
242
+ """
243
+ self.send(Command.STOP_METHOD_CMD)
244
+
245
+ def run(self, experiment_name: str):
246
+ """
247
+ This is the preferred method to trigger a run.
248
+ Starts the currently selected method, storing data
249
+ under the <data_dir>/<experiment_name>.D folder.
250
+ The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
251
+ Device must be ready.
252
+
253
+ :param experiment_name: Name of the experiment
254
+ """
255
+ timestamp = time.strftime(TIME_FORMAT)
256
+ self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dir,
257
+ experiment_name=experiment_name,
258
+ timestamp=timestamp))
259
+
260
+ if self.check_hplc_is_running():
261
+ folder_name = f"{experiment_name}_{timestamp}.D"
262
+ self.data_files.append(os.path.join(self.data_dir, folder_name))
263
+
264
+ run_completed = self.check_hplc_done_running()
265
+
266
+ if not run_completed.is_ok():
267
+ raise RuntimeError("Run did not complete as expected")
268
+
269
+ def retrieve_recent_data_files(self) -> str:
270
+ return self.data_files[-1]
271
+
272
+ def get_data(self) -> dict[str, AgilentHPLCChromatogram]:
273
+ self.get_spectrum(self.data_files[-1])
274
+ return self.spectra