pychemstation 0.10.5__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/control/controllers/__init__.py +2 -2
- pychemstation/control/controllers/abc_tables/__init__.py +0 -0
- pychemstation/control/controllers/abc_tables/device.py +15 -0
- pychemstation/control/controllers/{tables/table.py → abc_tables/run.py} +24 -195
- pychemstation/control/controllers/abc_tables/table.py +221 -0
- pychemstation/control/controllers/comm.py +3 -99
- pychemstation/control/controllers/{tables → data_aq}/method.py +2 -2
- pychemstation/control/controllers/{tables → data_aq}/sequence.py +72 -53
- pychemstation/control/controllers/devices/__init__.py +3 -0
- pychemstation/control/controllers/devices/injector.py +57 -24
- pychemstation/control/hplc.py +5 -1
- pychemstation/utils/injector_types.py +22 -2
- 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.5.dist-info → pychemstation-0.10.6.dist-info}/METADATA +1 -1
- pychemstation-0.10.6.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.6.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.5.dist-info → pychemstation-0.10.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,11 @@
|
|
1
1
|
from .process_report import CSVProcessor
|
2
2
|
from .process_report import TXTProcessor
|
3
|
+
from .chromatogram import AgilentChannelChromatogramData
|
4
|
+
from .chromatogram import AgilentHPLCChromatogram
|
3
5
|
|
4
|
-
__all__ = [
|
6
|
+
__all__ = [
|
7
|
+
"CSVProcessor",
|
8
|
+
"TXTProcessor",
|
9
|
+
"AgilentChannelChromatogramData",
|
10
|
+
"AgilentHPLCChromatogram",
|
11
|
+
]
|
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC
|
4
|
+
|
5
|
+
from ....control.controllers import CommunicationController
|
6
|
+
from ....utils.table_types import Table
|
7
|
+
from .table import ABCTableController
|
8
|
+
|
9
|
+
|
10
|
+
class DeviceController(ABCTableController, ABC):
|
11
|
+
def __init__(
|
12
|
+
self, controller: CommunicationController, table: Table, offline: bool
|
13
|
+
):
|
14
|
+
super().__init__(controller=controller, table=table)
|
15
|
+
self.offline = offline
|
@@ -17,6 +17,7 @@ import polling
|
|
17
17
|
import rainbow as rb
|
18
18
|
from result import Err, Result, Ok
|
19
19
|
|
20
|
+
from .table import ABCTableController
|
20
21
|
from ....analysis.process_report import (
|
21
22
|
AgilentReport,
|
22
23
|
CSVProcessor,
|
@@ -28,15 +29,15 @@ from pychemstation.analysis.chromatogram import (
|
|
28
29
|
AgilentChannelChromatogramData,
|
29
30
|
AgilentHPLCChromatogram,
|
30
31
|
)
|
31
|
-
from ....utils.macro import
|
32
|
+
from ....utils.macro import HPLCRunningStatus
|
32
33
|
from ....utils.method_types import MethodDetails
|
33
34
|
from ....utils.sequence_types import SequenceTable
|
34
|
-
from ....utils.table_types import
|
35
|
+
from ....utils.table_types import Table, T
|
35
36
|
|
36
37
|
TableType = Union[MethodDetails, SequenceTable]
|
37
38
|
|
38
39
|
|
39
|
-
class
|
40
|
+
class RunController(ABCTableController, abc.ABC):
|
40
41
|
def __init__(
|
41
42
|
self,
|
42
43
|
controller: Optional[CommunicationController],
|
@@ -45,8 +46,10 @@ class TableController(abc.ABC):
|
|
45
46
|
table: Table,
|
46
47
|
offline: bool = False,
|
47
48
|
):
|
48
|
-
|
49
|
-
|
49
|
+
super().__init__(controller=controller, table=table)
|
50
|
+
warnings.warn(
|
51
|
+
"This abstract class is not meant to be initialized. Use MethodController or SequenceController."
|
52
|
+
)
|
50
53
|
self.table_state: Optional[TableType] = None
|
51
54
|
self.curr_run_starting_time: Optional[float] = None
|
52
55
|
self.timeout: Optional[float] = None
|
@@ -76,179 +79,27 @@ class TableController(abc.ABC):
|
|
76
79
|
self.uv: Dict[int, AgilentHPLCChromatogram] = {}
|
77
80
|
self.data_files: List = []
|
78
81
|
|
79
|
-
def receive(self) -> Result[Response, str]:
|
80
|
-
if self.controller:
|
81
|
-
for _ in range(10):
|
82
|
-
try:
|
83
|
-
return self.controller.receive()
|
84
|
-
except IndexError:
|
85
|
-
continue
|
86
|
-
return Err("Could not parse response")
|
87
|
-
else:
|
88
|
-
raise ValueError("Controller is offline!")
|
89
|
-
|
90
|
-
def send(self, cmd: Union[Command, str]):
|
91
|
-
if not self.controller:
|
92
|
-
raise RuntimeError(
|
93
|
-
"Communication controller must be initialized before sending command. It is currently in offline mode."
|
94
|
-
)
|
95
|
-
self.controller.send(cmd)
|
96
|
-
|
97
|
-
def sleepy_send(self, cmd: Union[Command, str]):
|
98
|
-
if self.controller:
|
99
|
-
self.controller.sleepy_send(cmd)
|
100
|
-
else:
|
101
|
-
raise ValueError("Controller is offline")
|
102
|
-
|
103
|
-
def sleep(self, seconds: int):
|
104
|
-
"""
|
105
|
-
Tells the HPLC to wait for a specified number of seconds.
|
106
|
-
|
107
|
-
:param seconds: number of seconds to wait
|
108
|
-
"""
|
109
|
-
self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
|
110
|
-
|
111
|
-
def get_num(self, row: int, col_name: RegisterFlag) -> Union[int, float]:
|
112
|
-
if self.controller:
|
113
|
-
return self.controller.get_num_val(
|
114
|
-
TableOperation.GET_ROW_VAL.value.format(
|
115
|
-
register=self.table_locator.register,
|
116
|
-
table_name=self.table_locator.name,
|
117
|
-
row=row,
|
118
|
-
col_name=col_name.value,
|
119
|
-
)
|
120
|
-
)
|
121
|
-
else:
|
122
|
-
raise ValueError("Controller is offline")
|
123
|
-
|
124
|
-
def get_text(self, row: int, col_name: RegisterFlag) -> str:
|
125
|
-
if self.controller:
|
126
|
-
return self.controller.get_text_val(
|
127
|
-
TableOperation.GET_ROW_TEXT.value.format(
|
128
|
-
register=self.table_locator.register,
|
129
|
-
table_name=self.table_locator.name,
|
130
|
-
row=row,
|
131
|
-
col_name=col_name.value,
|
132
|
-
)
|
133
|
-
)
|
134
|
-
else:
|
135
|
-
raise ValueError("Controller is offline")
|
136
|
-
|
137
|
-
def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
|
138
|
-
self.sleepy_send(
|
139
|
-
TableOperation.NEW_COL_VAL.value.format(
|
140
|
-
register=self.table_locator.register,
|
141
|
-
table_name=self.table_locator.name,
|
142
|
-
col_name=col_name,
|
143
|
-
val=val,
|
144
|
-
)
|
145
|
-
)
|
146
|
-
|
147
|
-
def add_new_col_text(self, col_name: RegisterFlag, val: str):
|
148
|
-
self.sleepy_send(
|
149
|
-
TableOperation.NEW_COL_TEXT.value.format(
|
150
|
-
register=self.table_locator.register,
|
151
|
-
table_name=self.table_locator.name,
|
152
|
-
col_name=col_name,
|
153
|
-
val=val,
|
154
|
-
)
|
155
|
-
)
|
156
|
-
|
157
|
-
def _edit_row_num(
|
158
|
-
self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
|
159
|
-
):
|
160
|
-
self.sleepy_send(
|
161
|
-
TableOperation.EDIT_ROW_VAL.value.format(
|
162
|
-
register=self.table_locator.register,
|
163
|
-
table_name=self.table_locator.name,
|
164
|
-
row=row if row is not None else "Rows",
|
165
|
-
col_name=col_name,
|
166
|
-
val=val,
|
167
|
-
)
|
168
|
-
)
|
169
|
-
|
170
|
-
def _edit_row_text(
|
171
|
-
self, col_name: RegisterFlag, val: str, row: Optional[int] = None
|
172
|
-
):
|
173
|
-
self.sleepy_send(
|
174
|
-
TableOperation.EDIT_ROW_TEXT.value.format(
|
175
|
-
register=self.table_locator.register,
|
176
|
-
table_name=self.table_locator.name,
|
177
|
-
row=row if row is not None else "Rows",
|
178
|
-
col_name=col_name,
|
179
|
-
val=val,
|
180
|
-
)
|
181
|
-
)
|
182
|
-
|
183
82
|
@abc.abstractmethod
|
184
|
-
def
|
83
|
+
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
185
84
|
pass
|
186
85
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
row=row,
|
193
|
-
)
|
194
|
-
)
|
195
|
-
|
196
|
-
def add_row(self):
|
197
|
-
"""
|
198
|
-
Adds a row to the provided table for currently loaded method or sequence.
|
199
|
-
"""
|
200
|
-
self.sleepy_send(
|
201
|
-
TableOperation.NEW_ROW.value.format(
|
202
|
-
register=self.table_locator.register, table_name=self.table_locator.name
|
203
|
-
)
|
204
|
-
)
|
205
|
-
|
206
|
-
def delete_table(self):
|
207
|
-
"""
|
208
|
-
Deletes the table for the current loaded method or sequence.
|
209
|
-
"""
|
210
|
-
self.sleepy_send(
|
211
|
-
TableOperation.DELETE_TABLE.value.format(
|
212
|
-
register=self.table_locator.register, table_name=self.table_locator.name
|
213
|
-
)
|
214
|
-
)
|
215
|
-
|
216
|
-
def new_table(self):
|
217
|
-
"""
|
218
|
-
Creates the table for the currently loaded method or sequence.
|
219
|
-
"""
|
220
|
-
self.send(
|
221
|
-
TableOperation.CREATE_TABLE.value.format(
|
222
|
-
register=self.table_locator.register, table_name=self.table_locator.name
|
223
|
-
)
|
224
|
-
)
|
86
|
+
@abc.abstractmethod
|
87
|
+
def get_data(
|
88
|
+
self, custom_path: Optional[str] = None
|
89
|
+
) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
90
|
+
pass
|
225
91
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
col_name=RegisterFlag.NUM_ROWS,
|
232
|
-
)
|
233
|
-
)
|
234
|
-
self.send(
|
235
|
-
Command.GET_ROWS_CMD.value.format(
|
236
|
-
register=self.table_locator.register,
|
237
|
-
table_name=self.table_locator.name,
|
238
|
-
col_name=RegisterFlag.NUM_ROWS,
|
239
|
-
)
|
240
|
-
)
|
241
|
-
if self.controller:
|
242
|
-
res = self.controller.receive()
|
243
|
-
else:
|
244
|
-
raise ValueError("Controller is offline")
|
92
|
+
@abc.abstractmethod
|
93
|
+
def get_data_uv(
|
94
|
+
self, custom_path: str | None = None
|
95
|
+
) -> Dict[int, AgilentHPLCChromatogram]:
|
96
|
+
pass
|
245
97
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
return Err("No rows could be read.")
|
98
|
+
@abc.abstractmethod
|
99
|
+
def get_report(
|
100
|
+
self, custom_path: str, report_type: ReportType = ReportType.TXT
|
101
|
+
) -> List[AgilentReport]:
|
102
|
+
pass
|
252
103
|
|
253
104
|
def check_hplc_is_running(self) -> bool:
|
254
105
|
if self.controller:
|
@@ -334,28 +185,6 @@ class TableController(abc.ABC):
|
|
334
185
|
return self.data_files[-1]
|
335
186
|
return Err("Run did not complete as expected")
|
336
187
|
|
337
|
-
@abc.abstractmethod
|
338
|
-
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
339
|
-
pass
|
340
|
-
|
341
|
-
@abc.abstractmethod
|
342
|
-
def get_data(
|
343
|
-
self, custom_path: Optional[str] = None
|
344
|
-
) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
345
|
-
pass
|
346
|
-
|
347
|
-
@abc.abstractmethod
|
348
|
-
def get_data_uv(
|
349
|
-
self, custom_path: str | None = None
|
350
|
-
) -> Dict[int, AgilentHPLCChromatogram]:
|
351
|
-
pass
|
352
|
-
|
353
|
-
@abc.abstractmethod
|
354
|
-
def get_report(
|
355
|
-
self, custom_path: str, report_type: ReportType = ReportType.TXT
|
356
|
-
) -> List[AgilentReport]:
|
357
|
-
pass
|
358
|
-
|
359
188
|
def get_uv_spectrum(self, path: str):
|
360
189
|
data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
|
361
190
|
times = data_uv.xlabels
|
@@ -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,22 +17,18 @@ 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,
|
@@ -48,24 +43,7 @@ class CommunicationController:
|
|
48
43
|
:param reply_file: Name of reply file
|
49
44
|
:param debug: whether to save log of sent commands
|
50
45
|
"""
|
51
|
-
|
52
|
-
self.debug = debug
|
53
|
-
if os.path.isdir(comm_dir):
|
54
|
-
self.cmd_file = os.path.join(comm_dir, cmd_file)
|
55
|
-
self.reply_file = os.path.join(comm_dir, reply_file)
|
56
|
-
self.cmd_no = 0
|
57
|
-
else:
|
58
|
-
raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
|
59
|
-
|
60
|
-
# Create files for Chemstation to communicate with Python
|
61
|
-
open(self.cmd_file, "a").close()
|
62
|
-
open(self.reply_file, "a").close()
|
63
|
-
|
64
|
-
self.reset_cmd_counter()
|
65
|
-
|
66
|
-
# Initialize row counter for table operations
|
67
|
-
self._most_recent_hplc_status: Status = self.get_status()
|
68
|
-
self.send("Local Rows")
|
46
|
+
super().__init__(comm_dir, cmd_file, reply_file, offline, debug)
|
69
47
|
|
70
48
|
def get_num_val(self, cmd: str) -> Union[int, float]:
|
71
49
|
tries = 10
|
@@ -108,21 +86,6 @@ class CommunicationController:
|
|
108
86
|
except IndexError:
|
109
87
|
return HPLCErrorStatus.MALFORMED
|
110
88
|
|
111
|
-
def set_status(self):
|
112
|
-
"""Updates current status of HPLC machine"""
|
113
|
-
self._most_recent_hplc_status = self.get_status()
|
114
|
-
|
115
|
-
def check_if_not_running(self) -> bool:
|
116
|
-
"""Checks if HPLC machine is in an available state, meaning a state that data is not being written.
|
117
|
-
|
118
|
-
:return: whether the HPLC machine is in a safe state to retrieve data back."""
|
119
|
-
self.set_status()
|
120
|
-
hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
121
|
-
time.sleep(5)
|
122
|
-
self.set_status()
|
123
|
-
hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
124
|
-
return hplc_avail and hplc_actually_avail
|
125
|
-
|
126
89
|
def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
|
127
90
|
"""Low-level execution primitive. Sends a command string to HPLC.
|
128
91
|
|
@@ -185,62 +148,3 @@ class CommunicationController:
|
|
185
148
|
return Err(
|
186
149
|
f"Failed to receive reply to command #{cmd_no} due to {err} caused by {err_msg}."
|
187
150
|
)
|
188
|
-
|
189
|
-
def sleepy_send(self, cmd: Union[Command, str]):
|
190
|
-
self.send("Sleep 0.1")
|
191
|
-
self.send(cmd)
|
192
|
-
self.send("Sleep 0.1")
|
193
|
-
|
194
|
-
def send(self, cmd: Union[Command, str]):
|
195
|
-
"""Sends a command to Chemstation.
|
196
|
-
|
197
|
-
:param cmd: Command to be sent to HPLC
|
198
|
-
"""
|
199
|
-
if self.cmd_no == self.MAX_CMD_NO:
|
200
|
-
self.reset_cmd_counter()
|
201
|
-
|
202
|
-
cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
|
203
|
-
self.cmd_no += 1
|
204
|
-
self._send(cmd_to_send, self.cmd_no)
|
205
|
-
if self.debug:
|
206
|
-
f = open("out.txt", "a")
|
207
|
-
f.write(cmd_to_send + "\n")
|
208
|
-
f.close()
|
209
|
-
|
210
|
-
def receive(self) -> Result[Response, str]:
|
211
|
-
"""Returns messages received in reply file.
|
212
|
-
|
213
|
-
:return: ChemStation response
|
214
|
-
"""
|
215
|
-
num_response_prefix = "Numerical Responses:"
|
216
|
-
str_response_prefix = "String Responses:"
|
217
|
-
possible_response = self._receive(self.cmd_no)
|
218
|
-
if possible_response.is_ok():
|
219
|
-
lines = possible_response.ok_value.splitlines()
|
220
|
-
for line in lines:
|
221
|
-
if str_response_prefix in line and num_response_prefix in line:
|
222
|
-
string_responses_dirty, _, numerical_responses = line.partition(
|
223
|
-
num_response_prefix
|
224
|
-
)
|
225
|
-
_, _, string_responses = string_responses_dirty.partition(
|
226
|
-
str_response_prefix
|
227
|
-
)
|
228
|
-
return Ok(
|
229
|
-
Response(
|
230
|
-
string_response=string_responses.strip(),
|
231
|
-
num_response=float(numerical_responses.strip()),
|
232
|
-
)
|
233
|
-
)
|
234
|
-
return Err("Could not retrieve HPLC response")
|
235
|
-
else:
|
236
|
-
return Err(f"Could not establish response to HPLC: {possible_response}")
|
237
|
-
|
238
|
-
def reset_cmd_counter(self):
|
239
|
-
"""Resets the command counter."""
|
240
|
-
self._send(Command.RESET_COUNTER_CMD.value, cmd_no=self.MAX_CMD_NO + 1)
|
241
|
-
self._receive(cmd_no=self.MAX_CMD_NO + 1)
|
242
|
-
self.cmd_no = 0
|
243
|
-
|
244
|
-
def stop_macro(self):
|
245
|
-
"""Stops Macro execution. Connection will be lost."""
|
246
|
-
self.send(Command.STOP_MACRO_CMD)
|
@@ -7,6 +7,7 @@ from typing import List, Optional, Union, Dict
|
|
7
7
|
|
8
8
|
from result import Err, Ok, Result
|
9
9
|
|
10
|
+
from ..abc_tables.run import RunController
|
10
11
|
from ....analysis.process_report import AgilentReport, ReportType
|
11
12
|
from ....control.controllers import CommunicationController
|
12
13
|
from pychemstation.analysis.chromatogram import (
|
@@ -24,10 +25,9 @@ from ....utils.method_types import (
|
|
24
25
|
)
|
25
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
|
"""
|