pychemstation 0.10.5__py3-none-any.whl → 0.10.7__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 (29) hide show
  1. pychemstation/analysis/__init__.py +8 -1
  2. pychemstation/control/README.md +1 -1
  3. pychemstation/control/controllers/__init__.py +2 -2
  4. pychemstation/control/controllers/abc_tables/__init__.py +0 -0
  5. pychemstation/control/controllers/abc_tables/abc_comm.py +155 -0
  6. pychemstation/control/controllers/abc_tables/device.py +20 -0
  7. pychemstation/control/controllers/{tables/table.py → abc_tables/run.py} +58 -201
  8. pychemstation/control/controllers/abc_tables/table.py +230 -0
  9. pychemstation/control/controllers/comm.py +26 -101
  10. pychemstation/control/controllers/{tables → data_aq}/method.py +12 -15
  11. pychemstation/control/controllers/{tables → data_aq}/sequence.py +168 -119
  12. pychemstation/control/controllers/devices/__init__.py +3 -0
  13. pychemstation/control/controllers/devices/injector.py +61 -28
  14. pychemstation/control/hplc.py +42 -26
  15. pychemstation/utils/injector_types.py +22 -2
  16. pychemstation/utils/macro.py +11 -0
  17. pychemstation/utils/mocking/__init__.py +0 -0
  18. pychemstation/utils/mocking/mock_comm.py +5 -0
  19. pychemstation/utils/mocking/mock_hplc.py +2 -0
  20. pychemstation/utils/sequence_types.py +22 -2
  21. pychemstation/utils/table_types.py +6 -0
  22. pychemstation/utils/tray_types.py +36 -1
  23. {pychemstation-0.10.5.dist-info → pychemstation-0.10.7.dist-info}/METADATA +3 -3
  24. pychemstation-0.10.7.dist-info/RECORD +42 -0
  25. pychemstation/control/controllers/devices/device.py +0 -74
  26. pychemstation-0.10.5.dist-info/RECORD +0 -36
  27. /pychemstation/control/controllers/{tables → data_aq}/__init__.py +0 -0
  28. {pychemstation-0.10.5.dist-info → pychemstation-0.10.7.dist-info}/WHEEL +0 -0
  29. {pychemstation-0.10.5.dist-info → pychemstation-0.10.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,15 @@
1
1
  import os
2
2
  import time
3
3
  import warnings
4
- from typing import Dict, List, Optional, Any
4
+ from typing import Any, Dict, List, Optional, Union, Set
5
5
 
6
6
  from result import Err, Ok, Result
7
7
  from typing_extensions import override
8
8
 
9
9
  from pychemstation.analysis.chromatogram import (
10
- SEQUENCE_TIME_FORMAT,
11
10
  AgilentChannelChromatogramData,
12
11
  AgilentHPLCChromatogram,
13
12
  )
14
- from . import MethodController
15
13
 
16
14
  from ....analysis.process_report import AgilentReport, ReportType
17
15
  from ....control.controllers.comm import CommunicationController
@@ -25,10 +23,11 @@ from ....utils.sequence_types import (
25
23
  )
26
24
  from ....utils.table_types import RegisterFlag, T, Table
27
25
  from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn, Tray
28
- from .table import TableController
26
+ from ..abc_tables.run import RunController
27
+ from . import MethodController
29
28
 
30
29
 
31
- class SequenceController(TableController):
30
+ class SequenceController(RunController):
32
31
  """
33
32
  Class containing sequence related logic
34
33
  """
@@ -92,6 +91,7 @@ class SequenceController(TableController):
92
91
  def get_row(self, row: int) -> SequenceEntry:
93
92
  sample_name = self.get_text(row, RegisterFlag.NAME)
94
93
  vial_location = self.try_int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
94
+ data_file = self.get_text(row, RegisterFlag.DATA_FILE)
95
95
  method = self.get_text(row, RegisterFlag.METHOD)
96
96
  num_inj = self.try_int(self.get_num(row, RegisterFlag.NUM_INJ))
97
97
  inj_vol = self.try_float(self.get_text(row, RegisterFlag.INJ_VOL))
@@ -106,6 +106,7 @@ class SequenceController(TableController):
106
106
  inj_vol=inj_vol,
107
107
  inj_source=inj_source,
108
108
  sample_type=sample_type,
109
+ data_file=data_file,
109
110
  )
110
111
 
111
112
  def check(self) -> str:
@@ -127,13 +128,17 @@ class SequenceController(TableController):
127
128
  self.send(f'_SeqPath$ = "{self.src}"')
128
129
  self.send(Command.SWITCH_SEQUENCE_CMD)
129
130
  time.sleep(2)
130
- self.send(Command.GET_SEQUENCE_CMD)
131
- time.sleep(2)
132
- parsed_response = self.receive().ok_value.string_response
131
+ parsed_response = self.get_current_sequence_name()
133
132
 
134
133
  assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
135
134
  self.table_state = self.load()
136
135
 
136
+ def get_current_sequence_name(self):
137
+ self.send(Command.GET_SEQUENCE_CMD)
138
+ time.sleep(2)
139
+ parsed_response = self.receive().ok_value.string_response
140
+ return parsed_response
141
+
137
142
  def edit(self, sequence_table: SequenceTable):
138
143
  """
139
144
  Updates the currently loaded sequence table with the provided table. This method will delete the existing sequence table and remake it.
@@ -146,19 +151,18 @@ class SequenceController(TableController):
146
151
  if rows.is_ok():
147
152
  existing_row_num = rows.ok_value.num_response
148
153
  wanted_row_num = len(sequence_table.rows)
149
- while existing_row_num != wanted_row_num:
150
- if wanted_row_num > existing_row_num:
151
- self.add_row()
152
- elif wanted_row_num < existing_row_num:
153
- self.delete_row(int(existing_row_num))
154
+ for i in range(int(existing_row_num)):
155
+ self.delete_row(int(existing_row_num - i))
154
156
  self.send(Command.SAVE_SEQUENCE_CMD)
155
- existing_row_num = self.get_num_rows().ok_value.num_response
157
+ for i in range(int(wanted_row_num)):
158
+ self.add_row()
159
+ self.save()
156
160
  self.send(Command.SWITCH_SEQUENCE_CMD)
157
161
 
158
162
  for i, row in enumerate(sequence_table.rows):
159
163
  self._edit_row(row=row, row_num=i + 1)
160
164
  self.sleep(1)
161
- self.send(Command.SAVE_SEQUENCE_CMD)
165
+ self.save()
162
166
  self.send(Command.SWITCH_SEQUENCE_CMD)
163
167
 
164
168
  def _edit_row(self, row: SequenceEntry, row_num: int):
@@ -172,56 +176,90 @@ class SequenceController(TableController):
172
176
  if num_rows.is_ok():
173
177
  while num_rows.ok_value.num_response < row_num:
174
178
  self.add_row()
175
- self.send(Command.SAVE_SEQUENCE_CMD)
179
+ self.save()
176
180
  num_rows = self.get_num_rows()
177
181
  if row.vial_location:
178
- loc = row.vial_location
179
- loc_num = -1
180
- if isinstance(loc, TenVialColumn):
181
- loc_num = loc.value
182
- elif isinstance(loc, FiftyFourVialPlate):
183
- loc_num = loc.value()
184
- self._edit_row_num(
185
- row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
186
- )
182
+ self.edit_vial_location(row.vial_location, row_num, save=False)
187
183
  if row.method:
188
- method_dir = self.method_controller.src
189
- possible_path = os.path.join(method_dir, row.method) + ".M\\"
190
- method = row.method
191
- if os.path.exists(possible_path):
192
- method = os.path.join(method_dir, row.method)
193
- self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
184
+ self.edit_method_name(row.method, row_num, save=False)
194
185
  if row.num_inj:
195
- self._edit_row_num(
196
- row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj
197
- )
186
+ self.edit_num_injections(row.num_inj, row_num, save=False)
198
187
  if row.inj_vol:
199
- self._edit_row_text(
200
- row=row_num, col_name=RegisterFlag.INJ_VOL, val=str(row.inj_vol)
201
- )
188
+ self.edit_injection_volume(row.inj_vol, row_num, save=False)
202
189
  if row.inj_source:
203
- self._edit_row_text(
204
- row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value
205
- )
190
+ self.edit_injection_source(row.inj_source, row_num, save=False)
206
191
  if row.sample_name:
207
- self._edit_row_text(
208
- row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name
209
- )
210
- if row.data_file:
211
- self._edit_row_text(
212
- row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file
213
- )
214
- else:
215
- self._edit_row_text(
216
- row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name
217
- )
192
+ self.edit_sample_name(row.sample_name, row_num, save=False)
193
+ if row.data_file:
194
+ self.edit_data_file(row.data_file, row_num, save=False)
218
195
  if row.sample_type:
219
- self._edit_row_num(
220
- row=row_num,
221
- col_name=RegisterFlag.SAMPLE_TYPE,
222
- val=row.sample_type.value,
223
- )
196
+ self.edit_sample_type(row.sample_type, row_num, save=False)
197
+ self.save()
198
+
199
+ def edit_sample_type(
200
+ self, sample_type: SampleType, row_num: int, save: bool = True
201
+ ):
202
+ self._edit_row_num(
203
+ row=row_num,
204
+ col_name=RegisterFlag.SAMPLE_TYPE,
205
+ val=sample_type.value,
206
+ )
207
+ if save:
208
+ self.save()
209
+
210
+ def edit_data_file(self, data_file: str, row_num: int, save: bool = True):
211
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=data_file)
212
+ if save:
213
+ self.save()
214
+
215
+ def edit_sample_name(self, sample_name: str, row_num: int, save: bool = True):
216
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=sample_name)
217
+ if save:
218
+ self.save()
224
219
 
220
+ def edit_injection_source(
221
+ self, inj_source: InjectionSource, row_num: int, save: bool = True
222
+ ):
223
+ self._edit_row_text(
224
+ row=row_num, col_name=RegisterFlag.INJ_SOR, val=inj_source.value
225
+ )
226
+ if save:
227
+ self.save()
228
+
229
+ def edit_injection_volume(
230
+ self, inj_vol: Union[int, float], row_num: int, save: bool = True
231
+ ):
232
+ self._edit_row_text(
233
+ row=row_num, col_name=RegisterFlag.INJ_VOL, val=str(inj_vol)
234
+ )
235
+ if save:
236
+ self.save()
237
+
238
+ def edit_num_injections(self, num_inj: int, row_num: int, save: bool = True):
239
+ self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=num_inj)
240
+
241
+ def edit_method_name(self, method: str, row_num: int, save: bool = True):
242
+ method_dir = self.method_controller.src
243
+ possible_path = os.path.join(method_dir, method) + ".M\\"
244
+ if os.path.exists(possible_path):
245
+ method = os.path.join(method_dir, method)
246
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
247
+ if save:
248
+ self.save()
249
+
250
+ def edit_vial_location(self, loc: Tray, row_num: int, save: bool = True):
251
+ loc_num = -1
252
+ if isinstance(loc, TenVialColumn):
253
+ loc_num = loc.value
254
+ elif isinstance(loc, FiftyFourVialPlate):
255
+ loc_num = loc.value()
256
+ self._edit_row_num(
257
+ row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
258
+ )
259
+ if save:
260
+ self.save()
261
+
262
+ def save(self):
225
263
  self.send(Command.SAVE_SEQUENCE_CMD)
226
264
 
227
265
  def run(self, stall_while_running: bool = True):
@@ -236,7 +274,8 @@ class SequenceController(TableController):
236
274
  else:
237
275
  raise ValueError("Controller is offline!")
238
276
 
239
- if not self.table_state:
277
+ current_sequence_name = self.get_current_sequence_name()
278
+ if not self.table_state or self.table_state.name not in current_sequence_name:
240
279
  self.table_state = self.load()
241
280
 
242
281
  total_runtime = 0.0
@@ -258,16 +297,35 @@ class SequenceController(TableController):
258
297
  curr_method_runtime = self.method_controller.get_total_runtime()
259
298
  total_runtime += curr_method_runtime
260
299
 
261
- timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
262
300
  self.send(Command.RUN_SEQUENCE_CMD.value)
263
301
  self.timeout = total_runtime * 60
264
302
 
265
- if self.check_hplc_is_running():
266
- folder_name = f"{self.table_state.name} {timestamp}"
267
- data_file = SequenceDataFiles(
268
- dir=folder_name, sequence_name=self.table_state.name
269
- )
270
- self.data_files.append(data_file)
303
+ tries = 10
304
+ hplc_running = False
305
+ for _ in range(tries):
306
+ hplc_running = self.check_hplc_is_running()
307
+ if hplc_running:
308
+ break
309
+
310
+ if hplc_running:
311
+ full_path_name, current_sample_file = None, None
312
+ for _ in range(5):
313
+ try:
314
+ full_path_name, current_sample_file = (
315
+ self.get_current_run_data_dir_file()
316
+ )
317
+ break
318
+ except ValueError:
319
+ pass
320
+ if full_path_name and current_sample_file:
321
+ data_file = SequenceDataFiles(
322
+ sequence_name=self.table_state.name,
323
+ dir=full_path_name,
324
+ _data_files=[r.data_file for r in self.table_state.rows],
325
+ )
326
+ self.data_files.append(data_file)
327
+ else:
328
+ raise ValueError("Data directory for sequence was not found.")
271
329
 
272
330
  if stall_while_running:
273
331
  run_completed = self.check_hplc_done_running()
@@ -276,58 +334,43 @@ class SequenceController(TableController):
276
334
  else:
277
335
  warnings.warn("Run may have not completed.")
278
336
  else:
279
- raise RuntimeError("Sequence run did not start.")
337
+ raise RuntimeError("Sequence run may not have started.")
280
338
 
281
339
  @override
282
340
  def fuzzy_match_most_recent_folder(
283
- self, most_recent_folder: T
341
+ self, most_recent_folder: T, child_dirs: Optional[Set[str]]
284
342
  ) -> Result[SequenceDataFiles, str]:
285
343
  if isinstance(most_recent_folder, SequenceDataFiles):
286
- if os.path.isdir(most_recent_folder.dir):
287
- subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
288
- potential_folders = sorted(
289
- list(filter(lambda d: most_recent_folder.dir in d, subdirs))
290
- )
291
- most_recent_folder.child_dirs = [
292
- f
293
- for f in potential_folders
294
- if most_recent_folder.dir in f and ".M" not in f and ".D" in f
295
- ]
296
- return Ok(most_recent_folder)
297
-
298
344
  try:
299
- potential_folders = []
300
- for d in self.data_dirs:
301
- subdirs = [x[0] for x in os.walk(d)]
302
- potential_folders = sorted(
303
- list(filter(lambda d: most_recent_folder.dir in d, subdirs))
304
- )
305
- if len(potential_folders) > 0:
306
- break
307
- assert len(potential_folders) > 0
308
- parent_dirs = []
309
- for folder in potential_folders:
310
- path = os.path.normpath(folder)
311
- split_folder = path.split(os.sep)
312
- if most_recent_folder.dir in split_folder[-1]:
313
- parent_dirs.append(folder)
314
- parent_dir = sorted(parent_dirs, reverse=True)[0]
315
-
316
- potential_folders = []
317
- for d in self.data_dirs:
318
- subdirs = [x[0] for x in os.walk(d)]
319
- potential_folders = sorted(
320
- list(filter(lambda d: parent_dir in d, subdirs))
321
- )
322
- if len(potential_folders) > 0:
323
- break
324
- assert len(potential_folders) > 0
325
- most_recent_folder.child_dirs = [
326
- f
327
- for f in potential_folders
328
- if parent_dir in f and ".M" not in f and ".D" in f
329
- ]
330
- return Ok(most_recent_folder)
345
+ if most_recent_folder.dir and os.path.isdir(most_recent_folder.dir):
346
+ subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
347
+ if (
348
+ child_dirs
349
+ and len(child_dirs) > 0
350
+ and set(most_recent_folder._data_files) == child_dirs
351
+ ):
352
+ most_recent_folder.child_dirs = [
353
+ os.path.join(most_recent_folder.dir, f) for f in child_dirs
354
+ ]
355
+ else:
356
+ potential_folders: List[str] = sorted(
357
+ list(
358
+ filter(
359
+ lambda d: most_recent_folder.dir in d,
360
+ subdirs,
361
+ )
362
+ )
363
+ )
364
+ most_recent_folder.child_dirs = [
365
+ f
366
+ for f in potential_folders
367
+ if most_recent_folder.dir in f
368
+ and ".M" not in f
369
+ and ".D" in f
370
+ ]
371
+ return Ok(most_recent_folder)
372
+ else:
373
+ return Err("No sequence folder found, please give the full path.")
331
374
  except Exception as e:
332
375
  error = f"Failed to get sequence folder: {e}"
333
376
  return Err(error)
@@ -335,16 +378,20 @@ class SequenceController(TableController):
335
378
 
336
379
  def get_data_mult_uv(self, custom_path: Optional[str] = None):
337
380
  seq_data_dir = (
338
- SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
381
+ SequenceDataFiles(dir=custom_path, sequence_name="")
339
382
  if custom_path
340
383
  else self.data_files[-1]
341
384
  )
342
385
  if len(seq_data_dir.child_dirs) == 0:
343
- self.data_files[-1] = self.fuzzy_match_most_recent_folder(
344
- seq_data_dir
345
- ).ok_value
386
+ search_folder = self.fuzzy_match_most_recent_folder(
387
+ seq_data_dir, set(seq_data_dir.child_dirs)
388
+ )
389
+ if search_folder.is_ok():
390
+ seq_data_dir = search_folder.ok_value
391
+ else:
392
+ raise FileNotFoundError(search_folder.err_value)
346
393
  all_w_spectra: List[Dict[int, AgilentHPLCChromatogram]] = []
347
- for row in self.data_files[-1].child_dirs:
394
+ for row in seq_data_dir.child_dirs:
348
395
  all_w_spectra.append(self.get_data_uv(custom_path=row))
349
396
  return all_w_spectra
350
397
 
@@ -362,13 +409,13 @@ class SequenceController(TableController):
362
409
  self, custom_path: Optional[str] = None
363
410
  ) -> List[AgilentChannelChromatogramData]:
364
411
  seq_file_dir = (
365
- SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
412
+ SequenceDataFiles(dir=custom_path, sequence_name="")
366
413
  if custom_path
367
414
  else self.data_files[-1]
368
415
  )
369
416
  if len(seq_file_dir.child_dirs) == 0:
370
417
  self.data_files[-1] = self.fuzzy_match_most_recent_folder(
371
- seq_file_dir
418
+ seq_file_dir, set(seq_file_dir.child_dirs)
372
419
  ).ok_value
373
420
  spectra: List[AgilentChannelChromatogramData] = []
374
421
  for row in self.data_files[-1].child_dirs:
@@ -385,8 +432,10 @@ class SequenceController(TableController):
385
432
  self.data_files.append(
386
433
  self.fuzzy_match_most_recent_folder(
387
434
  most_recent_folder=SequenceDataFiles(
388
- dir=custom_path, child_dirs=[], sequence_name="NA"
389
- )
435
+ dir=custom_path,
436
+ sequence_name="NA",
437
+ ),
438
+ child_dirs=None,
390
439
  ).ok_value
391
440
  )
392
441
  parent_dir = self.data_files[-1]
@@ -0,0 +1,3 @@
1
+ from .injector import InjectorController
2
+
3
+ __all__ = ["InjectorController"]
@@ -1,19 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from ..abc_tables.device import DeviceController
1
4
  from ....control.controllers import CommunicationController
2
5
  from ....utils.injector_types import (
3
6
  Draw,
4
7
  Inject,
5
- InjectorFunction,
6
8
  InjectorTable,
7
9
  Mode,
8
10
  Remote,
9
11
  RemoteCommand,
10
12
  SourceType,
11
13
  Wait,
14
+ DrawDefault,
15
+ DrawDefaultVolume,
16
+ DrawDefaultLocation,
12
17
  )
13
18
  from ....utils.macro import Response
14
19
  from ....utils.table_types import RegisterFlag, Table
15
- from ....utils.tray_types import Tray
16
- from .device import DeviceController
20
+ from ....utils.tray_types import Tray, FiftyFourVialPlate, TenVialColumn, LocationPlus
17
21
 
18
22
 
19
23
  class InjectorController(DeviceController):
@@ -22,14 +26,32 @@ class InjectorController(DeviceController):
22
26
  ):
23
27
  super().__init__(controller, table, offline)
24
28
 
25
- def get_row(self, row: int) -> InjectorFunction:
26
- def return_tray_loc() -> Tray:
27
- raise NotImplementedError
28
- # unit = self.get_text(row, RegisterFlag.DRAW_LOCATION_UNIT)
29
- # tray = self.get_text(row, RegisterFlag.DRAW_LOCATION_TRAY)
30
- # x = self.get_text(row, RegisterFlag.DRAW_LOCATION_ROW)
31
- # y = self.get_text(row, RegisterFlag.DRAW_LOCATION_COLUMN)
32
- # return FiftyFourVialPlate.from_str("P1-A1")
29
+ def try_vial_location(self, val: str) -> Tray:
30
+ try:
31
+ return FiftyFourVialPlate.from_str(val)
32
+ except Exception:
33
+ try:
34
+ return TenVialColumn(int(val))
35
+ except Exception:
36
+ raise ValueError("Location could not be identified.")
37
+
38
+ def get_row(
39
+ self, row: int
40
+ ) -> (
41
+ Draw
42
+ | DrawDefaultVolume
43
+ | Inject
44
+ | Wait
45
+ | DrawDefault
46
+ | DrawDefaultLocation
47
+ | Remote
48
+ ):
49
+ def return_location_plus() -> Tray:
50
+ unit = self.get_num(row, RegisterFlag.DRAW_LOCATION_UNIT)
51
+ tray = self.get_num(row, RegisterFlag.DRAW_LOCATION_TRAY)
52
+ row_ = self.get_num(row, RegisterFlag.DRAW_LOCATION_ROW)
53
+ col = self.get_num(row, RegisterFlag.DRAW_LOCATION_COLUMN)
54
+ return LocationPlus(int(unit), int(tray), int(row_), int(col))
33
55
 
34
56
  function = self.get_text(row, RegisterFlag.FUNCTION)
35
57
  if function == "Wait":
@@ -37,20 +59,32 @@ class InjectorController(DeviceController):
37
59
  elif function == "Inject":
38
60
  return Inject()
39
61
  elif function == "Draw":
40
- # TODO: better error handling
41
62
  is_source = SourceType(self.get_text(row, RegisterFlag.DRAW_SOURCE))
42
63
  is_volume = Mode(self.get_text(row, RegisterFlag.DRAW_VOLUME))
43
- vol = (
44
- self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
45
- if is_volume == Mode.SET
46
- else None
47
- )
48
- if is_source is SourceType.SPECIFIC_LOCATION:
49
- return Draw(amount=vol, source=return_tray_loc())
50
- elif is_source is SourceType.LOCATION:
51
- return Draw(
52
- amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION)
53
- )
64
+ if is_volume is not Mode.SET:
65
+ if is_source == SourceType.DEFAULT:
66
+ return DrawDefault()
67
+ elif is_source is SourceType.SPECIFIC_LOCATION:
68
+ return DrawDefaultVolume(location=return_location_plus())
69
+ elif is_source is SourceType.LOCATION:
70
+ return DrawDefaultVolume(
71
+ location=self.try_vial_location(
72
+ self.get_text(row, RegisterFlag.DRAW_LOCATION)
73
+ )
74
+ )
75
+ else:
76
+ vol = self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
77
+ if is_source == SourceType.DEFAULT:
78
+ return DrawDefaultLocation(amount=vol)
79
+ elif is_source is SourceType.SPECIFIC_LOCATION:
80
+ return Draw(amount=vol, location=return_location_plus())
81
+ elif is_source is SourceType.LOCATION:
82
+ return Draw(
83
+ amount=vol,
84
+ location=self.try_vial_location(
85
+ self.get_text(row, RegisterFlag.DRAW_LOCATION)
86
+ ),
87
+ )
54
88
  elif function == "Remote":
55
89
  return Remote(
56
90
  command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
@@ -58,16 +92,15 @@ class InjectorController(DeviceController):
58
92
  )
59
93
  raise ValueError("No valid function found.")
60
94
 
61
- def load(self) -> InjectorTable:
95
+ def load(self) -> InjectorTable | None:
62
96
  rows = self.get_num_rows()
63
97
  if rows.is_ok():
64
98
  row_response = rows.value
65
99
  if isinstance(row_response, Response):
66
100
  return InjectorTable(
67
101
  functions=[
68
- self.get_row(i) for i in range(int(row_response.num_response))
102
+ self.get_row(i + 1)
103
+ for i in range(int(row_response.num_response))
69
104
  ]
70
105
  )
71
- elif rows.is_err():
72
- return InjectorTable(functions=[])
73
- raise ValueError("Unexpected error")
106
+ raise ValueError("Couldn't read injector table rows.")
@@ -6,6 +6,7 @@ Authors: Lucy Hao
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import os.path
9
10
  from typing import Dict, List, Optional, Tuple, Union
10
11
 
11
12
  from pychemstation.analysis.chromatogram import AgilentChannelChromatogramData
@@ -13,9 +14,10 @@ from pychemstation.analysis.chromatogram import (
13
14
  AgilentHPLCChromatogram,
14
15
  )
15
16
  from .controllers.devices.injector import InjectorController
16
- from .controllers.tables.sequence import SequenceController, MethodController
17
+ from .controllers.data_aq.sequence import SequenceController, MethodController
17
18
  from ..analysis.process_report import AgilentReport, ReportType
18
19
  from ..control.controllers import CommunicationController
20
+ from ..utils.injector_types import InjectorTable
19
21
  from ..utils.macro import Command, Response, Status
20
22
  from ..utils.method_types import MethodDetails
21
23
  from ..utils.sequence_types import SequenceTable
@@ -30,14 +32,16 @@ class HPLCController:
30
32
 
31
33
  INJECTOR_TABLE = Table(register="RCWLS1Pretreatment[1]", name="InstructionTable")
32
34
 
35
+ DAD_TABLE = Table(register="RCDAD1Method[1]", name="Timetable")
36
+
33
37
  MSD_TABLE = Table(register="MSACQINFO[1]", name="SprayChamber")
34
38
 
35
39
  def __init__(
36
40
  self,
37
41
  comm_dir: str,
38
- method_dir: str,
39
- sequence_dir: str,
40
- data_dirs: List[str],
42
+ method_dir: Optional[str] = None,
43
+ sequence_dir: Optional[str] = None,
44
+ extra_data_dirs: Optional[List[str]] = None,
41
45
  offline: bool = False,
42
46
  debug: bool = False,
43
47
  ):
@@ -46,33 +50,42 @@ class HPLCController:
46
50
  double escaped: "C:\\my_folder\\"
47
51
 
48
52
  :param comm_dir: Name of directory for communication, where ChemStation will read and write from. Can be any existing directory.
49
- :param data_dirs: Name of directories for storing data after method or sequence runs. Method data dir is default
50
53
  the first one in the list. In other words, the first dir in the list is highest prio. Must be "normal" strings and not r-strings.
51
- :param method_dir: Name of directory where method files are stored.
52
- :param sequence_dir: Name of directory where sequence files are stored.
53
54
  :raises FileNotFoundError: If either `data_dir`, `method_dir`, `sequence_dir`, `sequence_data_dir`or `comm_dir` is not a valid directory.
54
55
  """
55
56
  self.comm: CommunicationController = CommunicationController(
56
- comm_dir=comm_dir, debug=debug
57
- )
58
- self.method_controller: MethodController = MethodController(
59
- controller=self.comm,
60
- src=method_dir,
61
- data_dirs=data_dirs,
62
- table=self.METHOD_TIMETABLE,
63
- offline=offline,
64
- injector_controller=InjectorController(
65
- controller=self.comm, table=self.INJECTOR_TABLE, offline=offline
66
- ),
67
- )
68
- self.sequence_controller: SequenceController = SequenceController(
69
- controller=self.comm,
70
- src=sequence_dir,
71
- data_dirs=data_dirs,
72
- table=self.SEQUENCE_TABLE,
73
- method_controller=self.method_controller,
74
- offline=offline,
57
+ comm_dir=comm_dir, debug=debug, offline=offline
75
58
  )
59
+ data_dirs: List[str] = []
60
+ if not offline:
61
+ if not method_dir or not sequence_dir or not extra_data_dirs:
62
+ method_dir, sequence_dir, data_dirs = self.comm.get_chemstation_dirs()
63
+ if extra_data_dirs:
64
+ data_dirs.extend(extra_data_dirs)
65
+ data_dirs = list(set([os.path.normpath(p) for p in data_dirs]))
66
+ if method_dir and sequence_dir and data_dirs:
67
+ self.method_controller: MethodController = MethodController(
68
+ controller=self.comm,
69
+ src=method_dir,
70
+ data_dirs=data_dirs,
71
+ table=self.METHOD_TIMETABLE,
72
+ offline=offline,
73
+ injector_controller=InjectorController(
74
+ controller=self.comm, table=self.INJECTOR_TABLE, offline=offline
75
+ ),
76
+ )
77
+ self.sequence_controller: SequenceController = SequenceController(
78
+ controller=self.comm,
79
+ src=sequence_dir,
80
+ data_dirs=data_dirs,
81
+ table=self.SEQUENCE_TABLE,
82
+ method_controller=self.method_controller,
83
+ offline=offline,
84
+ )
85
+ else:
86
+ raise ValueError(
87
+ "Expected a method dir, sequence dir and data dirs but they were None."
88
+ )
76
89
 
77
90
  def send(self, cmd: Union[Command, str]):
78
91
  """
@@ -278,6 +291,9 @@ class HPLCController:
278
291
  """Returns the currently loaded sequence."""
279
292
  return self.sequence_controller.load()
280
293
 
294
+ def load_injector_program(self) -> InjectorTable | None:
295
+ return self.method_controller.injector_controller.load()
296
+
281
297
  def standby(self):
282
298
  """Switches all modules in standby mode. All lamps and pumps are switched off."""
283
299
  self.send(Command.STANDBY_CMD)