pychemstation 0.10.4__py3-none-any.whl → 0.10.6__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 (34) hide show
  1. pychemstation/analysis/__init__.py +8 -1
  2. pychemstation/analysis/chromatogram.py +20 -0
  3. pychemstation/analysis/process_report.py +125 -63
  4. pychemstation/control/__init__.py +2 -0
  5. pychemstation/control/controllers/__init__.py +2 -3
  6. pychemstation/control/controllers/abc_tables/device.py +15 -0
  7. pychemstation/control/controllers/abc_tables/run.py +228 -0
  8. pychemstation/control/controllers/abc_tables/table.py +221 -0
  9. pychemstation/control/controllers/comm.py +25 -106
  10. pychemstation/control/controllers/data_aq/__init__.py +4 -0
  11. pychemstation/control/controllers/{tables → data_aq}/method.py +52 -95
  12. pychemstation/control/controllers/{tables → data_aq}/sequence.py +199 -141
  13. pychemstation/control/controllers/devices/__init__.py +3 -0
  14. pychemstation/control/controllers/devices/injector.py +69 -24
  15. pychemstation/control/hplc.py +15 -17
  16. pychemstation/utils/injector_types.py +23 -3
  17. pychemstation/utils/macro.py +2 -2
  18. pychemstation/utils/method_types.py +1 -1
  19. pychemstation/utils/mocking/__init__.py +0 -0
  20. pychemstation/utils/mocking/abc_comm.py +160 -0
  21. pychemstation/utils/mocking/mock_comm.py +5 -0
  22. pychemstation/utils/mocking/mock_hplc.py +2 -0
  23. pychemstation/utils/sequence_types.py +19 -0
  24. pychemstation/utils/table_types.py +6 -0
  25. pychemstation/utils/tray_types.py +36 -1
  26. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/METADATA +4 -4
  27. pychemstation-0.10.6.dist-info/RECORD +42 -0
  28. pychemstation/control/controllers/devices/device.py +0 -49
  29. pychemstation/control/controllers/tables/ms.py +0 -24
  30. pychemstation/control/controllers/tables/table.py +0 -375
  31. pychemstation-0.10.4.dist-info/RECORD +0 -37
  32. /pychemstation/control/controllers/{tables → abc_tables}/__init__.py +0 -0
  33. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/WHEEL +0 -0
  34. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,18 +1,19 @@
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
5
5
 
6
6
  from result import Err, Ok, Result
7
7
  from typing_extensions import override
8
8
 
9
- from ....analysis.process_report import AgilentReport, ReportType
10
- from ....control.controllers.comm import CommunicationController
11
9
  from pychemstation.analysis.chromatogram import (
12
10
  SEQUENCE_TIME_FORMAT,
13
11
  AgilentChannelChromatogramData,
14
12
  AgilentHPLCChromatogram,
15
13
  )
14
+
15
+ from ....analysis.process_report import AgilentReport, ReportType
16
+ from ....control.controllers.comm import CommunicationController
16
17
  from ....utils.macro import Command
17
18
  from ....utils.sequence_types import (
18
19
  InjectionSource,
@@ -21,20 +22,20 @@ from ....utils.sequence_types import (
21
22
  SequenceEntry,
22
23
  SequenceTable,
23
24
  )
24
- from ....utils.table_types import RegisterFlag, Table
25
- from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn
26
- from .. import MethodController
27
- from .table import TableController
25
+ from ....utils.table_types import RegisterFlag, T, Table
26
+ from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn, Tray
27
+ from ..abc_tables.run import RunController
28
+ from . import MethodController
28
29
 
29
30
 
30
- class SequenceController(TableController):
31
+ class SequenceController(RunController):
31
32
  """
32
33
  Class containing sequence related logic
33
34
  """
34
35
 
35
36
  def __init__(
36
37
  self,
37
- controller: CommunicationController,
38
+ controller: Optional[CommunicationController],
38
39
  method_controller: MethodController,
39
40
  src: str,
40
41
  data_dirs: List[str],
@@ -57,7 +58,7 @@ class SequenceController(TableController):
57
58
  seq_name = self.receive()
58
59
 
59
60
  if rows.is_ok() and seq_name.is_ok():
60
- self.table_state = SequenceTable(
61
+ self.table_state: SequenceTable = SequenceTable(
61
62
  name=seq_name.ok_value.string_response.partition(".S")[0],
62
63
  rows=[
63
64
  self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))
@@ -66,19 +67,37 @@ class SequenceController(TableController):
66
67
  return self.table_state
67
68
  raise RuntimeError(rows.err_value)
68
69
 
70
+ def try_int(self, val: Any) -> Optional[int]:
71
+ try:
72
+ return int(val)
73
+ except ValueError:
74
+ return None
75
+
76
+ def try_float(self, val: Any) -> Optional[float]:
77
+ try:
78
+ return float(val)
79
+ except ValueError:
80
+ return None
81
+
82
+ def try_vial_location(self, val: Any) -> Tray:
83
+ try:
84
+ return (
85
+ TenVialColumn(val)
86
+ if val <= 10
87
+ else FiftyFourVialPlate.from_int(num=val)
88
+ )
89
+ except ValueError:
90
+ raise ValueError("Expected vial location, is empty.")
91
+
69
92
  def get_row(self, row: int) -> SequenceEntry:
70
93
  sample_name = self.get_text(row, RegisterFlag.NAME)
71
- vial_location = int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
94
+ vial_location = self.try_int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
72
95
  method = self.get_text(row, RegisterFlag.METHOD)
73
- num_inj = int(self.get_num(row, RegisterFlag.NUM_INJ))
74
- inj_vol = float(self.get_text(row, RegisterFlag.INJ_VOL))
96
+ num_inj = self.try_int(self.get_num(row, RegisterFlag.NUM_INJ))
97
+ inj_vol = self.try_float(self.get_text(row, RegisterFlag.INJ_VOL))
75
98
  inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
76
99
  sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
77
- vial_enum = (
78
- TenVialColumn(vial_location)
79
- if vial_location <= 10
80
- else FiftyFourVialPlate.from_int(num=vial_location)
81
- )
100
+ vial_enum = self.try_vial_location(vial_location)
82
101
  return SequenceEntry(
83
102
  sample_name=sample_name,
84
103
  vial_location=vial_enum,
@@ -113,7 +132,7 @@ class SequenceController(TableController):
113
132
  parsed_response = self.receive().ok_value.string_response
114
133
 
115
134
  assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
116
- self.table_state = None
135
+ self.table_state = self.load()
117
136
 
118
137
  def edit(self, sequence_table: SequenceTable):
119
138
  """
@@ -127,13 +146,12 @@ class SequenceController(TableController):
127
146
  if rows.is_ok():
128
147
  existing_row_num = rows.ok_value.num_response
129
148
  wanted_row_num = len(sequence_table.rows)
130
- while existing_row_num != wanted_row_num:
131
- if wanted_row_num > existing_row_num:
132
- self.add_row()
133
- elif wanted_row_num < existing_row_num:
134
- self.delete_row(int(existing_row_num))
149
+ for i in range(int(existing_row_num)):
150
+ self.delete_row(int(existing_row_num - i))
151
+ self.send(Command.SAVE_SEQUENCE_CMD)
152
+ for i in range(int(wanted_row_num)):
153
+ self.add_row()
135
154
  self.send(Command.SAVE_SEQUENCE_CMD)
136
- existing_row_num = self.get_num_rows().ok_value.num_response
137
155
  self.send(Command.SWITCH_SEQUENCE_CMD)
138
156
 
139
157
  for i, row in enumerate(sequence_table.rows):
@@ -156,87 +174,114 @@ class SequenceController(TableController):
156
174
  self.send(Command.SAVE_SEQUENCE_CMD)
157
175
  num_rows = self.get_num_rows()
158
176
  if row.vial_location:
159
- loc = row.vial_location
160
- if isinstance(loc, TenVialColumn):
161
- loc = row.vial_location.value
162
- elif isinstance(loc, FiftyFourVialPlate):
163
- loc = row.vial_location.value()
164
- self._edit_row_num(
165
- row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc
166
- )
177
+ self.edit_vial_location(row.vial_location, row_num)
167
178
  if row.method:
168
- method_dir = self.method_controller.src
169
- possible_path = os.path.join(method_dir, row.method) + ".M\\"
170
- method = row.method
171
- if os.path.exists(possible_path):
172
- method = os.path.join(method_dir, row.method)
173
- self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
179
+ self.edit_method_name(row.method, row_num)
174
180
  if row.num_inj:
175
- self._edit_row_num(
176
- row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj
177
- )
181
+ self.edit_num_injections(row.num_inj, row_num)
178
182
  if row.inj_vol:
179
- self._edit_row_text(
180
- row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol
181
- )
183
+ self.edit_injection_volume(row.inj_vol, row_num)
182
184
  if row.inj_source:
183
- self._edit_row_text(
184
- row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value
185
- )
185
+ self.edit_injection_source(row.inj_source, row_num)
186
186
  if row.sample_name:
187
- self._edit_row_text(
188
- row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name
189
- )
190
- if row.data_file:
191
- self._edit_row_text(
192
- row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file
193
- )
194
- else:
195
- self._edit_row_text(
196
- row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name
197
- )
187
+ self.edit_sample_name(row.sample_name, row_num)
188
+ if row.data_file:
189
+ self.edit_data_file(row.data_file, row_num)
190
+ elif row.sample_name and not row.data_file:
191
+ self.edit_data_file(row.sample_name, row_num)
198
192
  if row.sample_type:
199
- self._edit_row_num(
200
- row=row_num,
201
- col_name=RegisterFlag.SAMPLE_TYPE,
202
- val=row.sample_type.value,
203
- )
204
-
193
+ self.edit_sample_type(row.sample_type, row_num)
205
194
  self.send(Command.SAVE_SEQUENCE_CMD)
206
195
 
196
+ def edit_sample_type(self, sample_type: SampleType, row_num: int):
197
+ self._edit_row_num(
198
+ row=row_num,
199
+ col_name=RegisterFlag.SAMPLE_TYPE,
200
+ val=sample_type.value,
201
+ )
202
+
203
+ def edit_data_file(self, data_file: str, row_num: int):
204
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=data_file)
205
+
206
+ def edit_sample_name(self, sample_name: str, row_num: int):
207
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=sample_name)
208
+
209
+ def edit_injection_source(self, inj_source: InjectionSource, row_num: int):
210
+ self._edit_row_text(
211
+ row=row_num, col_name=RegisterFlag.INJ_SOR, val=inj_source.value
212
+ )
213
+
214
+ def edit_injection_volume(self, inj_vol: Union[int, float], row_num: int):
215
+ self._edit_row_text(
216
+ row=row_num, col_name=RegisterFlag.INJ_VOL, val=str(inj_vol)
217
+ )
218
+
219
+ def edit_num_injections(self, num_inj: int, row_num: int):
220
+ self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=num_inj)
221
+
222
+ def edit_method_name(self, method: str, row_num: int):
223
+ method_dir = self.method_controller.src
224
+ possible_path = os.path.join(method_dir, method) + ".M\\"
225
+ if os.path.exists(possible_path):
226
+ method = os.path.join(method_dir, method)
227
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
228
+
229
+ def edit_vial_location(self, loc: Tray, row_num: int):
230
+ loc_num = -1
231
+ if isinstance(loc, TenVialColumn):
232
+ loc_num = loc.value
233
+ elif isinstance(loc, FiftyFourVialPlate):
234
+ loc_num = loc.value()
235
+ self._edit_row_num(
236
+ row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
237
+ )
238
+
207
239
  def run(self, stall_while_running: bool = True):
208
240
  """
209
241
  Starts the currently loaded sequence, storing data
210
242
  under the <data_dir>/<sequence table name> folder.
211
243
  Device must be ready.
212
244
  """
213
- self.controller.send(Command.SAVE_METHOD_CMD)
214
- self.controller.send(Command.SAVE_SEQUENCE_CMD)
245
+ if self.controller:
246
+ self.controller.send(Command.SAVE_METHOD_CMD)
247
+ self.controller.send(Command.SAVE_SEQUENCE_CMD)
248
+ else:
249
+ raise ValueError("Controller is offline!")
215
250
 
216
251
  if not self.table_state:
217
252
  self.table_state = self.load()
218
253
 
219
- total_runtime = 0
254
+ total_runtime = 0.0
220
255
  for entry in self.table_state.rows:
221
256
  curr_method_runtime = self.method_controller.get_total_runtime()
222
257
  loaded_method = self.method_controller.get_method_name().removesuffix(".M")
223
- method_path = entry.method.split(sep="\\")
224
- method_name = method_path[-1]
225
- if loaded_method != method_name:
226
- method_dir = (
227
- "\\".join(method_path[:-1]) + "\\" if len(method_path) > 1 else None
228
- )
229
- self.method_controller.switch(
230
- method_name=method_name, alt_method_dir=method_dir
231
- )
232
- curr_method_runtime = self.method_controller.get_total_runtime()
258
+ if entry.method:
259
+ method_path = entry.method.split(sep="\\")
260
+ method_name = method_path[-1]
261
+ if loaded_method != method_name:
262
+ method_dir = (
263
+ "\\".join(method_path[:-1]) + "\\"
264
+ if len(method_path) > 1
265
+ else None
266
+ )
267
+ self.method_controller.switch(
268
+ method_name=method_name, alt_method_dir=method_dir
269
+ )
270
+ curr_method_runtime = self.method_controller.get_total_runtime()
233
271
  total_runtime += curr_method_runtime
234
272
 
235
273
  timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
236
274
  self.send(Command.RUN_SEQUENCE_CMD.value)
237
275
  self.timeout = total_runtime * 60
238
276
 
239
- if self.check_hplc_is_running():
277
+ tries = 10
278
+ hplc_running = False
279
+ for _ in range(tries):
280
+ hplc_running = self.check_hplc_is_running()
281
+ if hplc_running:
282
+ break
283
+
284
+ if hplc_running:
240
285
  folder_name = f"{self.table_state.name} {timestamp}"
241
286
  data_file = SequenceDataFiles(
242
287
  dir=folder_name, sequence_name=self.table_state.name
@@ -250,95 +295,104 @@ class SequenceController(TableController):
250
295
  else:
251
296
  warnings.warn("Run may have not completed.")
252
297
  else:
253
- raise RuntimeError("Sequence run did not start.")
298
+ raise RuntimeError("Sequence run may not have started.")
254
299
 
255
300
  @override
256
301
  def fuzzy_match_most_recent_folder(
257
- self, most_recent_folder: SequenceDataFiles
302
+ self, most_recent_folder: T
258
303
  ) -> Result[SequenceDataFiles, str]:
259
- if os.path.isdir(most_recent_folder.dir):
260
- subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
261
- potential_folders = sorted(
262
- list(filter(lambda d: most_recent_folder.dir in d, subdirs))
263
- )
264
- most_recent_folder.child_dirs = [
265
- f
266
- for f in potential_folders
267
- if most_recent_folder.dir in f and ".M" not in f and ".D" in f
268
- ]
269
- return Ok(most_recent_folder)
270
-
271
- try:
272
- potential_folders = []
273
- for d in self.data_dirs:
274
- subdirs = [x[0] for x in os.walk(d)]
304
+ if isinstance(most_recent_folder, SequenceDataFiles):
305
+ if os.path.isdir(most_recent_folder.dir):
306
+ subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
275
307
  potential_folders = sorted(
276
308
  list(filter(lambda d: most_recent_folder.dir in d, subdirs))
277
309
  )
278
- if len(potential_folders) > 0:
279
- break
280
- assert len(potential_folders) > 0
281
- parent_dirs = []
282
- for folder in potential_folders:
283
- path = os.path.normpath(folder)
284
- split_folder = path.split(os.sep)
285
- if most_recent_folder.dir in split_folder[-1]:
286
- parent_dirs.append(folder)
287
- parent_dir = sorted(parent_dirs, reverse=True)[0]
288
-
289
- potential_folders = []
290
- for d in self.data_dirs:
291
- subdirs = [x[0] for x in os.walk(d)]
292
- potential_folders = sorted(
293
- list(filter(lambda d: parent_dir in d, subdirs))
294
- )
295
- if len(potential_folders) > 0:
296
- break
297
- assert len(potential_folders) > 0
298
- most_recent_folder.child_dirs = [
299
- f
300
- for f in potential_folders
301
- if parent_dir in f and ".M" not in f and ".D" in f
302
- ]
303
- return Ok(most_recent_folder)
304
- except Exception as e:
305
- error = f"Failed to get sequence folder: {e}"
306
- return Err(error)
310
+ most_recent_folder.child_dirs = [
311
+ f
312
+ for f in potential_folders
313
+ if most_recent_folder.dir in f and ".M" not in f and ".D" in f
314
+ ]
315
+ return Ok(most_recent_folder)
307
316
 
308
- def get_data_uv(
309
- self, custom_path: Optional[str] = None
310
- ) -> List[Dict[int, AgilentHPLCChromatogram]]:
311
- custom_path = (
317
+ try:
318
+ potential_folders = []
319
+ for d in self.data_dirs:
320
+ subdirs = [x[0] for x in os.walk(d)]
321
+ potential_folders = sorted(
322
+ list(filter(lambda d: most_recent_folder.dir in d, subdirs))
323
+ )
324
+ if len(potential_folders) > 0:
325
+ break
326
+ assert len(potential_folders) > 0
327
+ parent_dirs = []
328
+ for folder in potential_folders:
329
+ path = os.path.normpath(folder)
330
+ split_folder = path.split(os.sep)
331
+ if most_recent_folder.dir in split_folder[-1]:
332
+ parent_dirs.append(folder)
333
+ parent_dir = sorted(parent_dirs, reverse=True)[0]
334
+
335
+ potential_folders = []
336
+ for d in self.data_dirs:
337
+ subdirs = [x[0] for x in os.walk(d)]
338
+ potential_folders = sorted(
339
+ list(filter(lambda d: parent_dir in d, subdirs))
340
+ )
341
+ if len(potential_folders) > 0:
342
+ break
343
+ assert len(potential_folders) > 0
344
+ most_recent_folder.child_dirs = [
345
+ f
346
+ for f in potential_folders
347
+ if parent_dir in f and ".M" not in f and ".D" in f
348
+ ]
349
+ return Ok(most_recent_folder)
350
+ except Exception as e:
351
+ error = f"Failed to get sequence folder: {e}"
352
+ return Err(error)
353
+ return Err("Expected SequenceDataFile type.")
354
+
355
+ def get_data_mult_uv(self, custom_path: Optional[str] = None):
356
+ seq_data_dir = (
312
357
  SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
313
358
  if custom_path
314
359
  else self.data_files[-1]
315
360
  )
316
- if len(custom_path.child_dirs) == 0:
361
+ if len(seq_data_dir.child_dirs) == 0:
317
362
  self.data_files[-1] = self.fuzzy_match_most_recent_folder(
318
- custom_path
363
+ seq_data_dir
319
364
  ).ok_value
320
365
  all_w_spectra: List[Dict[int, AgilentHPLCChromatogram]] = []
321
366
  for row in self.data_files[-1].child_dirs:
322
- self.get_uv_spectrum(row)
323
- all_w_spectra.append(self.uv)
367
+ all_w_spectra.append(self.get_data_uv(custom_path=row))
324
368
  return all_w_spectra
325
369
 
370
+ def get_data_uv(
371
+ self, custom_path: Optional[str] = None
372
+ ) -> Dict[int, AgilentHPLCChromatogram]:
373
+ if isinstance(custom_path, str):
374
+ self.get_uv_spectrum(custom_path)
375
+ return self.uv
376
+ raise ValueError(
377
+ "Path should exist when calling from sequence. Provide a child path (contains the method)."
378
+ )
379
+
326
380
  def get_data(
327
381
  self, custom_path: Optional[str] = None
328
382
  ) -> List[AgilentChannelChromatogramData]:
329
- custom_path = (
383
+ seq_file_dir = (
330
384
  SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
331
385
  if custom_path
332
386
  else self.data_files[-1]
333
387
  )
334
- if len(custom_path.child_dirs) == 0:
388
+ if len(seq_file_dir.child_dirs) == 0:
335
389
  self.data_files[-1] = self.fuzzy_match_most_recent_folder(
336
- custom_path
390
+ seq_file_dir
337
391
  ).ok_value
338
392
  spectra: List[AgilentChannelChromatogramData] = []
339
393
  for row in self.data_files[-1].child_dirs:
340
394
  self.get_spectrum_at_channels(row)
341
- spectra.append(AgilentChannelChromatogramData(**self.spectra))
395
+ spectra.append(AgilentChannelChromatogramData.from_dict(self.spectra))
342
396
  return spectra
343
397
 
344
398
  def get_report(
@@ -363,8 +417,12 @@ class SequenceController(TableController):
363
417
  spectra[i].__dict__.values()
364
418
  )
365
419
  for j, signal in enumerate(metd_report.signals):
366
- possible_data = child_spectra[j]
367
- if len(possible_data.x) > 0:
368
- signal.data = possible_data
420
+ assert len(metd_report.signals) <= len(child_spectra)
421
+ try:
422
+ possible_data = child_spectra[j]
423
+ if len(possible_data.x) > 0:
424
+ signal.data = possible_data
425
+ except IndexError:
426
+ raise ValueError(j)
369
427
  reports.append(metd_report)
370
428
  return reports
@@ -0,0 +1,3 @@
1
+ from .injector import InjectorController
2
+
3
+ __all__ = ["InjectorController"]
@@ -1,18 +1,21 @@
1
+ from ..abc_tables.device import DeviceController
1
2
  from ....control.controllers import CommunicationController
2
3
  from ....utils.injector_types import (
3
4
  Draw,
4
5
  Inject,
5
- InjectorFunction,
6
6
  InjectorTable,
7
7
  Mode,
8
8
  Remote,
9
9
  RemoteCommand,
10
10
  SourceType,
11
11
  Wait,
12
+ DrawDefault,
13
+ DrawDefaultVolume,
14
+ DrawDefaultLocation,
12
15
  )
16
+ from ....utils.macro import Response
13
17
  from ....utils.table_types import RegisterFlag, Table
14
- from ....utils.tray_types import Tray
15
- from .device import DeviceController
18
+ from ....utils.tray_types import Tray, FiftyFourVialPlate, TenVialColumn, LocationPlus
16
19
 
17
20
 
18
21
  class InjectorController(DeviceController):
@@ -21,9 +24,32 @@ class InjectorController(DeviceController):
21
24
  ):
22
25
  super().__init__(controller, table, offline)
23
26
 
24
- def get_row(self, row: int) -> InjectorFunction:
25
- def return_tray_loc() -> Tray:
26
- pass
27
+ def try_vial_location(self, val: str) -> Tray:
28
+ try:
29
+ return FiftyFourVialPlate.from_str(val)
30
+ except Exception:
31
+ try:
32
+ return TenVialColumn(int(val))
33
+ except Exception:
34
+ raise ValueError("Location could not be identified.")
35
+
36
+ def get_row(
37
+ self, row: int
38
+ ) -> (
39
+ Draw
40
+ | DrawDefaultVolume
41
+ | Inject
42
+ | Wait
43
+ | DrawDefault
44
+ | DrawDefaultLocation
45
+ | Remote
46
+ ):
47
+ def return_location_plus() -> Tray:
48
+ unit = self.get_num(row, RegisterFlag.DRAW_LOCATION_UNIT)
49
+ tray = self.get_num(row, RegisterFlag.DRAW_LOCATION_TRAY)
50
+ row_ = self.get_num(row, RegisterFlag.DRAW_LOCATION_ROW)
51
+ col = self.get_num(row, RegisterFlag.DRAW_LOCATION_COLUMN)
52
+ return LocationPlus(int(unit), int(tray), int(row_), int(col))
27
53
 
28
54
  function = self.get_text(row, RegisterFlag.FUNCTION)
29
55
  if function == "Wait":
@@ -31,31 +57,50 @@ class InjectorController(DeviceController):
31
57
  elif function == "Inject":
32
58
  return Inject()
33
59
  elif function == "Draw":
34
- # TODO: better error handling
35
60
  is_source = SourceType(self.get_text(row, RegisterFlag.DRAW_SOURCE))
36
61
  is_volume = Mode(self.get_text(row, RegisterFlag.DRAW_VOLUME))
37
- vol = (
38
- self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
39
- if is_volume == Mode.SET
40
- else None
41
- )
42
- if is_source is SourceType.SPECIFIC_LOCATION:
43
- return Draw(amount=vol, source=return_tray_loc())
44
- elif is_source is SourceType.LOCATION:
45
- return Draw(
46
- amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION)
47
- )
62
+ if is_volume is not Mode.SET:
63
+ if is_source == SourceType.DEFAULT:
64
+ return DrawDefault()
65
+ elif is_source is SourceType.SPECIFIC_LOCATION:
66
+ return DrawDefaultVolume(location=return_location_plus())
67
+ elif is_source is SourceType.LOCATION:
68
+ return DrawDefaultVolume(
69
+ location=self.try_vial_location(
70
+ self.get_text(row, RegisterFlag.DRAW_LOCATION)
71
+ )
72
+ )
73
+ else:
74
+ vol = self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
75
+ if is_source == SourceType.DEFAULT:
76
+ return DrawDefaultLocation(amount=vol)
77
+ elif is_source is SourceType.SPECIFIC_LOCATION:
78
+ return Draw(amount=vol, location=return_location_plus())
79
+ elif is_source is SourceType.LOCATION:
80
+ return Draw(
81
+ amount=vol,
82
+ location=self.try_vial_location(
83
+ self.get_text(row, RegisterFlag.DRAW_LOCATION)
84
+ ),
85
+ )
48
86
  elif function == "Remote":
49
87
  return Remote(
50
88
  command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
51
- duration=self.get_num(row, RegisterFlag.REMOTE_DUR),
89
+ duration=int(self.get_num(row, RegisterFlag.REMOTE_DUR)),
52
90
  )
91
+ raise ValueError("No valid function found.")
53
92
 
54
93
  def load(self) -> InjectorTable:
55
94
  rows = self.get_num_rows()
56
95
  if rows.is_ok():
57
- return InjectorTable(
58
- functions=[
59
- self.get_row(i) for i in range(int(rows.ok_value.num_response))
60
- ]
61
- )
96
+ row_response = rows.value
97
+ if isinstance(row_response, Response):
98
+ return InjectorTable(
99
+ functions=[
100
+ self.get_row(i + 1)
101
+ for i in range(int(row_response.num_response))
102
+ ]
103
+ )
104
+ elif rows.is_err():
105
+ return InjectorTable(functions=[])
106
+ raise ValueError("Unexpected error")