pychemstation 0.4.7.dev1__py3-none-any.whl → 0.4.7.dev3__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/control/__init__.py +3 -2
- hein-analytical-control/devices/Agilent/hplc.py → pychemstation/control/comm.py +21 -181
- pychemstation/control/method.py +232 -0
- pychemstation/control/sequence.py +140 -0
- pychemstation/control/table_controller.py +75 -0
- pychemstation/utils/__init__.py +0 -2
- {ag_hplc_macro/control → pychemstation/utils}/chromatogram.py +2 -1
- pychemstation/utils/constants.py +1 -1
- hein_analytical_control/devices/Agilent/hplc_param_types.py → pychemstation/utils/macro.py +5 -69
- pychemstation/utils/method_types.py +44 -0
- pychemstation/utils/sequence_types.py +33 -0
- pychemstation/utils/table_types.py +60 -0
- {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev3.dist-info}/METADATA +13 -12
- pychemstation-0.4.7.dev3.dist-info/RECORD +30 -0
- ag_hplc_macro/__init__.py +0 -3
- ag_hplc_macro/analysis/__init__.py +0 -1
- ag_hplc_macro/analysis/base_spectrum.py +0 -509
- ag_hplc_macro/analysis/spec_utils.py +0 -304
- ag_hplc_macro/analysis/utils.py +0 -63
- ag_hplc_macro/control/__init__.py +0 -5
- ag_hplc_macro/control/hplc.py +0 -673
- ag_hplc_macro/generated/__init__.py +0 -56
- ag_hplc_macro/generated/dad_method.py +0 -367
- ag_hplc_macro/generated/pump_method.py +0 -519
- ag_hplc_macro/utils/__init__.py +0 -2
- ag_hplc_macro/utils/constants.py +0 -15
- ag_hplc_macro/utils/hplc_param_types.py +0 -185
- hein-analytical-control/__init__.py +0 -3
- hein-analytical-control/analysis/__init__.py +0 -1
- hein-analytical-control/analysis/base_spectrum.py +0 -509
- hein-analytical-control/analysis/spec_utils.py +0 -304
- hein-analytical-control/analysis/utils.py +0 -63
- hein-analytical-control/devices/Agilent/__init__.py +0 -3
- hein-analytical-control/devices/Agilent/chemstation.py +0 -290
- hein-analytical-control/devices/Agilent/chromatogram.py +0 -129
- hein-analytical-control/devices/Agilent/hplc_param_types.py +0 -141
- hein-analytical-control/devices/Magritek/Spinsolve/__init__.py +0 -0
- hein-analytical-control/devices/Magritek/Spinsolve/commands.py +0 -495
- hein-analytical-control/devices/Magritek/Spinsolve/spectrum.py +0 -822
- hein-analytical-control/devices/Magritek/Spinsolve/spinsolve.py +0 -425
- hein-analytical-control/devices/Magritek/Spinsolve/utils/__init__.py +0 -5
- hein-analytical-control/devices/Magritek/Spinsolve/utils/connection.py +0 -168
- hein-analytical-control/devices/Magritek/Spinsolve/utils/constants.py +0 -8
- hein-analytical-control/devices/Magritek/Spinsolve/utils/exceptions.py +0 -25
- hein-analytical-control/devices/Magritek/Spinsolve/utils/parser.py +0 -340
- hein-analytical-control/devices/Magritek/Spinsolve/utils/shimming.py +0 -55
- hein-analytical-control/devices/Magritek/Spinsolve/utils/spinsolve_logging.py +0 -43
- hein-analytical-control/devices/Magritek/__init__.py +0 -0
- hein-analytical-control/devices/OceanOptics/IR/NIRQuest512.py +0 -90
- hein-analytical-control/devices/OceanOptics/IR/__init__.py +0 -0
- hein-analytical-control/devices/OceanOptics/IR/ir_spectrum.py +0 -191
- hein-analytical-control/devices/OceanOptics/Raman/__init__.py +0 -0
- hein-analytical-control/devices/OceanOptics/Raman/raman_control.py +0 -46
- hein-analytical-control/devices/OceanOptics/Raman/raman_spectrum.py +0 -148
- hein-analytical-control/devices/OceanOptics/UV/QEPro2192.py +0 -90
- hein-analytical-control/devices/OceanOptics/UV/__init__.py +0 -0
- hein-analytical-control/devices/OceanOptics/UV/uv_spectrum.py +0 -227
- hein-analytical-control/devices/OceanOptics/__init__.py +0 -0
- hein-analytical-control/devices/OceanOptics/oceanoptics.py +0 -115
- hein-analytical-control/devices/__init__.py +0 -15
- hein-analytical-control/generated/__init__.py +0 -56
- hein-analytical-control/generated/dad_method.py +0 -367
- hein-analytical-control/generated/pump_method.py +0 -519
- hein_analytical_control/__init__.py +0 -3
- hein_analytical_control/analysis/__init__.py +0 -1
- hein_analytical_control/analysis/base_spectrum.py +0 -509
- hein_analytical_control/analysis/spec_utils.py +0 -304
- hein_analytical_control/analysis/utils.py +0 -63
- hein_analytical_control/devices/Agilent/__init__.py +0 -3
- hein_analytical_control/devices/Agilent/chemstation.py +0 -290
- hein_analytical_control/devices/Agilent/chromatogram.py +0 -129
- hein_analytical_control/devices/Agilent/hplc.py +0 -436
- hein_analytical_control/devices/Magritek/Spinsolve/__init__.py +0 -0
- hein_analytical_control/devices/Magritek/Spinsolve/commands.py +0 -495
- hein_analytical_control/devices/Magritek/Spinsolve/spectrum.py +0 -822
- hein_analytical_control/devices/Magritek/Spinsolve/spinsolve.py +0 -425
- hein_analytical_control/devices/Magritek/Spinsolve/utils/__init__.py +0 -5
- hein_analytical_control/devices/Magritek/Spinsolve/utils/connection.py +0 -168
- hein_analytical_control/devices/Magritek/Spinsolve/utils/constants.py +0 -8
- hein_analytical_control/devices/Magritek/Spinsolve/utils/exceptions.py +0 -25
- hein_analytical_control/devices/Magritek/Spinsolve/utils/parser.py +0 -340
- hein_analytical_control/devices/Magritek/Spinsolve/utils/shimming.py +0 -55
- hein_analytical_control/devices/Magritek/Spinsolve/utils/spinsolve_logging.py +0 -43
- hein_analytical_control/devices/Magritek/__init__.py +0 -0
- hein_analytical_control/devices/OceanOptics/IR/NIRQuest512.py +0 -90
- hein_analytical_control/devices/OceanOptics/IR/__init__.py +0 -0
- hein_analytical_control/devices/OceanOptics/IR/ir_spectrum.py +0 -191
- hein_analytical_control/devices/OceanOptics/Raman/__init__.py +0 -0
- hein_analytical_control/devices/OceanOptics/Raman/raman_control.py +0 -46
- hein_analytical_control/devices/OceanOptics/Raman/raman_spectrum.py +0 -148
- hein_analytical_control/devices/OceanOptics/UV/QEPro2192.py +0 -90
- hein_analytical_control/devices/OceanOptics/UV/__init__.py +0 -0
- hein_analytical_control/devices/OceanOptics/UV/uv_spectrum.py +0 -227
- hein_analytical_control/devices/OceanOptics/__init__.py +0 -0
- hein_analytical_control/devices/OceanOptics/oceanoptics.py +0 -115
- hein_analytical_control/devices/__init__.py +0 -15
- hein_analytical_control/generated/__init__.py +0 -56
- hein_analytical_control/generated/dad_method.py +0 -367
- hein_analytical_control/generated/pump_method.py +0 -519
- pychemstation-0.4.7.dev1.dist-info/RECORD +0 -109
- /ag_hplc_macro/utils/chemstation.py → /pychemstation/utils/parsing.py +0 -0
- {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev3.dist-info}/LICENSE +0 -0
- {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev3.dist-info}/WHEEL +0 -0
- {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev3.dist-info}/top_level.txt +0 -0
@@ -1,495 +0,0 @@
|
|
1
|
-
"""This module provides access and use of the Spinsolve NMR remote commands"""
|
2
|
-
|
3
|
-
import os
|
4
|
-
import logging
|
5
|
-
from io import BytesIO
|
6
|
-
|
7
|
-
import xml.etree.ElementTree as ET
|
8
|
-
from xml.etree.ElementTree import ParseError
|
9
|
-
|
10
|
-
from .utils.exceptions import ProtocolError, ProtocolOptionsError, RequestError
|
11
|
-
from .utils.constants import (
|
12
|
-
SAMPLE_TAG,
|
13
|
-
SOLVENT_TAG,
|
14
|
-
USER_DATA_TAG,
|
15
|
-
)
|
16
|
-
|
17
|
-
|
18
|
-
def load_commands_from_file(protocols_path=None):
|
19
|
-
"""Loads NMR protocol commands and options from XML file.
|
20
|
-
|
21
|
-
If the file is not provided, will search for it in the default Margitek folder
|
22
|
-
|
23
|
-
Args:
|
24
|
-
protocol_options_file (str, optional): An external file containing XML commands
|
25
|
-
for Spinsolve NMR. Usually 'ProtocolOptions.xml'
|
26
|
-
|
27
|
-
Returns:
|
28
|
-
dict: A dictionary containing protocol name as a key and XML element as a value
|
29
|
-
For example:
|
30
|
-
|
31
|
-
{'1D PROTON': <Element 'Protocol'>, '1D CARBON': <Element 'Protocol'>}
|
32
|
-
|
33
|
-
Raises:
|
34
|
-
ProtocolError: In case the XML command file wasn't found or the supplied file is not
|
35
|
-
a valid XML file
|
36
|
-
"""
|
37
|
-
# If the xml wasn't provided check for it in the standard Magritek folder
|
38
|
-
# where it is created by default, <current_user>/Documents/Magritek/Spinsolve
|
39
|
-
if protocols_path is None:
|
40
|
-
current_user = os.getlogin()
|
41
|
-
spinsolve_folder = os.path.join(
|
42
|
-
"C:\\", "Users", current_user, "Documents", "Magritek", "Spinsolve"
|
43
|
-
)
|
44
|
-
spinsolve_commands_file = os.path.join(spinsolve_folder, "ProtocolOptions.xml")
|
45
|
-
try:
|
46
|
-
commands_tree = ET.parse(spinsolve_commands_file)
|
47
|
-
except FileNotFoundError:
|
48
|
-
raise ProtocolError(
|
49
|
-
"The ProtocolOptions file wasn't found in the original folder \n Please check or supply the file manually"
|
50
|
-
) from None
|
51
|
-
else:
|
52
|
-
protocol_options_file = os.path.join(protocols_path, "ProtocolOptions.xml")
|
53
|
-
try:
|
54
|
-
commands_tree = ET.parse(protocol_options_file)
|
55
|
-
except ParseError:
|
56
|
-
raise ProtocolError("Supply file is not a valid XML document") from None
|
57
|
-
commands_root = commands_tree.getroot()
|
58
|
-
protocols = {
|
59
|
-
element.get("protocol"): element for element in commands_root.iter("Protocol")
|
60
|
-
}
|
61
|
-
return protocols
|
62
|
-
|
63
|
-
|
64
|
-
def load_commands_from_device(device_message):
|
65
|
-
"""Loads command list from the connected device
|
66
|
-
|
67
|
-
Args:
|
68
|
-
device_message (bytes): A message from the instrument containing all possible
|
69
|
-
protocols and their options
|
70
|
-
|
71
|
-
Returns:
|
72
|
-
dict: A dictionary containing protocol name as a key and XML element as a value
|
73
|
-
For example:
|
74
|
-
|
75
|
-
{'1D PROTON': <Element 'Protocol'>, '1D CARBON': <Element 'Protocol'>}
|
76
|
-
"""
|
77
|
-
|
78
|
-
commands_root = ET.fromstring(device_message)
|
79
|
-
protocols = {
|
80
|
-
element.get("protocol"): element for element in commands_root.iter("Protocol")
|
81
|
-
}
|
82
|
-
return protocols
|
83
|
-
|
84
|
-
|
85
|
-
class ProtocolCommands:
|
86
|
-
"""Provides API for accessing NMR commands"""
|
87
|
-
|
88
|
-
# Short list of most commonly used NMR protocols for easier maintenance
|
89
|
-
|
90
|
-
PROTON = "1D PROTON"
|
91
|
-
CARBON = "1D CARBON"
|
92
|
-
FLUORINE = "1D FLUORINE"
|
93
|
-
|
94
|
-
PROTON_EXTENDED = "1D EXTENDED+"
|
95
|
-
CARBON_EXTENDED = "1D CARBON+"
|
96
|
-
FLUORINE_EXTENDED = "1D FLUORINE+"
|
97
|
-
|
98
|
-
SHIM_ON_SAMPLE_PROTOCOL = "SHIM 1H SAMPLE"
|
99
|
-
SHIM_PROTOCOL = "SHIM"
|
100
|
-
|
101
|
-
# Although this is a request and should be in RequestCommands
|
102
|
-
# The XML message syntax is similar to "Start protocol"
|
103
|
-
ESTIMATE_DURATION_REQUEST = "EstimateDurationRequest"
|
104
|
-
|
105
|
-
def __init__(self, protocols_path=None):
|
106
|
-
"""Initialiser for the protocol commands
|
107
|
-
|
108
|
-
Loads the commands from the supplied file or directly from the NMR
|
109
|
-
instrument and stores them as an internal dictionary. Also provides
|
110
|
-
methods to access specific commands and generate valid XML strings
|
111
|
-
from supplied command tuple
|
112
|
-
|
113
|
-
Args:
|
114
|
-
protocol_path (str, optional): Optional path to the location of the
|
115
|
-
ProtocolOptions.xml file
|
116
|
-
device (Any, optional): Reserved for future use to load command list from the
|
117
|
-
instrument
|
118
|
-
"""
|
119
|
-
|
120
|
-
self.logger = logging.getLogger("spinsolve.commandsapi")
|
121
|
-
|
122
|
-
self._protocols = load_commands_from_file(protocols_path)
|
123
|
-
|
124
|
-
def __iter__(self):
|
125
|
-
"""Yields every protocol name"""
|
126
|
-
|
127
|
-
for protocol in self._protocols.keys():
|
128
|
-
yield protocol
|
129
|
-
|
130
|
-
def generate_command(self, protocol_and_options, custom_tag="Start"):
|
131
|
-
"""Generates XML message for the command to execute the requested protocol with requested options
|
132
|
-
|
133
|
-
Args:
|
134
|
-
protocol_and_options (tuple): Tuple with protocol name and dictionary with protocol
|
135
|
-
option names and values. Example: ('1D PROTON', {'Scan': 'QuickScan'})
|
136
|
-
|
137
|
-
Returns:
|
138
|
-
bytes: encoded to 'utf-8' string containing the valid XML message to be sent to the
|
139
|
-
NMR instrument to start the requested protocol
|
140
|
-
|
141
|
-
Raises:
|
142
|
-
ProtocolError: If the supplied protocol is not a valid command
|
143
|
-
ProtocolOptionsError: If the supplied option or its value are not validated in a protocol
|
144
|
-
command
|
145
|
-
|
146
|
-
Example:
|
147
|
-
>>> generate_command(
|
148
|
-
('valid_protocol_name',
|
149
|
-
{'valid_protocol_option_name': 'valid_protocol_option_value'}))
|
150
|
-
b'<?xml version=\'1.0\' encoding=\'utf-8\'?>
|
151
|
-
<Message>
|
152
|
-
<Start protocol="valid_protocol_name">
|
153
|
-
<Option name="valid_protocol_option_name" value="valid_protocol_option_value" />
|
154
|
-
</Protocol>
|
155
|
-
</Message>'
|
156
|
-
"""
|
157
|
-
|
158
|
-
# Checking supplied argument types
|
159
|
-
self.logger.debug(
|
160
|
-
"Checking the supplied attributes for the protocol <%s> - <%s>",
|
161
|
-
protocol_and_options[0],
|
162
|
-
protocol_and_options[1],
|
163
|
-
)
|
164
|
-
if (
|
165
|
-
not isinstance(protocol_and_options, tuple)
|
166
|
-
or len(protocol_and_options) != 2
|
167
|
-
):
|
168
|
-
raise TypeError(
|
169
|
-
"Supplied argument must be a tuple with exactly two items: protocol name and protocol options as dict"
|
170
|
-
)
|
171
|
-
try:
|
172
|
-
full_command = self.get_protocol(
|
173
|
-
protocol_and_options[0]
|
174
|
-
) # Loading the full command dictionary for future validation
|
175
|
-
except KeyError:
|
176
|
-
raise ProtocolError(
|
177
|
-
"Supplied protocol <{}> is not a valid protocol".format(
|
178
|
-
protocol_and_options[0]
|
179
|
-
)
|
180
|
-
) from None
|
181
|
-
try:
|
182
|
-
for key, value in protocol_and_options[1].items():
|
183
|
-
# Casting the value to string, as all stored values are strings
|
184
|
-
value = str(value)
|
185
|
-
# If the value list is empty but the value is expected, e.g. SHIM 1H SAMPLE - SampleReference
|
186
|
-
# All value checks should be performed when the method called
|
187
|
-
if value not in full_command[1].get(key) and full_command[1].get(key):
|
188
|
-
raise ProtocolOptionsError(
|
189
|
-
"Supplied value <{}> is not valid for the option <{}>".format(
|
190
|
-
value, key
|
191
|
-
)
|
192
|
-
)
|
193
|
-
except AttributeError:
|
194
|
-
raise TypeError(
|
195
|
-
"Supplied options should be packed into dictionary"
|
196
|
-
) from None
|
197
|
-
except ProtocolOptionsError:
|
198
|
-
raise
|
199
|
-
except (KeyError, TypeError):
|
200
|
-
raise ProtocolOptionsError(
|
201
|
-
"Supplied option <{}> is not valid for the selected protocol <{}>".format(
|
202
|
-
key, protocol_and_options[0]
|
203
|
-
)
|
204
|
-
) from None
|
205
|
-
|
206
|
-
# Creating an empty bytes object to write the future XML message
|
207
|
-
msg = BytesIO()
|
208
|
-
# First element of the XML message
|
209
|
-
msg_root = ET.Element("Message")
|
210
|
-
# First subelement of the message root as <"command"/>
|
211
|
-
# with attributes as "command_option_key"="command_option_value"
|
212
|
-
msg_root_command = ET.SubElement(
|
213
|
-
msg_root, custom_tag, {"protocol": f"{protocol_and_options[0]}"}
|
214
|
-
)
|
215
|
-
# If additional options required
|
216
|
-
for key, value in protocol_and_options[1].items():
|
217
|
-
_ = ET.SubElement(
|
218
|
-
msg_root_command, "Option", {"name": f"{key}", "value": f"{value}"}
|
219
|
-
)
|
220
|
-
# Growing a message XML tree with the <Message /> root
|
221
|
-
msg_tree = ET.ElementTree(msg_root)
|
222
|
-
# Writing the message tree to msg object
|
223
|
-
msg_tree.write(msg, encoding="utf-8", xml_declaration=True)
|
224
|
-
|
225
|
-
self.logger.debug("Message built: <%s>", msg.getvalue())
|
226
|
-
|
227
|
-
return msg.getvalue()
|
228
|
-
|
229
|
-
def get_protocol(self, protocol_name):
|
230
|
-
"""Obtains the command from XML with all available options
|
231
|
-
|
232
|
-
Args:
|
233
|
-
protocol_name (str): Valid protocol name
|
234
|
-
|
235
|
-
Returns:
|
236
|
-
tuple: Containing protocol name and protocol options with all possible
|
237
|
-
values packed as dictionary, as required by generate_command method
|
238
|
-
|
239
|
-
Raises:
|
240
|
-
ProtocolError
|
241
|
-
"""
|
242
|
-
|
243
|
-
protocol_options = {}
|
244
|
-
try:
|
245
|
-
for option_element in self._protocols[protocol_name].findall(".//Option"):
|
246
|
-
option = option_element.get("name")
|
247
|
-
option_values = []
|
248
|
-
for value_element in option_element:
|
249
|
-
if value_element.text is not None:
|
250
|
-
option_values.append(value_element.text)
|
251
|
-
protocol_options[option] = option_values
|
252
|
-
except KeyError:
|
253
|
-
raise ProtocolError(
|
254
|
-
"Supplied protocol <{}> is not a valid protocol".format(protocol_name)
|
255
|
-
) from None
|
256
|
-
return (protocol_name, protocol_options)
|
257
|
-
|
258
|
-
def reload_commands(self, data):
|
259
|
-
"""Reload the protocols from the supplied data
|
260
|
-
|
261
|
-
Args:
|
262
|
-
data (bytes): A valid XML file, usually acquired from the instrument
|
263
|
-
"""
|
264
|
-
|
265
|
-
self.logger.debug("Requested protocols update")
|
266
|
-
self._protocols = load_commands_from_device(data)
|
267
|
-
self.logger.info("Protocols dictionary updated")
|
268
|
-
|
269
|
-
### For easier access the following properties are added ###
|
270
|
-
|
271
|
-
def shim_on_sample(self, reference_peak, option):
|
272
|
-
"""Generates the command for shimming on sample
|
273
|
-
|
274
|
-
Args:
|
275
|
-
reference_peak (float): Largest peak of the supplied sample used for shimming
|
276
|
-
and scale calibration
|
277
|
-
option (str): Shimming method
|
278
|
-
"""
|
279
|
-
# Validation the supplied peak value
|
280
|
-
if isinstance(reference_peak, float):
|
281
|
-
reference_peak = str(round(reference_peak, 2))
|
282
|
-
else:
|
283
|
-
raise ProtocolOptionsError("Supplied reference peak must be float!")
|
284
|
-
|
285
|
-
return self.generate_command(
|
286
|
-
(
|
287
|
-
self.SHIM_ON_SAMPLE_PROTOCOL,
|
288
|
-
{"SampleReference": f"{reference_peak}", "Shim": f"{option}"},
|
289
|
-
)
|
290
|
-
)
|
291
|
-
|
292
|
-
@property
|
293
|
-
def proton_protocol(self):
|
294
|
-
"""Gets protocol name and available options for simple 1D Proton experiment"""
|
295
|
-
return self.get_protocol("1D PROTON")
|
296
|
-
|
297
|
-
@property
|
298
|
-
def proton_extended_protocol(self):
|
299
|
-
"""Gets protocol name and available options for extended 1D Proton experiment"""
|
300
|
-
return self.get_protocol("1D EXTENDED+")
|
301
|
-
|
302
|
-
@property
|
303
|
-
def carbon_protocol(self):
|
304
|
-
"""Gets protocol name and available options for simple 1D Carbon experiment"""
|
305
|
-
return self.get_protocol("1D CARBON")
|
306
|
-
|
307
|
-
@property
|
308
|
-
def carbon_extended_protocol(self):
|
309
|
-
"""Gets protocol name and available options for extended 1D Carbon experiment"""
|
310
|
-
return self.get_protocol("1D CARBON+")
|
311
|
-
|
312
|
-
@property
|
313
|
-
def fluorine_protocol(self):
|
314
|
-
"""Gets protocol name and available options for simple 1D Fluorine experiment"""
|
315
|
-
return self.get_protocol("1D FLUORINE")
|
316
|
-
|
317
|
-
@property
|
318
|
-
def fluorine_extended_protocol(self):
|
319
|
-
"""Gets protocol name and available options for extended 1D Fluorine experiment"""
|
320
|
-
return self.get_protocol("1D FLUORINE+")
|
321
|
-
|
322
|
-
@property
|
323
|
-
def reaction_monitoring_protocol(self):
|
324
|
-
"""Gets protocol name and available options for reaction monitoring experiment"""
|
325
|
-
return self.get_protocol("RM")
|
326
|
-
|
327
|
-
|
328
|
-
class RequestCommands:
|
329
|
-
"""Contains misc commands for NMR operation"""
|
330
|
-
|
331
|
-
### List of supported requests for easier maintenance ###
|
332
|
-
HARDWARE_REQUEST = "HardwareRequest"
|
333
|
-
AVAILABLE_PROTOCOL_OPTIONS_REQUEST = "AvailableProtocolOptionsRequest"
|
334
|
-
GET_REQUEST = "GetRequest"
|
335
|
-
CHECK_SHIM_REQUEST = "CheckShimRequest"
|
336
|
-
QUICK_SHIM_REQUEST = "QuickShimRequest"
|
337
|
-
POWER_SHIM_REQUEST = "PowerShimRequest"
|
338
|
-
ABORT_REQUEST = "Abort"
|
339
|
-
|
340
|
-
# Tags for setting user specific information
|
341
|
-
SET_TAG = "Set"
|
342
|
-
DATA_FOLDER_TAG = "DataFolder"
|
343
|
-
DATA_FOLDER_METHODS = ["UserFolder", "TimeStamp", "TimeStampTree"]
|
344
|
-
|
345
|
-
def __init__(self):
|
346
|
-
|
347
|
-
self.logger = logging.getLogger("spinsolve.requestsapi")
|
348
|
-
|
349
|
-
def generate_request(self, tag, options=None):
|
350
|
-
"""Generate the XML request message
|
351
|
-
|
352
|
-
The syntax for the request commands is slightly different from
|
353
|
-
protocol commands, so this separate method is present
|
354
|
-
|
355
|
-
Args:
|
356
|
-
tag (str): The main message tag for the request command
|
357
|
-
options (dict, optional): Request options to be supplied to the request message
|
358
|
-
|
359
|
-
Returns:
|
360
|
-
bytes: encoded to 'utf-8' string containing the valid XML request message
|
361
|
-
to be sent to the NMR instrument
|
362
|
-
"""
|
363
|
-
|
364
|
-
self.logger.debug(
|
365
|
-
"Generating request from the supplied attributes: tag - <%s>; options - <%s>",
|
366
|
-
tag,
|
367
|
-
options,
|
368
|
-
)
|
369
|
-
|
370
|
-
# Creating an empty bytes object to write the future XML message
|
371
|
-
msg = BytesIO()
|
372
|
-
# First element of the XML message
|
373
|
-
msg_root = ET.Element("Message")
|
374
|
-
# First subelement of the message root as <"command"/>
|
375
|
-
# with attributes as "command_option_key"="command_option_value"
|
376
|
-
msg_root_element = ET.SubElement(msg_root, f"{tag}")
|
377
|
-
# Special case - UserData
|
378
|
-
if options is not None and USER_DATA_TAG in options:
|
379
|
-
# Removing the appended key
|
380
|
-
options.pop(USER_DATA_TAG)
|
381
|
-
# Creating new subelement
|
382
|
-
user_data_subelement = ET.SubElement(msg_root_element, USER_DATA_TAG)
|
383
|
-
for key, value in options.items():
|
384
|
-
subelem = ET.SubElement(
|
385
|
-
user_data_subelement, "Data", {"key": f"{key}", "value": f"{value}"}
|
386
|
-
)
|
387
|
-
elif options is not None:
|
388
|
-
for key, value in options.items():
|
389
|
-
subelem = ET.SubElement(msg_root_element, f"{key}")
|
390
|
-
subelem.text = value
|
391
|
-
# Growing a message XML tree with the <Message /> root
|
392
|
-
msg_tree = ET.ElementTree(msg_root)
|
393
|
-
# Writing the message tree to msg object
|
394
|
-
msg_tree.write(msg, encoding="utf-8", xml_declaration=True)
|
395
|
-
|
396
|
-
self.logger.debug("Request generated: <%s>", msg.getvalue())
|
397
|
-
|
398
|
-
return msg.getvalue()
|
399
|
-
|
400
|
-
def request_shim(self, shim_request_option):
|
401
|
-
"""Returns the message for shimming the instrument"""
|
402
|
-
|
403
|
-
if shim_request_option not in [
|
404
|
-
self.CHECK_SHIM_REQUEST,
|
405
|
-
self.POWER_SHIM_REQUEST,
|
406
|
-
self.QUICK_SHIM_REQUEST,
|
407
|
-
]:
|
408
|
-
raise RequestError("Supplied shimming option is not valid")
|
409
|
-
|
410
|
-
return self.generate_request(shim_request_option)
|
411
|
-
|
412
|
-
def request_hardware(self):
|
413
|
-
"""Returns the message for the hardware request"""
|
414
|
-
|
415
|
-
return self.generate_request(self.HARDWARE_REQUEST)
|
416
|
-
|
417
|
-
def request_available_protocol_options(self):
|
418
|
-
"""Returns the message to request full list of available protocols and their options"""
|
419
|
-
|
420
|
-
return self.generate_request(self.AVAILABLE_PROTOCOL_OPTIONS_REQUEST)
|
421
|
-
|
422
|
-
def set_solvent_data(self, solvent):
|
423
|
-
"""Returns the message to set the solvent data.
|
424
|
-
|
425
|
-
Args:
|
426
|
-
solvent (str): Solvent name to be saved with the spectrum data.
|
427
|
-
"""
|
428
|
-
|
429
|
-
return self.generate_request(self.SET_TAG, {SOLVENT_TAG: f"{solvent}"})
|
430
|
-
|
431
|
-
def set_sample_data(self, sample):
|
432
|
-
"""Returns the message to set the solvent data.
|
433
|
-
|
434
|
-
Args:
|
435
|
-
sample (str): Sample name to be saved with the spectrum data.
|
436
|
-
"""
|
437
|
-
|
438
|
-
return self.generate_request(self.SET_TAG, {SAMPLE_TAG: f"{sample}"})
|
439
|
-
|
440
|
-
def set_data_folder(self, data_folder_path, data_folder_method):
|
441
|
-
"""Returns the message to set the data saving method and path
|
442
|
-
|
443
|
-
Args:
|
444
|
-
data_folder_path (str): valid path to save the spectral data
|
445
|
-
data_folder_method (str): one of three methods according to the manual:
|
446
|
-
'UserFolder' - data is saved directly in the provided path
|
447
|
-
'TimeStamp' - data is saved in newly created folder in format
|
448
|
-
yyyymmddhhmmss in the provided path
|
449
|
-
'TimeStampTree' - data is saved in the newly created folders in format
|
450
|
-
yyyy/mm/dd/hh/mm/ss in the provided path
|
451
|
-
"""
|
452
|
-
# Just to avoid errors from the device
|
453
|
-
if data_folder_method not in self.DATA_FOLDER_METHODS:
|
454
|
-
raise RequestError("Please use valid data folder method")
|
455
|
-
|
456
|
-
return self.generate_request(
|
457
|
-
self.DATA_FOLDER_TAG, {data_folder_method: data_folder_path}
|
458
|
-
)
|
459
|
-
|
460
|
-
def set_user_data(self, user_data):
|
461
|
-
"""Returns the message for setting the user data
|
462
|
-
|
463
|
-
Args:
|
464
|
-
user_data (dict): Dictionary contaning user specific data, will be saved
|
465
|
-
in the "acq.par" file together with spectral data
|
466
|
-
"""
|
467
|
-
# appending "UserData" key to allow custom message creation
|
468
|
-
user_data.update({USER_DATA_TAG: ""})
|
469
|
-
return self.generate_request(self.SET_TAG, user_data)
|
470
|
-
|
471
|
-
def abort(self):
|
472
|
-
"""Returns the message to abort the current operation"""
|
473
|
-
|
474
|
-
return self.generate_request(self.ABORT_REQUEST)
|
475
|
-
|
476
|
-
def get_user_data(self):
|
477
|
-
"""
|
478
|
-
Returns the message for querying the user data from the instrument.
|
479
|
-
"""
|
480
|
-
|
481
|
-
return self.generate_request(self.GET_REQUEST, {USER_DATA_TAG: ""})
|
482
|
-
|
483
|
-
def get_solvent(self):
|
484
|
-
"""
|
485
|
-
Returns the message for querying the solvent data from the instrument.
|
486
|
-
"""
|
487
|
-
|
488
|
-
return self.generate_request(self.GET_REQUEST, {SOLVENT_TAG: ""})
|
489
|
-
|
490
|
-
def get_sample(self):
|
491
|
-
"""
|
492
|
-
Returns the message for querying the sample data from the instrument.
|
493
|
-
"""
|
494
|
-
|
495
|
-
return self.generate_request(self.GET_REQUEST, {SAMPLE_TAG: ""})
|