pychemstation 0.7.0.dev2__py3-none-any.whl → 0.8.1__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/base_spectrum.py +3 -6
- pychemstation/analysis/process_report.py +248 -225
- pychemstation/analysis/utils.py +3 -1
- pychemstation/control/README.md +124 -0
- pychemstation/control/controllers/README.md +1 -0
- pychemstation/control/controllers/__init__.py +0 -2
- pychemstation/control/controllers/comm.py +27 -20
- pychemstation/control/controllers/devices/device.py +17 -4
- pychemstation/control/controllers/tables/method.py +57 -39
- pychemstation/control/controllers/tables/sequence.py +98 -28
- pychemstation/control/controllers/tables/table.py +124 -127
- pychemstation/control/hplc.py +82 -37
- pychemstation/generated/dad_method.py +3 -3
- pychemstation/generated/pump_method.py +7 -7
- pychemstation/out.txt +145 -0
- pychemstation/tests.ipynb +310 -0
- pychemstation/utils/chromatogram.py +5 -1
- pychemstation/utils/injector_types.py +2 -2
- pychemstation/utils/macro.py +1 -1
- pychemstation/utils/table_types.py +3 -0
- pychemstation/utils/tray_types.py +59 -39
- {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.1.dist-info}/METADATA +25 -21
- pychemstation-0.8.1.dist-info/RECORD +39 -0
- {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.1.dist-info}/WHEEL +1 -2
- pychemstation/control/comm.py +0 -206
- pychemstation/control/controllers/devices/column.py +0 -12
- pychemstation/control/controllers/devices/dad.py +0 -0
- pychemstation/control/controllers/devices/pump.py +0 -43
- pychemstation/control/controllers/method.py +0 -338
- pychemstation/control/controllers/sequence.py +0 -190
- pychemstation/control/controllers/table_controller.py +0 -266
- pychemstation/control/table/__init__.py +0 -3
- pychemstation/control/table/method.py +0 -274
- pychemstation/control/table/sequence.py +0 -210
- pychemstation/control/table/table_controller.py +0 -201
- pychemstation-0.7.0.dev2.dist-info/RECORD +0 -58
- pychemstation-0.7.0.dev2.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/constants.py +0 -88
- tests/test_comb.py +0 -136
- tests/test_comm.py +0 -65
- tests/test_inj.py +0 -39
- tests/test_method.py +0 -99
- tests/test_nightly.py +0 -80
- tests/test_proc_rep.py +0 -52
- tests/test_runs_stable.py +0 -125
- tests/test_sequence.py +0 -125
- tests/test_stable.py +0 -276
- {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.1.dist-info/licenses}/LICENSE +0 -0
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import math
|
4
3
|
from dataclasses import dataclass
|
5
4
|
from enum import Enum
|
6
5
|
from typing import Union
|
@@ -18,28 +17,23 @@ class Num(Enum):
|
|
18
17
|
NINE = 9
|
19
18
|
|
20
19
|
@classmethod
|
21
|
-
def from_num(cls, num: int)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
return Num.EIGHT
|
39
|
-
case 9:
|
40
|
-
return Num.NINE
|
41
|
-
case _:
|
42
|
-
raise ValueError("Num is one of 1 to 9")
|
20
|
+
def from_num(cls, num: int):
|
21
|
+
num_mapping = {
|
22
|
+
1: Num.ONE,
|
23
|
+
2: Num.TWO,
|
24
|
+
3: Num.THREE,
|
25
|
+
4: Num.FOUR,
|
26
|
+
5: Num.FIVE,
|
27
|
+
6: Num.SIX,
|
28
|
+
7: Num.SEVEN,
|
29
|
+
8: Num.EIGHT,
|
30
|
+
9: Num.NINE
|
31
|
+
}
|
32
|
+
|
33
|
+
if num in num_mapping:
|
34
|
+
return num_mapping[num]
|
35
|
+
else:
|
36
|
+
raise ValueError("Num must be between 1 and 9")
|
43
37
|
|
44
38
|
|
45
39
|
class Plate(Enum):
|
@@ -63,25 +57,32 @@ class Letter(Enum):
|
|
63
57
|
|
64
58
|
@classmethod
|
65
59
|
def from_str(cls, let: str) -> Letter:
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
case _:
|
80
|
-
raise ValueError("Letter is one of A to F")
|
60
|
+
letter_mapping = {
|
61
|
+
"A": Letter.A,
|
62
|
+
"B": Letter.B,
|
63
|
+
"C": Letter.C,
|
64
|
+
"D": Letter.D,
|
65
|
+
"E": Letter.E,
|
66
|
+
"F": Letter.F
|
67
|
+
}
|
68
|
+
|
69
|
+
if let in letter_mapping:
|
70
|
+
return letter_mapping[let]
|
71
|
+
else:
|
72
|
+
raise ValueError("Letter must be one of A to F")
|
81
73
|
|
82
74
|
|
83
75
|
@dataclass
|
84
76
|
class FiftyFourVialPlate:
|
77
|
+
"""
|
78
|
+
Class to represent the 54 vial tray. Assumes you have:
|
79
|
+
2 plates (P1 or P2)
|
80
|
+
6 rows (A B C D E F)
|
81
|
+
9 columns (1 2 3 4 5 6 7 8 9)
|
82
|
+
|
83
|
+
valid vial locations: P1-A2, P2-F9
|
84
|
+
invalid vial locations: P3-A1, P1-Z3, P2-B10
|
85
|
+
"""
|
85
86
|
plate: Plate
|
86
87
|
letter: Letter
|
87
88
|
num: Num
|
@@ -91,6 +92,12 @@ class FiftyFourVialPlate:
|
|
91
92
|
|
92
93
|
@classmethod
|
93
94
|
def from_str(cls, loc: str):
|
95
|
+
"""
|
96
|
+
Converts a string representing the vial location into numerical representation for Chemstation.
|
97
|
+
:param loc: vial location
|
98
|
+
:returns: `FiftyFourVialPlate` object representing the vial location
|
99
|
+
:raises: ValueError if string is invalid tray location
|
100
|
+
"""
|
94
101
|
if len(loc) != 5:
|
95
102
|
raise ValueError("Plate locations must be PX-LY, where X is either 1 or 2 and Y is 1 to 9")
|
96
103
|
try:
|
@@ -104,7 +111,17 @@ class FiftyFourVialPlate:
|
|
104
111
|
raise ValueError("Plate locations must be PX-LY, where X is either 1 or 2 and Y is 1 to 9")
|
105
112
|
|
106
113
|
@classmethod
|
107
|
-
def from_int(cls, num: int) ->
|
114
|
+
def from_int(cls, num: int) -> Tray:
|
115
|
+
"""
|
116
|
+
Converts an integer representation of a vial location to a `FiftyFourVialPlate` or `TenVialColumn` object
|
117
|
+
|
118
|
+
:param num: numerical representation of a vial location
|
119
|
+
:returns: the proper vial location object
|
120
|
+
:raises: ValueError no matching can be made
|
121
|
+
"""
|
122
|
+
if num in range(1, 11):
|
123
|
+
return TenVialColumn(num)
|
124
|
+
|
108
125
|
row_starts = [
|
109
126
|
# plate 1
|
110
127
|
FiftyFourVialPlate.from_str('P1-F1'),
|
@@ -148,6 +165,9 @@ class FiftyFourVialPlate:
|
|
148
165
|
|
149
166
|
|
150
167
|
class TenVialColumn(Enum):
|
168
|
+
"""
|
169
|
+
Class to represent the 10 vial locations.
|
170
|
+
"""
|
151
171
|
ONE = 1
|
152
172
|
TWO = 2
|
153
173
|
THREE = 3
|
@@ -1,21 +1,24 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: pychemstation
|
3
|
-
Version: 0.
|
4
|
-
|
5
|
-
Home-page: https://gitlab.com/heingroup/device-api/pychemstation
|
6
|
-
Author: Lucy Hao
|
7
|
-
Author-email: lhao03@student.ubc.ca
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
10
|
-
Classifier: Operating System :: OS Independent
|
11
|
-
Description-Content-Type: text/markdown
|
3
|
+
Version: 0.8.1
|
4
|
+
Author-email: lucyhao <hao.lucyy@gmail.com>
|
12
5
|
License-File: LICENSE
|
13
|
-
Requires-
|
14
|
-
Requires-Dist:
|
15
|
-
Requires-Dist:
|
16
|
-
Requires-Dist:
|
17
|
-
Requires-Dist:
|
18
|
-
Requires-Dist:
|
6
|
+
Requires-Python: >=3.8
|
7
|
+
Requires-Dist: aghplctools>=4.8.8
|
8
|
+
Requires-Dist: coverage>=7.6.1
|
9
|
+
Requires-Dist: matplotlib>=3.7.5
|
10
|
+
Requires-Dist: pandas>=2.0.3
|
11
|
+
Requires-Dist: pdoc>=14.7.0
|
12
|
+
Requires-Dist: polling>=0.3.2
|
13
|
+
Requires-Dist: pytest>=7.3.5
|
14
|
+
Requires-Dist: rainbow-api>=1.0.10
|
15
|
+
Requires-Dist: result>=0.17.0
|
16
|
+
Requires-Dist: scipy>=1.10.1
|
17
|
+
Requires-Dist: seabreeze>=2.9.2
|
18
|
+
Requires-Dist: setuptools>=75.3.2
|
19
|
+
Requires-Dist: twine>=6.1.0
|
20
|
+
Requires-Dist: xsdata>=24.9
|
21
|
+
Description-Content-Type: text/markdown
|
19
22
|
|
20
23
|
# Agilent HPLC Macro Control
|
21
24
|
|
@@ -67,13 +70,14 @@ from pychemstation.control import HPLCController
|
|
67
70
|
import pandas as pd
|
68
71
|
|
69
72
|
# these paths will be unique to your Chemstation setup
|
70
|
-
DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
|
71
73
|
DEFAULT_METHOD = "GENERAL-POROSHELL"
|
74
|
+
DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
|
72
75
|
DEFAULT_METHOD_DIR = "C:\\ChemStation\\1\\Methods\\"
|
73
|
-
|
74
|
-
|
76
|
+
DATA_DIR_2 = "C:\\Users\\Public\\Documents\\ChemStation\\2\\Data\\"
|
77
|
+
DATA_DIR_3 = "C:\\Users\\Public\\Documents\\ChemStation\\3\\Data\\"
|
78
|
+
SEQUENCE_DIR = "C:\\USERS\\PUBLIC\\DOCUMENTS\\CHEMSTATION\\2\\Sequence\\"
|
75
79
|
|
76
|
-
hplc_controller = HPLCController(
|
80
|
+
hplc_controller = HPLCController(data_dirs=[DATA_DIR_2, DATA_DIR_3],
|
77
81
|
comm_dir=DEFAULT_COMMAND_PATH,
|
78
82
|
sequence_dir=SEQUENCE_DIR,
|
79
83
|
method_dir=DEFAULT_METHOD_DIR)
|
@@ -107,4 +111,4 @@ Lucy Hao, Maria Politi
|
|
107
111
|
Group. Copyright © Cronin Group, used under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) license.
|
108
112
|
- Adapted from the [MACROS](https://github.com/Bourne-Group/HPLCMethodOptimisationGUI)
|
109
113
|
used in [**Operator-free HPLC automated method development guided by Bayesian optimization**](https://pubs.rsc.org/en/content/articlelanding/2024/dd/d4dd00062e),
|
110
|
-
created by members in the Bourne Group. Copyright © Bourne Group, used under the [MIT](https://opensource.org/license/mit) license.
|
114
|
+
created by members in the Bourne Group. Copyright © Bourne Group, used under the [MIT](https://opensource.org/license/mit) license.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
pychemstation/__init__.py,sha256=SpTl-Tg1B1HTyjNOE-8ue-N2wGnXN_2zl7RFUSxlkiM,33
|
2
|
+
pychemstation/out.txt,sha256=PLwo0w7AOhwQUSmvf3jupRuBH9eEtPmVhLrVaFoDqJo,5990
|
3
|
+
pychemstation/tests.ipynb,sha256=rDrl2AyP47sWO5ZmPxUrMzExS8ex1k1NIe9UbMwkKio,41136
|
4
|
+
pychemstation/analysis/__init__.py,sha256=EWoU47iyn9xGS-b44zK9eq50bSjOV4AC5dvt420YMI4,44
|
5
|
+
pychemstation/analysis/base_spectrum.py,sha256=dPULGlr-sMHNMDIdyzlvX8taw_nkBliKTb00lNI8cwk,16517
|
6
|
+
pychemstation/analysis/process_report.py,sha256=wysKaA06UeK_9ba2SZc9maP4P-HQfZDMD9NNt21kWcY,11850
|
7
|
+
pychemstation/analysis/spec_utils.py,sha256=UOo9hJR3evJfmaohEEsyb7aq6X996ofuUfu-GKjiDi8,10201
|
8
|
+
pychemstation/analysis/utils.py,sha256=rgpTJTrpsiBANbtsfys9xj0sqlTe__3J0OSeoygaQTM,2081
|
9
|
+
pychemstation/control/README.md,sha256=7Q0rY014y7Qq8wfL7GSQG0l2P1PXfmgB0qZ2YkkE7n0,3350
|
10
|
+
pychemstation/control/__init__.py,sha256=4xTy8X-mkn_PPZKr7w9rnj1wZhtmTesbQptPhpYmKXs,64
|
11
|
+
pychemstation/control/hplc.py,sha256=E_QFMOqUabiqCO6Pu4lZYTsiWhdQULw4AqyKPwiQx90,12628
|
12
|
+
pychemstation/control/controllers/README.md,sha256=S5cd4NJmPjs6TUH98BtPJJhiS1Lu-mxLCNS786ogOrQ,32
|
13
|
+
pychemstation/control/controllers/__init__.py,sha256=LuFEsVGN5sXuHW_DG61DWBDJ_fU4dMls4bQJ5XNRaok,166
|
14
|
+
pychemstation/control/controllers/comm.py,sha256=fRCFh3Ye-HnoGaur69NRdkLHNtdlMoJjIOTqeJe1tAk,7720
|
15
|
+
pychemstation/control/controllers/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
pychemstation/control/controllers/devices/device.py,sha256=d9SwasjXTXplkRK5eH3asrNPo3BBZmAz6CKBYAuKSIc,1249
|
17
|
+
pychemstation/control/controllers/devices/injector.py,sha256=ynPQtvMFt1iK0LQBf4ZEYdxJCyavmashXwyCQbmRjuw,5542
|
18
|
+
pychemstation/control/controllers/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
+
pychemstation/control/controllers/tables/method.py,sha256=UMLwzsMYsZx53PMgmKIe6LcK4JjAUpuK41x9VMJObNw,17709
|
20
|
+
pychemstation/control/controllers/tables/ms.py,sha256=JFD-tOhu8uRyKdl-E3-neRssii8MNqVRIlsrnFhNY_M,682
|
21
|
+
pychemstation/control/controllers/tables/sequence.py,sha256=UT0KUDnn8A-19V24ZgW3KduSulvtbdvsJF9NJBUrz9M,13109
|
22
|
+
pychemstation/control/controllers/tables/table.py,sha256=Adis9584LJqP4yjLj5XrIxqq1jzdBiDieOG4vc90EIU,12327
|
23
|
+
pychemstation/generated/__init__.py,sha256=GAoZFAYbPVEJDkcOw3e1rgOqd7TCW0HyKNPM8OMehMg,1005
|
24
|
+
pychemstation/generated/dad_method.py,sha256=zfS9op450CRSGPKkUr9qUyPBbND06b9N8SUU9j4cosM,8408
|
25
|
+
pychemstation/generated/pump_method.py,sha256=c_FB14rgODZyH5eDb3kxZAI77xRj1HMpQkE2R6MypNA,12180
|
26
|
+
pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
+
pychemstation/utils/chromatogram.py,sha256=s6mqru88E9YQGLbdr2Thm2plf8urCFoHHkMqW6_1Rz4,3338
|
28
|
+
pychemstation/utils/injector_types.py,sha256=hTgC-4xnO-EXOLOsCZ0L6TX4KUsB14Q-5jU4_hCzmfM,822
|
29
|
+
pychemstation/utils/macro.py,sha256=WgXGR8fgyR_QhRML9NUD3oZZBT4LLSI9uwuragHEe2k,2904
|
30
|
+
pychemstation/utils/method_types.py,sha256=5FK7RThLhaQcLrzRi_qLnlPqZuGPtwwipP6eMoq0kpE,1638
|
31
|
+
pychemstation/utils/parsing.py,sha256=bnFIsZZwFy9NKzVUf517yN-ogzQbm0hp_aho3KUD6Is,9317
|
32
|
+
pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
|
33
|
+
pychemstation/utils/sequence_types.py,sha256=x2EClcq6ROdzeLZg63XcXXTknwl2aZ48Vuyru0xZjgA,1086
|
34
|
+
pychemstation/utils/table_types.py,sha256=7txqW_oNpkh4venSkGEtreVe6UV9dzNB1DTrIeTkQHA,3217
|
35
|
+
pychemstation/utils/tray_types.py,sha256=eOO-muUjadyvCM8JnYAZVyxJeyYBlENP1zXiFskAFbs,5049
|
36
|
+
pychemstation-0.8.1.dist-info/METADATA,sha256=pHxLXmz_fR2U_A416pLuVXVGXU0bPzG0y6WYWh6I024,4491
|
37
|
+
pychemstation-0.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
38
|
+
pychemstation-0.8.1.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
|
39
|
+
pychemstation-0.8.1.dist-info/RECORD,,
|
pychemstation/control/comm.py
DELETED
@@ -1,206 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Module to provide API for the communication with Agilent HPLC systems.
|
3
|
-
|
4
|
-
HPLCController sends commands to Chemstation software via a command file.
|
5
|
-
Answers are received via reply file. On the Chemstation side, a custom
|
6
|
-
Macro monitors the command file, executes commands and writes to the reply file.
|
7
|
-
Each command is given a number (cmd_no) to keep track of which commands have
|
8
|
-
been processed.
|
9
|
-
|
10
|
-
Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
|
11
|
-
"""
|
12
|
-
|
13
|
-
import os
|
14
|
-
import time
|
15
|
-
|
16
|
-
from result import Result, Ok, Err
|
17
|
-
from ..utils.macro import *
|
18
|
-
from ..utils.method_types import *
|
19
|
-
|
20
|
-
|
21
|
-
class CommunicationController:
|
22
|
-
"""
|
23
|
-
Class that communicates with Agilent using Macros
|
24
|
-
"""
|
25
|
-
|
26
|
-
# maximum command number
|
27
|
-
MAX_CMD_NO = 255
|
28
|
-
|
29
|
-
def __init__(
|
30
|
-
self,
|
31
|
-
comm_dir: str,
|
32
|
-
cmd_file: str = "cmd",
|
33
|
-
reply_file: str = "reply",
|
34
|
-
):
|
35
|
-
"""
|
36
|
-
:param comm_dir:
|
37
|
-
:param cmd_file: Name of command file
|
38
|
-
:param reply_file: Name of reply file
|
39
|
-
"""
|
40
|
-
if os.path.isdir(comm_dir):
|
41
|
-
self.cmd_file = os.path.join(comm_dir, cmd_file)
|
42
|
-
self.reply_file = os.path.join(comm_dir, reply_file)
|
43
|
-
self.cmd_no = 0
|
44
|
-
else:
|
45
|
-
raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
|
46
|
-
self._most_recent_hplc_status = None
|
47
|
-
|
48
|
-
# Create files for Chemstation to communicate with Python
|
49
|
-
open(self.cmd_file, "a").close()
|
50
|
-
open(self.reply_file, "a").close()
|
51
|
-
|
52
|
-
self.reset_cmd_counter()
|
53
|
-
|
54
|
-
def get_num_val(self, cmd: str) -> Union[int, float, Err]:
|
55
|
-
self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
|
56
|
-
res = self.receive()
|
57
|
-
if res.is_ok():
|
58
|
-
return res.ok_value.num_response
|
59
|
-
else:
|
60
|
-
raise RuntimeError("Failed to get number.")
|
61
|
-
|
62
|
-
def get_text_val(self, cmd: str) -> str:
|
63
|
-
self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
|
64
|
-
res = self.receive()
|
65
|
-
if res.is_ok():
|
66
|
-
return res.ok_value.string_response
|
67
|
-
else:
|
68
|
-
raise RuntimeError("Failed to get string")
|
69
|
-
|
70
|
-
def get_status(self) -> Union[HPLCRunningStatus, HPLCAvailStatus, HPLCErrorStatus]:
|
71
|
-
"""Get device status(es).
|
72
|
-
|
73
|
-
:return: list of ChemStation's current status
|
74
|
-
"""
|
75
|
-
self.send(Command.GET_STATUS_CMD)
|
76
|
-
time.sleep(1)
|
77
|
-
|
78
|
-
try:
|
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
|
-
except IOError:
|
83
|
-
return HPLCErrorStatus.NORESPONSE
|
84
|
-
except IndexError:
|
85
|
-
return HPLCErrorStatus.MALFORMED
|
86
|
-
|
87
|
-
def set_status(self):
|
88
|
-
"""Updates current status of HPLC machine"""
|
89
|
-
self._most_recent_hplc_status = self.get_status()
|
90
|
-
|
91
|
-
def check_if_running(self) -> bool:
|
92
|
-
"""Checks if HPLC machine is in an available state, meaning a state that data is not being written.
|
93
|
-
|
94
|
-
:return: whether the HPLC machine is in a safe state to retrieve data back."""
|
95
|
-
self.set_status()
|
96
|
-
hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
97
|
-
time.sleep(30)
|
98
|
-
self.set_status()
|
99
|
-
hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
100
|
-
return hplc_avail and hplc_actually_avail
|
101
|
-
|
102
|
-
def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
|
103
|
-
"""Low-level execution primitive. Sends a command string to HPLC.
|
104
|
-
|
105
|
-
:param cmd: string to be sent to HPLC
|
106
|
-
:param cmd_no: Command number
|
107
|
-
:param num_attempts: Number of attempts to send the command before raising exception.
|
108
|
-
:raises IOError: Could not write to command file.
|
109
|
-
"""
|
110
|
-
err = None
|
111
|
-
for _ in range(num_attempts):
|
112
|
-
time.sleep(1)
|
113
|
-
try:
|
114
|
-
with open(self.cmd_file, "w", encoding="utf8") as cmd_file:
|
115
|
-
cmd_file.write(f"{cmd_no} {cmd}")
|
116
|
-
except IOError as e:
|
117
|
-
err = e
|
118
|
-
continue
|
119
|
-
else:
|
120
|
-
return
|
121
|
-
else:
|
122
|
-
raise IOError(f"Failed to send command #{cmd_no}: {cmd}.") from err
|
123
|
-
|
124
|
-
def _receive(self, cmd_no: int, num_attempts=100) -> Result[str, str]:
|
125
|
-
"""Low-level execution primitive. Recives a response from HPLC.
|
126
|
-
|
127
|
-
:param cmd_no: Command number
|
128
|
-
:param num_attempts: Number of retries to open reply file
|
129
|
-
:raises IOError: Could not read reply file.
|
130
|
-
:return: Potential ChemStation response
|
131
|
-
"""
|
132
|
-
err = None
|
133
|
-
for _ in range(num_attempts):
|
134
|
-
time.sleep(1)
|
135
|
-
|
136
|
-
try:
|
137
|
-
with open(self.reply_file, "r", encoding="utf_16") as reply_file:
|
138
|
-
response = reply_file.read()
|
139
|
-
except OSError as e:
|
140
|
-
err = e
|
141
|
-
continue
|
142
|
-
|
143
|
-
try:
|
144
|
-
first_line = response.splitlines()[0]
|
145
|
-
response_no = int(first_line.split()[0])
|
146
|
-
except IndexError as e:
|
147
|
-
err = e
|
148
|
-
continue
|
149
|
-
|
150
|
-
# check that response corresponds to sent command
|
151
|
-
if response_no == cmd_no:
|
152
|
-
return Ok(response)
|
153
|
-
else:
|
154
|
-
continue
|
155
|
-
else:
|
156
|
-
return Err(f"Failed to receive reply to command #{cmd_no} due to {err}.")
|
157
|
-
|
158
|
-
def sleepy_send(self, cmd: Union[Command, str]):
|
159
|
-
self.send("Sleep 0.1")
|
160
|
-
self.send(cmd)
|
161
|
-
self.send("Sleep 0.1")
|
162
|
-
|
163
|
-
def send(self, cmd: Union[Command, str]):
|
164
|
-
"""Sends a command to Chemstation.
|
165
|
-
|
166
|
-
:param cmd: Command to be sent to HPLC
|
167
|
-
"""
|
168
|
-
if self.cmd_no == self.MAX_CMD_NO:
|
169
|
-
self.reset_cmd_counter()
|
170
|
-
|
171
|
-
cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
|
172
|
-
self.cmd_no += 1
|
173
|
-
self._send(cmd_to_send, self.cmd_no)
|
174
|
-
f = open("out.txt", "a")
|
175
|
-
f.write(cmd_to_send + "\n")
|
176
|
-
f.close()
|
177
|
-
|
178
|
-
def receive(self) -> Result[Response, str]:
|
179
|
-
"""Returns messages received in reply file.
|
180
|
-
|
181
|
-
:return: ChemStation response
|
182
|
-
"""
|
183
|
-
num_response_prefix = "Numerical Responses:"
|
184
|
-
str_response_prefix = "String Responses:"
|
185
|
-
possible_response = self._receive(self.cmd_no)
|
186
|
-
if Ok(possible_response):
|
187
|
-
lines = possible_response.value.splitlines()
|
188
|
-
for line in lines:
|
189
|
-
if str_response_prefix in line and num_response_prefix in line:
|
190
|
-
string_responses_dirty, _, numerical_responses = line.partition(num_response_prefix)
|
191
|
-
_, _, string_responses = string_responses_dirty.partition(str_response_prefix)
|
192
|
-
return Ok(Response(string_response=string_responses.strip(),
|
193
|
-
num_response=float(numerical_responses.strip())))
|
194
|
-
return Err(f"Could not retrieve HPLC response")
|
195
|
-
else:
|
196
|
-
return Err(f"Could not establish response to HPLC: {possible_response}")
|
197
|
-
|
198
|
-
def reset_cmd_counter(self):
|
199
|
-
"""Resets the command counter."""
|
200
|
-
self._send(Command.RESET_COUNTER_CMD.value, cmd_no=self.MAX_CMD_NO + 1)
|
201
|
-
self._receive(cmd_no=self.MAX_CMD_NO + 1)
|
202
|
-
self.cmd_no = 0
|
203
|
-
|
204
|
-
def stop_macro(self):
|
205
|
-
"""Stops Macro execution. Connection will be lost."""
|
206
|
-
self.send(Command.STOP_MACRO_CMD)
|
@@ -1,12 +0,0 @@
|
|
1
|
-
from ....control.controllers import CommunicationController
|
2
|
-
from .device import DeviceController
|
3
|
-
from ....utils.table_types import Table
|
4
|
-
|
5
|
-
|
6
|
-
class ColumnController(DeviceController):
|
7
|
-
|
8
|
-
def __init__(self, controller: CommunicationController, table: Table):
|
9
|
-
super().__init__(controller, table)
|
10
|
-
|
11
|
-
def get_row(self, row: int):
|
12
|
-
pass
|
File without changes
|
@@ -1,43 +0,0 @@
|
|
1
|
-
from ....control.controllers import CommunicationController
|
2
|
-
from .device import DeviceController
|
3
|
-
from ....utils.pump_types import Pump
|
4
|
-
from ....utils.table_types import Table
|
5
|
-
|
6
|
-
|
7
|
-
class PumpController(DeviceController):
|
8
|
-
|
9
|
-
def __init__(self, controller: CommunicationController, table: Table):
|
10
|
-
super().__init__(controller, table)
|
11
|
-
self.A1 = Pump(in_use=True, solvent="A1")
|
12
|
-
self.B1 = Pump(in_use=True, solvent="B1")
|
13
|
-
self.A2 = Pump(in_use=False, solvent="A2")
|
14
|
-
self.B2 = Pump(in_use=False, solvent="B2")
|
15
|
-
|
16
|
-
def validate_pumps(self):
|
17
|
-
invalid_A_pump_usage = self.A1.in_use and self.A2.in_use
|
18
|
-
invalid_B_pump_usage = self.B1.in_use and self.B2.in_use
|
19
|
-
if invalid_A_pump_usage or invalid_B_pump_usage:
|
20
|
-
raise AttributeError
|
21
|
-
|
22
|
-
def switch_pump(self, num: int, pump: str):
|
23
|
-
if pump == "A":
|
24
|
-
if num == 1:
|
25
|
-
self.A1.in_use = True
|
26
|
-
self.A2.in_use = False
|
27
|
-
elif num == 2:
|
28
|
-
self.A1.in_use = False
|
29
|
-
self.A2.in_use = True
|
30
|
-
elif pump == "B":
|
31
|
-
if num == 1:
|
32
|
-
self.B1.in_use = True
|
33
|
-
self.B2.in_use = False
|
34
|
-
elif num == 2:
|
35
|
-
self.B1.in_use = False
|
36
|
-
self.B2.in_use = True
|
37
|
-
self.purge()
|
38
|
-
|
39
|
-
def purge(self):
|
40
|
-
pass
|
41
|
-
|
42
|
-
def get_row(self, row: int):
|
43
|
-
pass
|