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.
- pychemstation/analysis/__init__.py +8 -1
- pychemstation/control/README.md +1 -1
- pychemstation/control/controllers/__init__.py +2 -2
- pychemstation/control/controllers/abc_tables/__init__.py +0 -0
- pychemstation/control/controllers/abc_tables/abc_comm.py +155 -0
- pychemstation/control/controllers/abc_tables/device.py +20 -0
- pychemstation/control/controllers/{tables/table.py → abc_tables/run.py} +58 -201
- pychemstation/control/controllers/abc_tables/table.py +230 -0
- pychemstation/control/controllers/comm.py +26 -101
- pychemstation/control/controllers/{tables → data_aq}/method.py +12 -15
- pychemstation/control/controllers/{tables → data_aq}/sequence.py +168 -119
- pychemstation/control/controllers/devices/__init__.py +3 -0
- pychemstation/control/controllers/devices/injector.py +61 -28
- pychemstation/control/hplc.py +42 -26
- pychemstation/utils/injector_types.py +22 -2
- pychemstation/utils/macro.py +11 -0
- pychemstation/utils/mocking/__init__.py +0 -0
- pychemstation/utils/mocking/mock_comm.py +5 -0
- pychemstation/utils/mocking/mock_hplc.py +2 -0
- pychemstation/utils/sequence_types.py +22 -2
- pychemstation/utils/table_types.py +6 -0
- pychemstation/utils/tray_types.py +36 -1
- {pychemstation-0.10.5.dist-info → pychemstation-0.10.7.dist-info}/METADATA +3 -3
- pychemstation-0.10.7.dist-info/RECORD +42 -0
- pychemstation/control/controllers/devices/device.py +0 -74
- pychemstation-0.10.5.dist-info/RECORD +0 -36
- /pychemstation/control/controllers/{tables → data_aq}/__init__.py +0 -0
- {pychemstation-0.10.5.dist-info → pychemstation-0.10.7.dist-info}/WHEEL +0 -0
- {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,
|
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 .
|
26
|
+
from ..abc_tables.run import RunController
|
27
|
+
from . import MethodController
|
29
28
|
|
30
29
|
|
31
|
-
class SequenceController(
|
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.
|
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
|
-
|
150
|
-
|
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
|
-
|
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.
|
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.
|
179
|
+
self.save()
|
176
180
|
num_rows = self.get_num_rows()
|
177
181
|
if row.vial_location:
|
178
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
208
|
-
|
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.
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
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
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
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
|
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
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
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,
|
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
|
-
|
344
|
-
seq_data_dir
|
345
|
-
)
|
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
|
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,
|
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,
|
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]
|
@@ -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
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
102
|
+
self.get_row(i + 1)
|
103
|
+
for i in range(int(row_response.num_response))
|
69
104
|
]
|
70
105
|
)
|
71
|
-
|
72
|
-
return InjectorTable(functions=[])
|
73
|
-
raise ValueError("Unexpected error")
|
106
|
+
raise ValueError("Couldn't read injector table rows.")
|
pychemstation/control/hplc.py
CHANGED
@@ -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.
|
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
|
-
|
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)
|