pychemstation 0.4.7.dev3__tar.gz → 0.5.1__tar.gz

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 (41) hide show
  1. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/PKG-INFO +12 -11
  2. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/README.md +11 -10
  3. pychemstation-0.5.1/pychemstation/control/__init__.py +5 -0
  4. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/control/comm.py +29 -130
  5. pychemstation-0.5.1/pychemstation/control/hplc.py +150 -0
  6. {pychemstation-0.4.7.dev3/pychemstation/control → pychemstation-0.5.1/pychemstation/control/table}/__init__.py +1 -4
  7. {pychemstation-0.4.7.dev3/pychemstation/control → pychemstation-0.5.1/pychemstation/control/table}/method.py +68 -42
  8. {pychemstation-0.4.7.dev3/pychemstation/control → pychemstation-0.5.1/pychemstation/control/table}/sequence.py +64 -34
  9. pychemstation-0.5.1/pychemstation/control/table/table_controller.py +137 -0
  10. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/chromatogram.py +6 -5
  11. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/macro.py +1 -0
  12. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/method_types.py +4 -6
  13. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/sequence_types.py +19 -5
  14. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation.egg-info/PKG-INFO +12 -11
  15. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation.egg-info/SOURCES.txt +10 -4
  16. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation.egg-info/top_level.txt +1 -0
  17. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/setup.py +1 -1
  18. pychemstation-0.5.1/tests/__init__.py +0 -0
  19. pychemstation-0.5.1/tests/constants.py +12 -0
  20. pychemstation-0.5.1/tests/test_comm.py +66 -0
  21. pychemstation-0.5.1/tests/test_method.py +64 -0
  22. pychemstation-0.5.1/tests/test_sequence.py +102 -0
  23. pychemstation-0.4.7.dev3/pychemstation/control/table_controller.py +0 -75
  24. pychemstation-0.4.7.dev3/tests/test_chemstation_integration.py +0 -193
  25. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/LICENSE +0 -0
  26. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/__init__.py +0 -0
  27. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/analysis/__init__.py +0 -0
  28. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/analysis/base_spectrum.py +0 -0
  29. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/analysis/spec_utils.py +0 -0
  30. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/analysis/utils.py +0 -0
  31. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/generated/__init__.py +0 -0
  32. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/generated/dad_method.py +0 -0
  33. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/generated/pump_method.py +0 -0
  34. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/__init__.py +0 -0
  35. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/constants.py +0 -0
  36. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/parsing.py +0 -0
  37. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation/utils/table_types.py +0 -0
  38. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation.egg-info/dependency_links.txt +0 -0
  39. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pychemstation.egg-info/requires.txt +0 -0
  40. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/pyproject.toml +0 -0
  41. {pychemstation-0.4.7.dev3 → pychemstation-0.5.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pychemstation
3
- Version: 0.4.7.dev3
3
+ Version: 0.5.1
4
4
  Summary: Library to interact with Chemstation software, primarily used in Hein lab
5
5
  Home-page: https://gitlab.com/heingroup/pychemstation
6
6
  Author: Lucy Hao
@@ -59,26 +59,27 @@ HPLCTalk_Run
59
59
  ## Example Usage
60
60
 
61
61
  ```python
62
- from pychemstation.control import HPLCController, MethodController, SequenceController
62
+ from pychemstation.control import HPLCController
63
63
  import pandas as pd
64
64
 
65
65
  # these paths will be unique to your Chemstation setup
66
+ DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
67
+ DEFAULT_METHOD = "GENERAL-POROSHELL"
66
68
  DEFAULT_METHOD_DIR = "C:\\ChemStation\\1\\Methods\\"
67
69
  DATA_DIR = "C:\\Users\\Public\\Documents\\ChemStation\\2\\Data"
68
- DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\hplc-method-optimization\\tests"
70
+ SEQUENCE_DIR = "C:\\USERS\\PUBLIC\\DOCUMENTS\\CHEMSTATION\\2\\Sequence"
69
71
 
70
72
  hplc_controller = HPLCController(data_dir=DATA_DIR,
71
- comm_dir=DEFAULT_COMMAND_PATH)
72
- method_controller = MethodController(controller=hplc_controller,
73
- src=DEFAULT_METHOD_DIR)
73
+ comm_dir=DEFAULT_COMMAND_PATH,
74
+ sequence_dir=SEQUENCE_DIR,
75
+ method_dir=DEFAULT_METHOD_DIR)
74
76
 
75
77
  hplc_controller.preprun()
76
- method_controller.switch(method_name="General-Poroshell")
77
- method_controller.run(experiment_name="Run 10")
78
- data_ready = method_controller.data_ready()
78
+ hplc_controller.switch_method(method_name=DEFAULT_METHOD)
79
+ hplc_controller.run_method(experiment_name="Run 10")
80
+ data_status_valid, chrom = hplc_controller.get_last_run_method_data()
79
81
 
80
- if data_ready:
81
- chrom = method_controller.get_data()
82
+ if data_status_valid:
82
83
  # afterwards, save, analyze or plot the data!
83
84
  values = {"x": chrom.x, "y": chrom.y}
84
85
  chromatogram_data = pd.DataFrame.from_dict(values)
@@ -44,26 +44,27 @@ HPLCTalk_Run
44
44
  ## Example Usage
45
45
 
46
46
  ```python
47
- from pychemstation.control import HPLCController, MethodController, SequenceController
47
+ from pychemstation.control import HPLCController
48
48
  import pandas as pd
49
49
 
50
50
  # these paths will be unique to your Chemstation setup
51
+ DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
52
+ DEFAULT_METHOD = "GENERAL-POROSHELL"
51
53
  DEFAULT_METHOD_DIR = "C:\\ChemStation\\1\\Methods\\"
52
54
  DATA_DIR = "C:\\Users\\Public\\Documents\\ChemStation\\2\\Data"
53
- DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\hplc-method-optimization\\tests"
55
+ SEQUENCE_DIR = "C:\\USERS\\PUBLIC\\DOCUMENTS\\CHEMSTATION\\2\\Sequence"
54
56
 
55
57
  hplc_controller = HPLCController(data_dir=DATA_DIR,
56
- comm_dir=DEFAULT_COMMAND_PATH)
57
- method_controller = MethodController(controller=hplc_controller,
58
- src=DEFAULT_METHOD_DIR)
58
+ comm_dir=DEFAULT_COMMAND_PATH,
59
+ sequence_dir=SEQUENCE_DIR,
60
+ method_dir=DEFAULT_METHOD_DIR)
59
61
 
60
62
  hplc_controller.preprun()
61
- method_controller.switch(method_name="General-Poroshell")
62
- method_controller.run(experiment_name="Run 10")
63
- data_ready = method_controller.data_ready()
63
+ hplc_controller.switch_method(method_name=DEFAULT_METHOD)
64
+ hplc_controller.run_method(experiment_name="Run 10")
65
+ data_status_valid, chrom = hplc_controller.get_last_run_method_data()
64
66
 
65
- if data_ready:
66
- chrom = method_controller.get_data()
67
+ if data_status_valid:
67
68
  # afterwards, save, analyze or plot the data!
68
69
  values = {"x": chrom.x, "y": chrom.y}
69
70
  chromatogram_data = pd.DataFrame.from_dict(values)
@@ -0,0 +1,5 @@
1
+ """
2
+ .. include:: README.md
3
+ """
4
+ from .comm import CommunicationController
5
+ from .hplc import HPLCController
@@ -1,5 +1,5 @@
1
1
  """
2
- Module to provide API for the remote control of the Agilent HPLC systems.
2
+ Module to provide API for the communication with Agilent HPLC systems.
3
3
 
4
4
  HPLCController sends commands to Chemstation software via a command file.
5
5
  Answers are received via reply file. On the Chemstation side, a custom
@@ -10,38 +10,29 @@ been processed.
10
10
  Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
11
11
  """
12
12
 
13
- import logging
14
13
  import os
15
14
  import time
16
15
 
17
- import polling
18
-
19
- from ..utils.chromatogram import AgilentHPLCChromatogram
20
16
  from ..utils.constants import MAX_CMD_NO
21
17
  from ..utils.macro import *
22
18
  from ..utils.method_types import *
23
19
 
24
20
 
25
- class HPLCController:
21
+ class CommunicationController:
26
22
  """
27
- Class to control Agilent HPLC systems via Chemstation Macros.
23
+ Class that communicates with Agilent using Macros
28
24
  """
29
25
 
30
26
  def __init__(
31
27
  self,
32
28
  comm_dir: str,
33
- data_dir: str,
34
29
  cmd_file: str = "cmd",
35
30
  reply_file: str = "reply",
36
31
  ):
37
- """Initialize HPLC controller. The `hplc_talk.mac` macro file must be loaded in the Chemstation software.
38
- `comm_dir` must match the file path in the macro file.
39
-
40
- :param comm_dir: Name of directory for communication, where ChemStation will read and write from. Can be any existing directory.
41
- :param data_dir: Name of directory that ChemStation saves run data. Must be accessible by ChemStation.
32
+ """
33
+ :param comm_dir:
42
34
  :param cmd_file: Name of command file
43
35
  :param reply_file: Name of reply file
44
- :raises FileNotFoundError: If either `data_dir`, `method_dir` or `comm_dir` is not a valid directory.
45
36
  """
46
37
  if os.path.isdir(comm_dir):
47
38
  self.cmd_file = os.path.join(comm_dir, cmd_file)
@@ -51,75 +42,47 @@ class HPLCController:
51
42
  raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
52
43
  self._most_recent_hplc_status = None
53
44
 
54
- if os.path.isdir(data_dir):
55
- self.data_dir = data_dir
56
- else:
57
- raise FileNotFoundError(f"data_dir: {data_dir} not found.")
58
-
59
- self.spectra = {
60
- "A": AgilentHPLCChromatogram(self.data_dir),
61
- "B": AgilentHPLCChromatogram(self.data_dir),
62
- "C": AgilentHPLCChromatogram(self.data_dir),
63
- "D": AgilentHPLCChromatogram(self.data_dir),
64
- }
65
-
66
- self.data_files: list[str] = []
67
- self.internal_variables: list[dict[str, str]] = []
68
-
69
45
  # Create files for Chemstation to communicate with Python
70
46
  open(self.cmd_file, "a").close()
71
47
  open(self.reply_file, "a").close()
72
48
 
73
- self.logger = logging.getLogger("hplc_logger")
74
- self.logger.addHandler(logging.NullHandler())
75
-
76
49
  self.reset_cmd_counter()
77
50
 
78
- self.logger.info("HPLC Controller initialized.")
51
+ def get_status(self) -> list[Union[HPLCRunningStatus, HPLCAvailStatus, HPLCErrorStatus]]:
52
+ """Get device status(es).
53
+
54
+ :return: list of ChemStation's current status
55
+ """
56
+ self.send(Command.GET_STATUS_CMD)
57
+ time.sleep(1)
58
+
59
+ try:
60
+ parsed_response = self.receive().splitlines()[1].split()[1:]
61
+ recieved_status = [str_to_status(res) for res in parsed_response]
62
+ self._most_recent_hplc_status = recieved_status[0]
63
+ return recieved_status
64
+ except IOError:
65
+ return [HPLCErrorStatus.NORESPONSE]
66
+ except IndexError:
67
+ return [HPLCErrorStatus.MALFORMED]
79
68
 
80
- def _set_status(self):
69
+ def set_status(self):
81
70
  """Updates current status of HPLC machine"""
82
- self._most_recent_hplc_status = self.status()[0]
71
+ self._most_recent_hplc_status = self.get_status()[0]
83
72
 
84
- def _check_data_status(self) -> bool:
73
+ def _check_data_status(self, data_path: str) -> bool:
85
74
  """Checks if HPLC machine is in an available state, meaning a state that data is not being written.
86
75
 
87
76
  :return: whether the HPLC machine is in a safe state to retrieve data back."""
88
77
  old_status = self._most_recent_hplc_status
89
- self._set_status()
90
- file_exists = os.path.exists(self.data_files[-1]) if len(self.data_files) > 0 else False
78
+ self.set_status()
79
+ file_exists = os.path.exists(data_path)
91
80
  done_writing_data = isinstance(self._most_recent_hplc_status,
92
81
  HPLCAvailStatus) and old_status != self._most_recent_hplc_status and file_exists
93
82
  return done_writing_data
94
83
 
95
- def check_hplc_ready_with_data(self) -> bool:
96
- """Checks if ChemStation has finished writing data and can be read back.
97
-
98
- :param method: if you are running a method and want to read back data, the timeout period will be adjusted to be longer than the method's runtime
99
-
100
- :return: Return True if data can be read back, else False.
101
- """
102
- self._set_status()
103
-
104
- timeout = 10 * 60
105
- hplc_run_done = polling.poll(
106
- lambda: self._check_data_status(),
107
- timeout=timeout,
108
- step=30
109
- )
110
-
111
- return hplc_run_done
112
-
113
- def get_spectrum(self):
114
- """ Load last chromatogram for any channel in spectra dictionary."""
115
- last_file = self.data_files[-1] if len(self.data_files) > 0 else None
116
-
117
- if last_file is None:
118
- raise IndexError
119
-
120
- for channel, spec in self.controller.spectra.items():
121
- spec.load_spectrum(data_path=last_file, channel=channel)
122
- self.logger.info("%s chromatogram loaded.", channel)
84
+ def check_data(self, data_path: str) -> bool:
85
+ return self._check_data_status(data_path)
123
86
 
124
87
  def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
125
88
  """Low-level execution primitive. Sends a command string to HPLC.
@@ -137,10 +100,8 @@ class HPLCController:
137
100
  cmd_file.write(f"{cmd_no} {cmd}")
138
101
  except IOError as e:
139
102
  err = e
140
- self.logger.warning("Failed to send command; trying again.")
141
103
  continue
142
104
  else:
143
- self.logger.info("Sent command #%d: %s.", cmd_no, cmd)
144
105
  return
145
106
  else:
146
107
  raise IOError(f"Failed to send command #{cmd_no}: {cmd}.") from err
@@ -162,7 +123,6 @@ class HPLCController:
162
123
  response = reply_file.read()
163
124
  except OSError as e:
164
125
  err = e
165
- self.logger.warning("Failed to read from reply file; trying again.")
166
126
  continue
167
127
 
168
128
  try:
@@ -170,19 +130,12 @@ class HPLCController:
170
130
  response_no = int(first_line.split()[0])
171
131
  except IndexError as e:
172
132
  err = e
173
- self.logger.warning("Malformed response %s; trying again.", response)
174
133
  continue
175
134
 
176
135
  # check that response corresponds to sent command
177
136
  if response_no == cmd_no:
178
- self.logger.info("Reply: \n%s", response)
179
137
  return response
180
138
  else:
181
- self.logger.warning(
182
- "Response #: %d != command #: %d; trying again.",
183
- response_no,
184
- cmd_no,
185
- )
186
139
  continue
187
140
  else:
188
141
  raise IOError(f"Failed to receive reply to command #{cmd_no}.") from err
@@ -217,60 +170,6 @@ class HPLCController:
217
170
  self._receive(cmd_no=MAX_CMD_NO + 1)
218
171
  self.cmd_no = 0
219
172
 
220
- self.logger.debug("Reset command counter")
221
-
222
- def sleep(self, seconds: int):
223
- """Tells the HPLC to wait for a specified number of seconds.
224
-
225
- :param seconds: number of seconds to wait
226
- """
227
- self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
228
- self.logger.debug("Sleep command sent.")
229
-
230
- def standby(self):
231
- """Switches all modules in standby mode. All lamps and pumps are switched off."""
232
- self.send(Command.STANDBY_CMD)
233
- self.logger.debug("Standby command sent.")
234
-
235
- def preprun(self):
236
- """ Prepares all modules for run. All lamps and pumps are switched on."""
237
- self.send(Command.PREPRUN_CMD)
238
- self.logger.debug("PrepRun command sent.")
239
-
240
- def status(self) -> list[Union[HPLCRunningStatus, HPLCAvailStatus, HPLCErrorStatus]]:
241
- """Get device status(es).
242
-
243
- :return: list of ChemStation's current status
244
- """
245
- self.send(Command.GET_STATUS_CMD)
246
- time.sleep(1)
247
-
248
- try:
249
- parsed_response = self.receive().splitlines()[1].split()[1:]
250
- except IOError:
251
- return [HPLCErrorStatus.NORESPONSE]
252
- except IndexError:
253
- return [HPLCErrorStatus.MALFORMED]
254
- recieved_status = [str_to_status(res) for res in parsed_response]
255
- self._most_recent_hplc_status = recieved_status[0]
256
- return recieved_status
257
-
258
173
  def stop_macro(self):
259
174
  """Stops Macro execution. Connection will be lost."""
260
175
  self.send(Command.STOP_MACRO_CMD)
261
-
262
- def lamp_on(self):
263
- """Turns the UV lamp on."""
264
- self.send(Command.LAMP_ON_CMD)
265
-
266
- def lamp_off(self):
267
- """Turns the UV lamp off."""
268
- self.send(Command.LAMP_OFF_CMD)
269
-
270
- def pump_on(self):
271
- """Turns on the pump on."""
272
- self.send(Command.PUMP_ON_CMD)
273
-
274
- def pump_off(self):
275
- """Turns the pump off."""
276
- self.send(Command.PUMP_OFF_CMD)
@@ -0,0 +1,150 @@
1
+ """
2
+ Module to provide API for higher-level HPLC actions.
3
+
4
+ Authors: Lucy Hao
5
+ """
6
+
7
+ from typing import Union, Optional
8
+
9
+ from ..control import CommunicationController
10
+ from ..control.table import MethodController, SequenceController
11
+ from ..utils.chromatogram import AgilentHPLCChromatogram
12
+ from ..utils.macro import Command, HPLCRunningStatus, HPLCAvailStatus, HPLCErrorStatus
13
+ from ..utils.method_types import MethodTimetable
14
+ from ..utils.sequence_types import SequenceTable, SequenceEntry
15
+
16
+
17
+ class HPLCController:
18
+ def __init__(self,
19
+ comm_dir: str,
20
+ data_dir: str,
21
+ method_dir: str,
22
+ sequence_dir: str):
23
+ """Initialize HPLC controller. The `hplc_talk.mac` macro file must be loaded in the Chemstation software.
24
+ `comm_dir` must match the file path in the macro file.
25
+
26
+ :param comm_dir: Name of directory for communication, where ChemStation will read and write from. Can be any existing directory.
27
+ :raises FileNotFoundError: If either `data_dir`, `method_dir` or `comm_dir` is not a valid directory.
28
+ """
29
+ self.comm = CommunicationController(comm_dir=comm_dir)
30
+ self.method_controller = MethodController(controller=self.comm, src=method_dir, data_dir=data_dir)
31
+ self.sequence_controller = SequenceController(controller=self.comm, src=sequence_dir, data_dir=data_dir)
32
+
33
+ def send(self, cmd: Union[Command, str]):
34
+ self.comm.send(cmd)
35
+
36
+ def receive(self) -> str:
37
+ return self.comm.receive()
38
+
39
+ def status(self) -> list[HPLCRunningStatus | HPLCAvailStatus | HPLCErrorStatus]:
40
+ return self.comm.get_status()
41
+
42
+ def switch_method(self, method_name: str):
43
+ """
44
+ Allows the user to switch between pre-programmed methods. No need to append '.M'
45
+ to the end of the method name. For example. for the method named 'General-Poroshell.M',
46
+ only 'General-Poroshell' is needed.
47
+
48
+ :param method_name: any available method in Chemstation method directory
49
+ :raise IndexError: Response did not have expected format. Try again.
50
+ :raise AssertionError: The desired method is not selected. Try again.
51
+ """
52
+ self.method_controller.switch(method_name)
53
+
54
+ def switch_sequence(self, sequence_name: str):
55
+ """
56
+ Allows the user to switch between pre-programmed sequences. The sequence name does not need the '.S' extension.
57
+ For example. for the method named 'mySeq.S', only 'mySeq' is needed.
58
+
59
+ :param seq_name: The name of the sequence file
60
+ """
61
+ self.sequence_controller.switch(sequence_name)
62
+
63
+ def run_method(self, experiment_name: str):
64
+ """
65
+ This is the preferred method to trigger a run.
66
+ Starts the currently selected method, storing data
67
+ under the <data_dir>/<experiment_name>.D folder.
68
+ The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
69
+ Device must be ready.
70
+
71
+ :param experiment_name: Name of the experiment
72
+ """
73
+ self.method_controller.run(experiment_name)
74
+
75
+ def run_sequence(self, sequence_table: SequenceTable):
76
+ """
77
+ Starts the currently loaded sequence, storing data
78
+ under the <data_dir>/<sequence table name> folder.
79
+ Device must be ready.
80
+
81
+ :param sequence_table:
82
+ """
83
+ self.sequence_controller.run(sequence_table)
84
+
85
+ def edit_method(self, updated_method: MethodTimetable):
86
+ """Updated the currently loaded method in ChemStation with provided values.
87
+
88
+ :param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
89
+ """
90
+ self.method_controller.edit(updated_method)
91
+
92
+ def edit_sequence(self, updated_sequence: SequenceTable):
93
+ """
94
+ Updates the currently loaded sequence table with the provided table. This method will delete the existing sequence table and remake it.
95
+ If you would only like to edit a single row of a sequence table, use `edit_sequence_row` instead.
96
+
97
+ :param sequence_table:
98
+ """
99
+ self.sequence_controller.edit(updated_sequence)
100
+
101
+ def edit_sequence_row(self, row: SequenceEntry, num: int):
102
+ """
103
+ Edits a row in the sequence table. Assumes the row already exists.
104
+
105
+ :param row: sequence row entry with updated information
106
+ :param row_num: the row to edit, based on -1-based indexing
107
+ """
108
+ self.sequence_controller.edit_row(row, num)
109
+
110
+ def get_last_run_method_data(self) -> Optional[dict[str, AgilentHPLCChromatogram]]:
111
+ """
112
+ Returns the last run method data.
113
+ """
114
+ data_valid, data = self.method_controller.get_data()
115
+ if data_valid:
116
+ return data
117
+ return None
118
+
119
+ def get_last_run_sequence_data(self) -> Optional[list[dict[str, AgilentHPLCChromatogram]]]:
120
+ """
121
+ Returns data for all rows in the last run sequence data.
122
+ """
123
+ data_valid, data = self.sequence_controller.get_data()
124
+ if data_valid:
125
+ return data
126
+ return None
127
+
128
+ def standby(self):
129
+ """Switches all modules in standby mode. All lamps and pumps are switched off."""
130
+ self.send(Command.STANDBY_CMD)
131
+
132
+ def preprun(self):
133
+ """ Prepares all modules for run. All lamps and pumps are switched on."""
134
+ self.send(Command.PREPRUN_CMD)
135
+
136
+ def lamp_on(self):
137
+ """Turns the UV lamp on."""
138
+ self.send(Command.LAMP_ON_CMD)
139
+
140
+ def lamp_off(self):
141
+ """Turns the UV lamp off."""
142
+ self.send(Command.LAMP_OFF_CMD)
143
+
144
+ def pump_on(self):
145
+ """Turns on the pump on."""
146
+ self.send(Command.PUMP_ON_CMD)
147
+
148
+ def pump_off(self):
149
+ """Turns the pump off."""
150
+ self.send(Command.PUMP_OFF_CMD)
@@ -1,6 +1,3 @@
1
- """
2
- .. include:: README.md
3
- """
4
- from .comm import HPLCController
5
1
  from .method import MethodController
6
2
  from .sequence import SequenceController
3
+ from .table_controller import TableController
@@ -1,15 +1,17 @@
1
1
  import os
2
2
  import time
3
+ from typing import Optional, Any
3
4
 
4
5
  from xsdata.formats.dataclass.parsers import XmlParser
5
6
 
6
- from ..control.table_controller import TableController, HPLCController
7
- from ..generated import DadMethod, PumpMethod
8
- from ..utils.chromatogram import TIME_FORMAT
9
- from ..utils.constants import METHOD_TIMETABLE
10
- from ..utils.macro import Command
11
- from ..utils.method_types import MethodTimetable, HPLCMethodParams, Param, PType, TimeTableEntry
12
- from ..utils.table_types import RegisterFlag, TableOperation
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
11
+ from ...utils.constants import METHOD_TIMETABLE
12
+ from ...utils.macro import Command
13
+ from ...utils.method_types import PType, TimeTableEntry, Param, MethodTimetable, HPLCMethodParams
14
+ from ...utils.table_types import RegisterFlag, TableOperation
13
15
 
14
16
 
15
17
  class MethodController(TableController):
@@ -17,11 +19,12 @@ class MethodController(TableController):
17
19
  Class containing method related logic
18
20
  """
19
21
 
20
- def __init__(self, controller: HPLCController, src: str):
21
- super().__init__(controller, src)
22
+ def __init__(self, controller: CommunicationController, src: str, data_dir: str):
23
+ super().__init__(controller, src, data_dir)
22
24
 
23
25
  def is_loaded(self, method_name: str):
24
- """Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
26
+ """
27
+ Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
25
28
 
26
29
  :param method_name: a Chemstation method
27
30
  :return: True if method is already loaded
@@ -31,7 +34,8 @@ class MethodController(TableController):
31
34
  return method_name in parsed_response
32
35
 
33
36
  def switch(self, method_name: str):
34
- """Allows the user to switch between pre-programmed methods. No need to append '.M'
37
+ """
38
+ Allows the user to switch between pre-programmed methods. No need to append '.M'
35
39
  to the end of the method name. For example. for the method named 'General-Poroshell.M',
36
40
  only 'General-Poroshell' is needed.
37
41
 
@@ -56,26 +60,9 @@ class MethodController(TableController):
56
60
 
57
61
  assert parsed_response == f"{method_name}.M", "Switching Methods failed."
58
62
 
59
- def run(self, experiment_name: str):
60
- """This is the preferred method to trigger a run.
61
- Starts the currently selected method, storing data
62
- under the <data_dir>/<experiment_name>.D folder.
63
- The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
64
- Device must be ready.
65
-
66
- :param experiment_name: Name of the experiment
67
- """
68
- timestamp = time.strftime(TIME_FORMAT)
69
-
70
- self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.src,
71
- experiment_name=experiment_name,
72
- timestamp=timestamp))
73
-
74
- folder_name = f"{experiment_name}_{timestamp}.D"
75
- self.controller.data_files.append(os.path.join(self.src, folder_name))
76
-
77
63
  def load(self, method_name: str):
78
- """Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
64
+ """
65
+ Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
79
66
  organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
80
67
  only two solvents are being used.
81
68
 
@@ -92,8 +79,8 @@ class MethodController(TableController):
92
79
  method = parser.parse(method_path, PumpMethod)
93
80
  dad = parser.parse(dad_path, DadMethod)
94
81
 
95
- organic_modifier = None
96
- aq_modifier = None
82
+ organic_modifier: Optional[SolventElement] = None
83
+ aq_modifier: Optional[SolventElement] = None
97
84
 
98
85
  if len(method.solvent_composition.solvent_element) == 2:
99
86
  for solvent in method.solvent_composition.solvent_element:
@@ -142,16 +129,22 @@ class MethodController(TableController):
142
129
 
143
130
  :param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
144
131
  """
145
- initial_organic_modifier: Param = updated_method.first_row.organic_modifier
146
- max_time: Param = updated_method.first_row.maximum_run_time
147
- temp: Param = updated_method.first_row.temperature
148
- injvol: Param = updated_method.first_row.inj_vol
149
- equalib_time: Param = updated_method.first_row.equ_time
150
- flow: Param = updated_method.first_row.flow
132
+ initial_organic_modifier: Param = Param(val=updated_method.first_row.organic_modifier,
133
+ chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
134
+ ptype=PType.NUM)
135
+ max_time: Param = Param(val=updated_method.first_row.maximum_run_time,
136
+ chemstation_key=RegisterFlag.MAX_TIME,
137
+ ptype=PType.NUM)
138
+ flow: Param = Param(val=updated_method.first_row.flow,
139
+ chemstation_key=RegisterFlag.FLOW,
140
+ ptype=PType.NUM)
141
+ temperature: Param = Param(val=updated_method.first_row.temperature,
142
+ chemstation_key=[RegisterFlag.COLUMN_OVEN_TEMP1,
143
+ RegisterFlag.COLUMN_OVEN_TEMP2],
144
+ ptype=PType.NUM)
151
145
 
152
146
  # Method settings required for all runs
153
- self.send(TableOperation.DELETE_TABLE.value.format(register=METHOD_TIMETABLE.register,
154
- table_name=METHOD_TIMETABLE.name, ))
147
+ self.delete_table(METHOD_TIMETABLE)
155
148
  self._update_param(initial_organic_modifier)
156
149
  self._update_param(flow)
157
150
  self._update_param(Param(val="Set",
@@ -210,7 +203,8 @@ class MethodController(TableController):
210
203
  self._get_table_rows(METHOD_TIMETABLE)
211
204
 
212
205
  def _update_param(self, method_param: Param):
213
- """Change a method parameter, changes what is visibly seen in Chemstation GUI. (changes the first row in the timetable)
206
+ """Change a method parameter, changes what is visibly seen in Chemstation GUI.
207
+ (changes the first row in the timetable)
214
208
 
215
209
  :param method_param: a parameter to update for currently loaded method.
216
210
  """
@@ -228,5 +222,37 @@ class MethodController(TableController):
228
222
  time.sleep(2)
229
223
 
230
224
  def stop(self):
231
- """Stops the run. A dialog window will pop up and manual intervention may be required."""
225
+ """
226
+ Stops the method run. A dialog window will pop up and manual intervention may be required.\
227
+ """
232
228
  self.send(Command.STOP_METHOD_CMD)
229
+
230
+ def run(self, experiment_name: str):
231
+ """
232
+ This is the preferred method to trigger a run.
233
+ Starts the currently selected method, storing data
234
+ under the <data_dir>/<experiment_name>.D folder.
235
+ The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
236
+ Device must be ready.
237
+
238
+ :param experiment_name: Name of the experiment
239
+ """
240
+ timestamp = time.strftime(TIME_FORMAT)
241
+
242
+ self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dir,
243
+ experiment_name=experiment_name,
244
+ timestamp=timestamp))
245
+
246
+ folder_name = f"{experiment_name}_{timestamp}.D"
247
+ self.data_files.append(os.path.join(self.data_dir, folder_name))
248
+
249
+ def retrieve_recent_data_files(self) -> str:
250
+ return self.data_files[-1]
251
+
252
+ def get_data(self) -> tuple[bool, Any]:
253
+ data_ready = self.data_ready()
254
+ if data_ready:
255
+ self.get_spectrum(self.data_files[-1])
256
+ return data_ready, self.spectra
257
+ else:
258
+ return False, None