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.
- pychemstation/analysis/__init__.py +8 -1
- pychemstation/analysis/chromatogram.py +20 -0
- pychemstation/analysis/process_report.py +125 -63
- pychemstation/control/__init__.py +2 -0
- pychemstation/control/controllers/__init__.py +2 -3
- pychemstation/control/controllers/abc_tables/device.py +15 -0
- pychemstation/control/controllers/abc_tables/run.py +228 -0
- pychemstation/control/controllers/abc_tables/table.py +221 -0
- pychemstation/control/controllers/comm.py +25 -106
- pychemstation/control/controllers/data_aq/__init__.py +4 -0
- pychemstation/control/controllers/{tables → data_aq}/method.py +52 -95
- pychemstation/control/controllers/{tables → data_aq}/sequence.py +199 -141
- pychemstation/control/controllers/devices/__init__.py +3 -0
- pychemstation/control/controllers/devices/injector.py +69 -24
- pychemstation/control/hplc.py +15 -17
- pychemstation/utils/injector_types.py +23 -3
- pychemstation/utils/macro.py +2 -2
- pychemstation/utils/method_types.py +1 -1
- pychemstation/utils/mocking/__init__.py +0 -0
- pychemstation/utils/mocking/abc_comm.py +160 -0
- pychemstation/utils/mocking/mock_comm.py +5 -0
- pychemstation/utils/mocking/mock_hplc.py +2 -0
- pychemstation/utils/sequence_types.py +19 -0
- pychemstation/utils/table_types.py +6 -0
- pychemstation/utils/tray_types.py +36 -1
- {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/METADATA +4 -4
- pychemstation-0.10.6.dist-info/RECORD +42 -0
- pychemstation/control/controllers/devices/device.py +0 -49
- pychemstation/control/controllers/tables/ms.py +0 -24
- pychemstation/control/controllers/tables/table.py +0 -375
- pychemstation-0.10.4.dist-info/RECORD +0 -37
- /pychemstation/control/controllers/{tables → abc_tables}/__init__.py +0 -0
- {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,221 @@
|
|
1
|
+
"""
|
2
|
+
Abstract module containing shared logic for Method and Sequence tables.
|
3
|
+
|
4
|
+
Authors: Lucy Hao
|
5
|
+
"""
|
6
|
+
|
7
|
+
from __future__ import annotations
|
8
|
+
|
9
|
+
import abc
|
10
|
+
import warnings
|
11
|
+
from typing import Optional, Union
|
12
|
+
|
13
|
+
from result import Err, Result
|
14
|
+
|
15
|
+
from ....control.controllers.comm import CommunicationController
|
16
|
+
from ....utils.macro import Command, Response
|
17
|
+
from ....utils.method_types import MethodDetails
|
18
|
+
from ....utils.sequence_types import SequenceTable
|
19
|
+
from ....utils.table_types import RegisterFlag, Table, TableOperation
|
20
|
+
|
21
|
+
TableType = Union[MethodDetails, SequenceTable]
|
22
|
+
|
23
|
+
|
24
|
+
class ABCTableController(abc.ABC):
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
controller: Optional[CommunicationController],
|
28
|
+
table: Table,
|
29
|
+
):
|
30
|
+
warnings.warn(
|
31
|
+
"This abstract class is not meant to be initialized. Use MethodController or SequenceController."
|
32
|
+
)
|
33
|
+
self.controller = controller
|
34
|
+
self.table_locator = table
|
35
|
+
self.table_state: Optional[TableType] = None
|
36
|
+
|
37
|
+
def receive(self) -> Result[Response, str]:
|
38
|
+
if self.controller:
|
39
|
+
for _ in range(10):
|
40
|
+
try:
|
41
|
+
return self.controller.receive()
|
42
|
+
except IndexError:
|
43
|
+
continue
|
44
|
+
return Err("Could not parse response")
|
45
|
+
else:
|
46
|
+
raise ValueError("Controller is offline!")
|
47
|
+
|
48
|
+
def send(self, cmd: Union[Command, str]):
|
49
|
+
if not self.controller:
|
50
|
+
raise RuntimeError(
|
51
|
+
"Communication controller must be initialized before sending command. It is currently in offline mode."
|
52
|
+
)
|
53
|
+
self.controller.send(cmd)
|
54
|
+
|
55
|
+
def sleepy_send(self, cmd: Union[Command, str]):
|
56
|
+
if self.controller:
|
57
|
+
self.controller.sleepy_send(cmd)
|
58
|
+
else:
|
59
|
+
raise ValueError("Controller is offline")
|
60
|
+
|
61
|
+
def sleep(self, seconds: int):
|
62
|
+
"""
|
63
|
+
Tells the HPLC to wait for a specified number of seconds.
|
64
|
+
|
65
|
+
:param seconds: number of seconds to wait
|
66
|
+
"""
|
67
|
+
self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
|
68
|
+
|
69
|
+
def get_num(self, row: int, col_name: RegisterFlag) -> Union[int, float]:
|
70
|
+
if self.controller:
|
71
|
+
return self.controller.get_num_val(
|
72
|
+
TableOperation.GET_ROW_VAL.value.format(
|
73
|
+
register=self.table_locator.register,
|
74
|
+
table_name=self.table_locator.name,
|
75
|
+
row=row,
|
76
|
+
col_name=col_name.value,
|
77
|
+
)
|
78
|
+
)
|
79
|
+
else:
|
80
|
+
raise ValueError("Controller is offline")
|
81
|
+
|
82
|
+
def get_text(self, row: int, col_name: RegisterFlag) -> str:
|
83
|
+
if self.controller:
|
84
|
+
return self.controller.get_text_val(
|
85
|
+
TableOperation.GET_ROW_TEXT.value.format(
|
86
|
+
register=self.table_locator.register,
|
87
|
+
table_name=self.table_locator.name,
|
88
|
+
row=row,
|
89
|
+
col_name=col_name.value,
|
90
|
+
)
|
91
|
+
)
|
92
|
+
else:
|
93
|
+
raise ValueError("Controller is offline")
|
94
|
+
|
95
|
+
def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
|
96
|
+
self.sleepy_send(
|
97
|
+
TableOperation.NEW_COL_VAL.value.format(
|
98
|
+
register=self.table_locator.register,
|
99
|
+
table_name=self.table_locator.name,
|
100
|
+
col_name=col_name,
|
101
|
+
val=val,
|
102
|
+
)
|
103
|
+
)
|
104
|
+
|
105
|
+
def add_new_col_text(self, col_name: RegisterFlag, val: str):
|
106
|
+
self.sleepy_send(
|
107
|
+
TableOperation.NEW_COL_TEXT.value.format(
|
108
|
+
register=self.table_locator.register,
|
109
|
+
table_name=self.table_locator.name,
|
110
|
+
col_name=col_name,
|
111
|
+
val=val,
|
112
|
+
)
|
113
|
+
)
|
114
|
+
|
115
|
+
def _edit_row_num(
|
116
|
+
self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
|
117
|
+
):
|
118
|
+
if row:
|
119
|
+
num_rows = self.get_num_rows()
|
120
|
+
if num_rows.is_ok():
|
121
|
+
if num_rows.value.num_response < row:
|
122
|
+
raise ValueError("Not enough rows to edit!")
|
123
|
+
|
124
|
+
self.sleepy_send(
|
125
|
+
TableOperation.EDIT_ROW_VAL.value.format(
|
126
|
+
register=self.table_locator.register,
|
127
|
+
table_name=self.table_locator.name,
|
128
|
+
row=row if row is not None else "Rows",
|
129
|
+
col_name=col_name,
|
130
|
+
val=val,
|
131
|
+
)
|
132
|
+
)
|
133
|
+
|
134
|
+
def _edit_row_text(
|
135
|
+
self, col_name: RegisterFlag, val: str, row: Optional[int] = None
|
136
|
+
):
|
137
|
+
if row:
|
138
|
+
num_rows = self.get_num_rows()
|
139
|
+
if num_rows.is_ok():
|
140
|
+
if num_rows.value.num_response < row:
|
141
|
+
raise ValueError("Not enough rows to edit!")
|
142
|
+
|
143
|
+
self.sleepy_send(
|
144
|
+
TableOperation.EDIT_ROW_TEXT.value.format(
|
145
|
+
register=self.table_locator.register,
|
146
|
+
table_name=self.table_locator.name,
|
147
|
+
row=row if row is not None else "Rows",
|
148
|
+
col_name=col_name,
|
149
|
+
val=val,
|
150
|
+
)
|
151
|
+
)
|
152
|
+
|
153
|
+
@abc.abstractmethod
|
154
|
+
def get_row(self, row: int):
|
155
|
+
pass
|
156
|
+
|
157
|
+
def delete_row(self, row: int):
|
158
|
+
self.sleepy_send(
|
159
|
+
TableOperation.DELETE_ROW.value.format(
|
160
|
+
register=self.table_locator.register,
|
161
|
+
table_name=self.table_locator.name,
|
162
|
+
row=row,
|
163
|
+
)
|
164
|
+
)
|
165
|
+
|
166
|
+
def add_row(self):
|
167
|
+
"""
|
168
|
+
Adds a row to the provided table for currently loaded method or sequence.
|
169
|
+
"""
|
170
|
+
self.sleepy_send(
|
171
|
+
TableOperation.NEW_ROW.value.format(
|
172
|
+
register=self.table_locator.register, table_name=self.table_locator.name
|
173
|
+
)
|
174
|
+
)
|
175
|
+
|
176
|
+
def delete_table(self):
|
177
|
+
"""
|
178
|
+
Deletes the table for the current loaded method or sequence.
|
179
|
+
"""
|
180
|
+
self.sleepy_send(
|
181
|
+
TableOperation.DELETE_TABLE.value.format(
|
182
|
+
register=self.table_locator.register, table_name=self.table_locator.name
|
183
|
+
)
|
184
|
+
)
|
185
|
+
|
186
|
+
def new_table(self):
|
187
|
+
"""
|
188
|
+
Creates the table for the currently loaded method or sequence.
|
189
|
+
"""
|
190
|
+
self.send(
|
191
|
+
TableOperation.CREATE_TABLE.value.format(
|
192
|
+
register=self.table_locator.register, table_name=self.table_locator.name
|
193
|
+
)
|
194
|
+
)
|
195
|
+
|
196
|
+
def get_num_rows(self) -> Result[Response, str]:
|
197
|
+
self.send(
|
198
|
+
TableOperation.GET_NUM_ROWS.value.format(
|
199
|
+
register=self.table_locator.register,
|
200
|
+
table_name=self.table_locator.name,
|
201
|
+
col_name=RegisterFlag.NUM_ROWS,
|
202
|
+
)
|
203
|
+
)
|
204
|
+
self.send(
|
205
|
+
Command.GET_ROWS_CMD.value.format(
|
206
|
+
register=self.table_locator.register,
|
207
|
+
table_name=self.table_locator.name,
|
208
|
+
col_name=RegisterFlag.NUM_ROWS,
|
209
|
+
)
|
210
|
+
)
|
211
|
+
if self.controller:
|
212
|
+
res = self.controller.receive()
|
213
|
+
else:
|
214
|
+
raise ValueError("Controller is offline")
|
215
|
+
|
216
|
+
if res.is_ok():
|
217
|
+
self.send("Sleep 0.1")
|
218
|
+
self.send("Print Rows")
|
219
|
+
return res
|
220
|
+
else:
|
221
|
+
return Err("No rows could be read.")
|
@@ -10,7 +10,6 @@ been processed.
|
|
10
10
|
Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
|
11
11
|
"""
|
12
12
|
|
13
|
-
import os
|
14
13
|
import time
|
15
14
|
from typing import Optional, Union
|
16
15
|
|
@@ -18,27 +17,24 @@ from result import Err, Ok, Result
|
|
18
17
|
|
19
18
|
from ...utils.macro import (
|
20
19
|
str_to_status,
|
21
|
-
HPLCAvailStatus,
|
22
20
|
HPLCErrorStatus,
|
23
21
|
Command,
|
24
22
|
Status,
|
25
|
-
Response,
|
26
23
|
)
|
24
|
+
from ...utils.mocking.abc_comm import ABCCommunicationController
|
27
25
|
|
28
26
|
|
29
|
-
class CommunicationController:
|
27
|
+
class CommunicationController(ABCCommunicationController):
|
30
28
|
"""
|
31
29
|
Class that communicates with Agilent using Macros
|
32
30
|
"""
|
33
31
|
|
34
|
-
# maximum command number
|
35
|
-
MAX_CMD_NO = 255
|
36
|
-
|
37
32
|
def __init__(
|
38
33
|
self,
|
39
34
|
comm_dir: str,
|
40
35
|
cmd_file: str = "cmd",
|
41
36
|
reply_file: str = "reply",
|
37
|
+
offline: bool = False,
|
42
38
|
debug: bool = False,
|
43
39
|
):
|
44
40
|
"""
|
@@ -47,26 +43,10 @@ class CommunicationController:
|
|
47
43
|
:param reply_file: Name of reply file
|
48
44
|
:param debug: whether to save log of sent commands
|
49
45
|
"""
|
50
|
-
|
51
|
-
if os.path.isdir(comm_dir):
|
52
|
-
self.cmd_file = os.path.join(comm_dir, cmd_file)
|
53
|
-
self.reply_file = os.path.join(comm_dir, reply_file)
|
54
|
-
self.cmd_no = 0
|
55
|
-
else:
|
56
|
-
raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
|
57
|
-
self._most_recent_hplc_status: Optional[Status] = None
|
58
|
-
|
59
|
-
# Create files for Chemstation to communicate with Python
|
60
|
-
open(self.cmd_file, "a").close()
|
61
|
-
open(self.reply_file, "a").close()
|
62
|
-
|
63
|
-
self.reset_cmd_counter()
|
64
|
-
|
65
|
-
# Initialize row counter for table operations
|
66
|
-
self.send("Local Rows")
|
46
|
+
super().__init__(comm_dir, cmd_file, reply_file, offline, debug)
|
67
47
|
|
68
48
|
def get_num_val(self, cmd: str) -> Union[int, float]:
|
69
|
-
tries =
|
49
|
+
tries = 10
|
70
50
|
for _ in range(tries):
|
71
51
|
self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
|
72
52
|
res = self.receive()
|
@@ -75,7 +55,7 @@ class CommunicationController:
|
|
75
55
|
raise RuntimeError("Failed to get number.")
|
76
56
|
|
77
57
|
def get_text_val(self, cmd: str) -> str:
|
78
|
-
tries =
|
58
|
+
tries = 10
|
79
59
|
for _ in range(tries):
|
80
60
|
self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
|
81
61
|
res = self.receive()
|
@@ -92,29 +72,20 @@ class CommunicationController:
|
|
92
72
|
time.sleep(1)
|
93
73
|
|
94
74
|
try:
|
95
|
-
|
96
|
-
|
97
|
-
|
75
|
+
res = self.receive()
|
76
|
+
if res.is_err():
|
77
|
+
return HPLCErrorStatus.NORESPONSE
|
78
|
+
if res.is_ok():
|
79
|
+
parsed_response = self.receive().value.string_response
|
80
|
+
self._most_recent_hplc_status = str_to_status(parsed_response)
|
81
|
+
return self._most_recent_hplc_status
|
82
|
+
else:
|
83
|
+
raise RuntimeError("Failed to get status")
|
98
84
|
except IOError:
|
99
85
|
return HPLCErrorStatus.NORESPONSE
|
100
86
|
except IndexError:
|
101
87
|
return HPLCErrorStatus.MALFORMED
|
102
88
|
|
103
|
-
def set_status(self):
|
104
|
-
"""Updates current status of HPLC machine"""
|
105
|
-
self._most_recent_hplc_status = self.get_status()
|
106
|
-
|
107
|
-
def check_if_not_running(self) -> bool:
|
108
|
-
"""Checks if HPLC machine is in an available state, meaning a state that data is not being written.
|
109
|
-
|
110
|
-
:return: whether the HPLC machine is in a safe state to retrieve data back."""
|
111
|
-
self.set_status()
|
112
|
-
hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
113
|
-
time.sleep(5)
|
114
|
-
self.set_status()
|
115
|
-
hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
116
|
-
return hplc_avail and hplc_actually_avail
|
117
|
-
|
118
89
|
def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
|
119
90
|
"""Low-level execution primitive. Sends a command string to HPLC.
|
120
91
|
|
@@ -145,7 +116,8 @@ class CommunicationController:
|
|
145
116
|
:raises IOError: Could not read reply file.
|
146
117
|
:return: Potential ChemStation response
|
147
118
|
"""
|
148
|
-
err: Optional[Union[OSError, IndexError]] = None
|
119
|
+
err: Optional[Union[OSError, IndexError, ValueError]] = None
|
120
|
+
err_msg = ""
|
149
121
|
for _ in range(num_attempts):
|
150
122
|
time.sleep(1)
|
151
123
|
|
@@ -158,7 +130,11 @@ class CommunicationController:
|
|
158
130
|
|
159
131
|
try:
|
160
132
|
first_line = response.splitlines()[0]
|
161
|
-
|
133
|
+
try:
|
134
|
+
response_no = int(first_line.split()[0])
|
135
|
+
except ValueError as e:
|
136
|
+
err = e
|
137
|
+
err_msg = f"Caused by {first_line}"
|
162
138
|
except IndexError as e:
|
163
139
|
err = e
|
164
140
|
continue
|
@@ -169,63 +145,6 @@ class CommunicationController:
|
|
169
145
|
else:
|
170
146
|
continue
|
171
147
|
else:
|
172
|
-
return Err(
|
173
|
-
|
174
|
-
|
175
|
-
self.send("Sleep 0.1")
|
176
|
-
self.send(cmd)
|
177
|
-
self.send("Sleep 0.1")
|
178
|
-
|
179
|
-
def send(self, cmd: Union[Command, str]):
|
180
|
-
"""Sends a command to Chemstation.
|
181
|
-
|
182
|
-
:param cmd: Command to be sent to HPLC
|
183
|
-
"""
|
184
|
-
if self.cmd_no == self.MAX_CMD_NO:
|
185
|
-
self.reset_cmd_counter()
|
186
|
-
|
187
|
-
cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
|
188
|
-
self.cmd_no += 1
|
189
|
-
self._send(cmd_to_send, self.cmd_no)
|
190
|
-
if self.debug:
|
191
|
-
f = open("out.txt", "a")
|
192
|
-
f.write(cmd_to_send + "\n")
|
193
|
-
f.close()
|
194
|
-
|
195
|
-
def receive(self) -> Result[Response, str]:
|
196
|
-
"""Returns messages received in reply file.
|
197
|
-
|
198
|
-
:return: ChemStation response
|
199
|
-
"""
|
200
|
-
num_response_prefix = "Numerical Responses:"
|
201
|
-
str_response_prefix = "String Responses:"
|
202
|
-
possible_response = self._receive(self.cmd_no)
|
203
|
-
if possible_response.is_ok():
|
204
|
-
lines = possible_response.ok_value.splitlines()
|
205
|
-
for line in lines:
|
206
|
-
if str_response_prefix in line and num_response_prefix in line:
|
207
|
-
string_responses_dirty, _, numerical_responses = line.partition(
|
208
|
-
num_response_prefix
|
209
|
-
)
|
210
|
-
_, _, string_responses = string_responses_dirty.partition(
|
211
|
-
str_response_prefix
|
212
|
-
)
|
213
|
-
return Ok(
|
214
|
-
Response(
|
215
|
-
string_response=string_responses.strip(),
|
216
|
-
num_response=float(numerical_responses.strip()),
|
217
|
-
)
|
218
|
-
)
|
219
|
-
return Err("Could not retrieve HPLC response")
|
220
|
-
else:
|
221
|
-
return Err(f"Could not establish response to HPLC: {possible_response}")
|
222
|
-
|
223
|
-
def reset_cmd_counter(self):
|
224
|
-
"""Resets the command counter."""
|
225
|
-
self._send(Command.RESET_COUNTER_CMD.value, cmd_no=self.MAX_CMD_NO + 1)
|
226
|
-
self._receive(cmd_no=self.MAX_CMD_NO + 1)
|
227
|
-
self.cmd_no = 0
|
228
|
-
|
229
|
-
def stop_macro(self):
|
230
|
-
"""Stops Macro execution. Connection will be lost."""
|
231
|
-
self.send(Command.STOP_MACRO_CMD)
|
148
|
+
return Err(
|
149
|
+
f"Failed to receive reply to command #{cmd_no} due to {err} caused by {err_msg}."
|
150
|
+
)
|
@@ -1,14 +1,15 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
import time
|
3
5
|
import warnings
|
4
|
-
from typing import
|
6
|
+
from typing import List, Optional, Union, Dict
|
5
7
|
|
6
8
|
from result import Err, Ok, Result
|
7
|
-
from xsdata.formats.dataclass.parsers import XmlParser
|
8
9
|
|
10
|
+
from ..abc_tables.run import RunController
|
9
11
|
from ....analysis.process_report import AgilentReport, ReportType
|
10
12
|
from ....control.controllers import CommunicationController
|
11
|
-
from ....generated import DadMethod, PumpMethod, SolventElement
|
12
13
|
from pychemstation.analysis.chromatogram import (
|
13
14
|
TIME_FORMAT,
|
14
15
|
AgilentChannelChromatogramData,
|
@@ -22,12 +23,11 @@ from ....utils.method_types import (
|
|
22
23
|
PType,
|
23
24
|
TimeTableEntry,
|
24
25
|
)
|
25
|
-
from ....utils.table_types import RegisterFlag,
|
26
|
+
from ....utils.table_types import RegisterFlag, Table, TableOperation, T
|
26
27
|
from ..devices.injector import InjectorController
|
27
|
-
from .table import TableController
|
28
28
|
|
29
29
|
|
30
|
-
class MethodController(
|
30
|
+
class MethodController(RunController):
|
31
31
|
"""
|
32
32
|
Class containing method related logic
|
33
33
|
"""
|
@@ -61,20 +61,22 @@ class MethodController(TableController):
|
|
61
61
|
return "ERROR"
|
62
62
|
|
63
63
|
def get_method_params(self) -> HPLCMethodParams:
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
64
|
+
if self.controller:
|
65
|
+
return HPLCMethodParams(
|
66
|
+
organic_modifier=self.controller.get_num_val(
|
67
|
+
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
68
|
+
register=self.table_locator.register,
|
69
|
+
register_flag=RegisterFlag.SOLVENT_B_COMPOSITION,
|
70
|
+
)
|
71
|
+
),
|
72
|
+
flow=self.controller.get_num_val(
|
73
|
+
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
74
|
+
register=self.table_locator.register,
|
75
|
+
register_flag=RegisterFlag.FLOW,
|
76
|
+
)
|
77
|
+
),
|
78
|
+
)
|
79
|
+
raise ValueError("Communication controller is offline!")
|
78
80
|
|
79
81
|
def get_row(self, row: int) -> TimeTableEntry:
|
80
82
|
flow = None
|
@@ -89,6 +91,9 @@ class MethodController(TableController):
|
|
89
91
|
except RuntimeError:
|
90
92
|
pass
|
91
93
|
|
94
|
+
if om is None and flow is None:
|
95
|
+
raise ValueError("Both flow and organic modifier is None")
|
96
|
+
|
92
97
|
return TimeTableEntry(
|
93
98
|
start_time=self.get_num(row, RegisterFlag.TIME),
|
94
99
|
organic_modifer=om,
|
@@ -97,7 +102,7 @@ class MethodController(TableController):
|
|
97
102
|
|
98
103
|
def get_timetable(self, rows: int):
|
99
104
|
uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
|
100
|
-
timetable_rows = {}
|
105
|
+
timetable_rows: Dict[str, TimeTableEntry] = {}
|
101
106
|
for row in uncoalesced_timetable_rows:
|
102
107
|
time_key = str(row.start_time)
|
103
108
|
if time_key not in timetable_rows.keys():
|
@@ -141,20 +146,24 @@ class MethodController(TableController):
|
|
141
146
|
return method_name
|
142
147
|
|
143
148
|
def get_post_time(self) -> Union[int, float]:
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
149
|
+
if self.controller:
|
150
|
+
return self.controller.get_num_val(
|
151
|
+
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
152
|
+
register=self.table_locator.register,
|
153
|
+
register_flag=RegisterFlag.POST_TIME,
|
154
|
+
)
|
148
155
|
)
|
149
|
-
)
|
156
|
+
raise ValueError("Communication controller is not online!")
|
150
157
|
|
151
158
|
def get_stop_time(self) -> Union[int, float]:
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
159
|
+
if self.controller:
|
160
|
+
return self.controller.get_num_val(
|
161
|
+
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
162
|
+
register=self.table_locator.register,
|
163
|
+
register_flag=RegisterFlag.MAX_TIME,
|
164
|
+
)
|
156
165
|
)
|
157
|
-
)
|
166
|
+
raise ValueError("Communication controller is not online!")
|
158
167
|
|
159
168
|
def get_total_runtime(self) -> Union[int, float]:
|
160
169
|
"""Returns total method runtime in minutes."""
|
@@ -198,60 +207,6 @@ class MethodController(TableController):
|
|
198
207
|
assert parsed_response == f"{method_name}.M", "Switching Methods failed."
|
199
208
|
self.table_state = None
|
200
209
|
|
201
|
-
def load_from_disk(self, method_name: str) -> MethodDetails:
|
202
|
-
"""
|
203
|
-
Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
|
204
|
-
organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
|
205
|
-
only two solvents are being used.
|
206
|
-
|
207
|
-
:param method_name: name of method to load details of
|
208
|
-
:raises FileNotFoundError: Method does not exist
|
209
|
-
:return: method details
|
210
|
-
"""
|
211
|
-
warnings.warn("This method is not actively maintained.")
|
212
|
-
method_folder = f"{method_name}.M"
|
213
|
-
method_path = os.path.join(
|
214
|
-
self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml"
|
215
|
-
)
|
216
|
-
dad_path = os.path.join(
|
217
|
-
self.src,
|
218
|
-
method_folder,
|
219
|
-
"Agilent1200erDadDriver1.RapidControl.MethodXML.xml",
|
220
|
-
)
|
221
|
-
|
222
|
-
if os.path.exists(os.path.join(self.src, f"{method_name}.M")):
|
223
|
-
parser = XmlParser()
|
224
|
-
method = parser.parse(method_path, PumpMethod)
|
225
|
-
dad = parser.parse(dad_path, DadMethod)
|
226
|
-
|
227
|
-
organic_modifier: Optional[SolventElement] = None
|
228
|
-
|
229
|
-
if len(method.solvent_composition.solvent_element) == 2:
|
230
|
-
for solvent in method.solvent_composition.solvent_element:
|
231
|
-
if solvent.channel == "Channel_B":
|
232
|
-
organic_modifier = solvent
|
233
|
-
|
234
|
-
self.table_state = MethodDetails(
|
235
|
-
name=method_name,
|
236
|
-
params=HPLCMethodParams(
|
237
|
-
organic_modifier=organic_modifier.percentage, flow=method.flow
|
238
|
-
),
|
239
|
-
stop_time=method.stop_time.stop_time_value,
|
240
|
-
post_time=method.post_time.post_time_value,
|
241
|
-
timetable=[
|
242
|
-
TimeTableEntry(
|
243
|
-
start_time=tte.time,
|
244
|
-
organic_modifer=tte.percent_b,
|
245
|
-
flow=method.flow,
|
246
|
-
)
|
247
|
-
for tte in method.timetable.timetable_entry
|
248
|
-
],
|
249
|
-
dad_wavelengthes=dad.signals.signal,
|
250
|
-
)
|
251
|
-
return self.table_state
|
252
|
-
else:
|
253
|
-
raise FileNotFoundError
|
254
|
-
|
255
210
|
def edit(self, updated_method: MethodDetails, save: bool):
|
256
211
|
"""Updated the currently loaded method in ChemStation with provided values.
|
257
212
|
|
@@ -317,13 +272,13 @@ class MethodController(TableController):
|
|
317
272
|
self,
|
318
273
|
new_flow: Union[int, float],
|
319
274
|
new_initial_om: Union[int, float],
|
320
|
-
new_stop_time: Union[int, float],
|
321
|
-
new_post_time: Union[int, float],
|
275
|
+
new_stop_time: Union[int, float] | None,
|
276
|
+
new_post_time: Union[int, float] | None,
|
322
277
|
):
|
323
278
|
self.delete_table()
|
324
279
|
self.edit_initial_om(new_initial_om)
|
325
280
|
self.edit_flow(new_flow)
|
326
|
-
if
|
281
|
+
if new_stop_time:
|
327
282
|
self.edit_stop_time(new_stop_time)
|
328
283
|
else:
|
329
284
|
self._update_param(
|
@@ -333,7 +288,7 @@ class MethodController(TableController):
|
|
333
288
|
ptype=PType.STR,
|
334
289
|
)
|
335
290
|
)
|
336
|
-
if
|
291
|
+
if new_post_time:
|
337
292
|
self.edit_post_time(new_post_time)
|
338
293
|
else:
|
339
294
|
self._update_param(
|
@@ -502,21 +457,23 @@ class MethodController(TableController):
|
|
502
457
|
warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
|
503
458
|
warnings.warn(warning)
|
504
459
|
|
505
|
-
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[
|
506
|
-
if
|
507
|
-
|
508
|
-
|
460
|
+
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[str, str]:
|
461
|
+
if isinstance(most_recent_folder, str) or isinstance(most_recent_folder, bytes):
|
462
|
+
if os.path.exists(most_recent_folder):
|
463
|
+
return Ok(most_recent_folder)
|
464
|
+
return Err("Folder not found!")
|
465
|
+
raise ValueError("Folder is not a str or byte type.")
|
509
466
|
|
510
467
|
def get_data(
|
511
468
|
self, custom_path: Optional[str] = None
|
512
469
|
) -> AgilentChannelChromatogramData:
|
513
470
|
custom_path = custom_path if custom_path else self.data_files[-1]
|
514
471
|
self.get_spectrum_at_channels(custom_path)
|
515
|
-
return AgilentChannelChromatogramData(
|
472
|
+
return AgilentChannelChromatogramData.from_dict(self.spectra)
|
516
473
|
|
517
474
|
def get_data_uv(
|
518
475
|
self, custom_path: Optional[str] = None
|
519
|
-
) ->
|
476
|
+
) -> dict[int, AgilentHPLCChromatogram]:
|
520
477
|
custom_path = custom_path if custom_path else self.data_files[-1]
|
521
478
|
self.get_uv_spectrum(custom_path)
|
522
479
|
return self.uv
|