pychemstation 0.4.7.dev1__py3-none-any.whl → 0.4.7.dev2__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.
Files changed (104) hide show
  1. pychemstation/control/__init__.py +3 -2
  2. hein-analytical-control/devices/Agilent/hplc.py → pychemstation/control/comm.py +21 -181
  3. pychemstation/control/method.py +232 -0
  4. pychemstation/control/sequence.py +140 -0
  5. pychemstation/control/table_controller.py +75 -0
  6. pychemstation/utils/__init__.py +0 -2
  7. {ag_hplc_macro/control → pychemstation/utils}/chromatogram.py +2 -1
  8. pychemstation/utils/constants.py +1 -1
  9. hein_analytical_control/devices/Agilent/hplc_param_types.py → pychemstation/utils/macro.py +5 -69
  10. pychemstation/utils/method_types.py +44 -0
  11. pychemstation/utils/sequence_types.py +33 -0
  12. pychemstation/utils/table_types.py +60 -0
  13. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/METADATA +13 -12
  14. pychemstation-0.4.7.dev2.dist-info/RECORD +30 -0
  15. ag_hplc_macro/__init__.py +0 -3
  16. ag_hplc_macro/analysis/__init__.py +0 -1
  17. ag_hplc_macro/analysis/base_spectrum.py +0 -509
  18. ag_hplc_macro/analysis/spec_utils.py +0 -304
  19. ag_hplc_macro/analysis/utils.py +0 -63
  20. ag_hplc_macro/control/__init__.py +0 -5
  21. ag_hplc_macro/control/hplc.py +0 -673
  22. ag_hplc_macro/generated/__init__.py +0 -56
  23. ag_hplc_macro/generated/dad_method.py +0 -367
  24. ag_hplc_macro/generated/pump_method.py +0 -519
  25. ag_hplc_macro/utils/__init__.py +0 -2
  26. ag_hplc_macro/utils/constants.py +0 -15
  27. ag_hplc_macro/utils/hplc_param_types.py +0 -185
  28. hein-analytical-control/__init__.py +0 -3
  29. hein-analytical-control/analysis/__init__.py +0 -1
  30. hein-analytical-control/analysis/base_spectrum.py +0 -509
  31. hein-analytical-control/analysis/spec_utils.py +0 -304
  32. hein-analytical-control/analysis/utils.py +0 -63
  33. hein-analytical-control/devices/Agilent/__init__.py +0 -3
  34. hein-analytical-control/devices/Agilent/chemstation.py +0 -290
  35. hein-analytical-control/devices/Agilent/chromatogram.py +0 -129
  36. hein-analytical-control/devices/Agilent/hplc_param_types.py +0 -141
  37. hein-analytical-control/devices/Magritek/Spinsolve/__init__.py +0 -0
  38. hein-analytical-control/devices/Magritek/Spinsolve/commands.py +0 -495
  39. hein-analytical-control/devices/Magritek/Spinsolve/spectrum.py +0 -822
  40. hein-analytical-control/devices/Magritek/Spinsolve/spinsolve.py +0 -425
  41. hein-analytical-control/devices/Magritek/Spinsolve/utils/__init__.py +0 -5
  42. hein-analytical-control/devices/Magritek/Spinsolve/utils/connection.py +0 -168
  43. hein-analytical-control/devices/Magritek/Spinsolve/utils/constants.py +0 -8
  44. hein-analytical-control/devices/Magritek/Spinsolve/utils/exceptions.py +0 -25
  45. hein-analytical-control/devices/Magritek/Spinsolve/utils/parser.py +0 -340
  46. hein-analytical-control/devices/Magritek/Spinsolve/utils/shimming.py +0 -55
  47. hein-analytical-control/devices/Magritek/Spinsolve/utils/spinsolve_logging.py +0 -43
  48. hein-analytical-control/devices/Magritek/__init__.py +0 -0
  49. hein-analytical-control/devices/OceanOptics/IR/NIRQuest512.py +0 -90
  50. hein-analytical-control/devices/OceanOptics/IR/__init__.py +0 -0
  51. hein-analytical-control/devices/OceanOptics/IR/ir_spectrum.py +0 -191
  52. hein-analytical-control/devices/OceanOptics/Raman/__init__.py +0 -0
  53. hein-analytical-control/devices/OceanOptics/Raman/raman_control.py +0 -46
  54. hein-analytical-control/devices/OceanOptics/Raman/raman_spectrum.py +0 -148
  55. hein-analytical-control/devices/OceanOptics/UV/QEPro2192.py +0 -90
  56. hein-analytical-control/devices/OceanOptics/UV/__init__.py +0 -0
  57. hein-analytical-control/devices/OceanOptics/UV/uv_spectrum.py +0 -227
  58. hein-analytical-control/devices/OceanOptics/__init__.py +0 -0
  59. hein-analytical-control/devices/OceanOptics/oceanoptics.py +0 -115
  60. hein-analytical-control/devices/__init__.py +0 -15
  61. hein-analytical-control/generated/__init__.py +0 -56
  62. hein-analytical-control/generated/dad_method.py +0 -367
  63. hein-analytical-control/generated/pump_method.py +0 -519
  64. hein_analytical_control/__init__.py +0 -3
  65. hein_analytical_control/analysis/__init__.py +0 -1
  66. hein_analytical_control/analysis/base_spectrum.py +0 -509
  67. hein_analytical_control/analysis/spec_utils.py +0 -304
  68. hein_analytical_control/analysis/utils.py +0 -63
  69. hein_analytical_control/devices/Agilent/__init__.py +0 -3
  70. hein_analytical_control/devices/Agilent/chemstation.py +0 -290
  71. hein_analytical_control/devices/Agilent/chromatogram.py +0 -129
  72. hein_analytical_control/devices/Agilent/hplc.py +0 -436
  73. hein_analytical_control/devices/Magritek/Spinsolve/__init__.py +0 -0
  74. hein_analytical_control/devices/Magritek/Spinsolve/commands.py +0 -495
  75. hein_analytical_control/devices/Magritek/Spinsolve/spectrum.py +0 -822
  76. hein_analytical_control/devices/Magritek/Spinsolve/spinsolve.py +0 -425
  77. hein_analytical_control/devices/Magritek/Spinsolve/utils/__init__.py +0 -5
  78. hein_analytical_control/devices/Magritek/Spinsolve/utils/connection.py +0 -168
  79. hein_analytical_control/devices/Magritek/Spinsolve/utils/constants.py +0 -8
  80. hein_analytical_control/devices/Magritek/Spinsolve/utils/exceptions.py +0 -25
  81. hein_analytical_control/devices/Magritek/Spinsolve/utils/parser.py +0 -340
  82. hein_analytical_control/devices/Magritek/Spinsolve/utils/shimming.py +0 -55
  83. hein_analytical_control/devices/Magritek/Spinsolve/utils/spinsolve_logging.py +0 -43
  84. hein_analytical_control/devices/Magritek/__init__.py +0 -0
  85. hein_analytical_control/devices/OceanOptics/IR/NIRQuest512.py +0 -90
  86. hein_analytical_control/devices/OceanOptics/IR/__init__.py +0 -0
  87. hein_analytical_control/devices/OceanOptics/IR/ir_spectrum.py +0 -191
  88. hein_analytical_control/devices/OceanOptics/Raman/__init__.py +0 -0
  89. hein_analytical_control/devices/OceanOptics/Raman/raman_control.py +0 -46
  90. hein_analytical_control/devices/OceanOptics/Raman/raman_spectrum.py +0 -148
  91. hein_analytical_control/devices/OceanOptics/UV/QEPro2192.py +0 -90
  92. hein_analytical_control/devices/OceanOptics/UV/__init__.py +0 -0
  93. hein_analytical_control/devices/OceanOptics/UV/uv_spectrum.py +0 -227
  94. hein_analytical_control/devices/OceanOptics/__init__.py +0 -0
  95. hein_analytical_control/devices/OceanOptics/oceanoptics.py +0 -115
  96. hein_analytical_control/devices/__init__.py +0 -15
  97. hein_analytical_control/generated/__init__.py +0 -56
  98. hein_analytical_control/generated/dad_method.py +0 -367
  99. hein_analytical_control/generated/pump_method.py +0 -519
  100. pychemstation-0.4.7.dev1.dist-info/RECORD +0 -109
  101. /ag_hplc_macro/utils/chemstation.py → /pychemstation/utils/parsing.py +0 -0
  102. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/LICENSE +0 -0
  103. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/WHEEL +0 -0
  104. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/top_level.txt +0 -0
@@ -1,425 +0,0 @@
1
- """
2
- Module provide API for the remote control of the Magritek SpinSolve NMR
3
- """
4
- import queue
5
- import threading
6
- import warnings
7
- import os
8
- import json
9
- import time
10
-
11
- from .utils import ReplyParser, SpinsolveConnection, get_logger
12
- from .utils.exceptions import HardwareError
13
- from .utils.shimming import shimming
14
- from .commands import ProtocolCommands, RequestCommands
15
- from .spectrum import SpinsolveNMRSpectrum
16
-
17
-
18
- # shimming parameters are stored here
19
- # after shimming operation has been performed
20
- SHIMMING_PATH = os.path.join(os.path.dirname(__file__), "utils", "shimming.json")
21
-
22
-
23
- class SpinsolveNMR:
24
- """Python class to handle Magritek Spinsolve NMR instrument"""
25
-
26
- DEFAULT_EXPERIMENT = ("1D PROTON", {"Scan": "StandardScan"})
27
-
28
- def __init__(
29
- self, spinsolve_options_path=None, address=None, port=13000, auto_connect=True
30
- ):
31
- """
32
- Args:
33
- spinsolve_options_path (str, optional): Valid path to the ProtocolOptions.xml
34
- address (str, optional): IP address of the local host
35
- host (int, optional): host for the TCP/IP connection to the Spinsolve software
36
- auto_connect (bool, optional): If you need to connect to the instrument immediately
37
- after instantiation
38
- """
39
-
40
- self.logger = get_logger()
41
-
42
- # Queue for storing path of the measured spectrum
43
- self.data_folder_queue = queue.Queue()
44
-
45
- # Flag for check the instrument status
46
- self._device_ready_flag = threading.Event()
47
-
48
- # Instantiating submodules
49
- self._parser = ReplyParser(self._device_ready_flag, self.data_folder_queue)
50
- self._connection = SpinsolveConnection(HOST=address, PORT=port)
51
- self.cmd = ProtocolCommands(spinsolve_options_path)
52
- self.req_cmd = RequestCommands()
53
- self.spectrum = SpinsolveNMRSpectrum()
54
-
55
- if auto_connect:
56
- self.connect()
57
- self.initialise()
58
-
59
- # placeholder to store shimming parameters
60
- self.last_shimming_results = {}
61
-
62
- # placeholders for experiment data
63
- self._user_data = {}
64
- self._solvent = None
65
- self._sample = None
66
-
67
- def check_last_shimming(self):
68
- """Checks last shimming.
69
-
70
- Returns:
71
- bool: False if shimming procedure is required, True otherwise.
72
- """
73
- if not self.last_shimming_results:
74
- try:
75
- with open(SHIMMING_PATH) as fobj:
76
- self.last_shimming_results = json.load(fobj)
77
- except FileNotFoundError:
78
- self.logger.warning(
79
- "Last shimming was not recorded, please run\
80
- any shimming protocol to update!"
81
- )
82
- return False
83
- now = time.time()
84
- # if the last shimming was performed more than 24 hours ago
85
- if now - self.last_shimming_results["timestamp"] > 24 * 3600:
86
- self.logger.critical(
87
- "Last shimming was performed more than 24 \
88
- hours ago, please perform CheckShim to check spectrometer performance!"
89
- )
90
- return False
91
- return True
92
-
93
- def connect(self):
94
- """Connects to the instrument"""
95
-
96
- self.logger.debug("Connection requested")
97
- self._connection.open_connection()
98
-
99
- def disconnect(self):
100
- """Closes the socket connection"""
101
-
102
- self.logger.info("Request to close the connection received")
103
- self._connection.close_connection()
104
- self.logger.info("The instrument is disconnected")
105
-
106
- def send_message(self, msg):
107
- """Sends the message to the instrument"""
108
-
109
- if self._parser.connected_tag != "true":
110
- raise HardwareError(
111
- "The instrument is not connected, check the Spinsolve software"
112
- )
113
- self.logger.debug("Waiting for the device to be ready")
114
- self._device_ready_flag.wait()
115
- self.logger.debug("Sending the message \n%s", msg)
116
- self._connection.transmit(msg)
117
- self.logger.debug("Message sent")
118
-
119
- def receive_reply(self, parse=True):
120
- """Receives the reply from the instrument and parses it if necessary"""
121
-
122
- while True:
123
- self.logger.debug("Reply requested from the connection")
124
- reply = self._connection.receive()
125
- self.logger.debug("Reply received")
126
- if parse:
127
- reply = self._parser.parse(reply)
128
- if self._device_ready_flag.is_set():
129
- return reply
130
-
131
- def initialise(self):
132
- """Initialises the instrument by sending HardwareRequest"""
133
-
134
- cmd = self.req_cmd.request_hardware()
135
- self._connection.transmit(cmd)
136
- return self.receive_reply()
137
-
138
- def is_instrument_ready(self):
139
- """Checks if the instrument is ready for the next command"""
140
-
141
- if self._parser.connected_tag == "true" and self._device_ready_flag.is_set():
142
- return True
143
- else:
144
- return False
145
-
146
- def load_commands(self):
147
- """Requests the available commands from the instrument"""
148
-
149
- cmd = self.req_cmd.request_available_protocol_options()
150
- self.send_message(cmd)
151
- reply = self.receive_reply()
152
- self.cmd.reload_commands(reply)
153
- self.logger.info(
154
- "Commands updated, see available protocols \n <%s>",
155
- list(self.cmd._protocols.keys()),
156
- ) # pylint: disable=protected-access
157
-
158
- @shimming
159
- def shim(
160
- self,
161
- option="CheckShimRequest",
162
- *,
163
- line_width_threshold=1,
164
- base_width_threshold=40,
165
- ):
166
- """Initialise shimming protocol
167
-
168
- Consider checking <Spinsolve>.cmd.get_protocol(<Spinsolve>.cmd.SHIM_PROTOCOL) for available options
169
-
170
- Args:
171
- option (str, optional): A name of the instrument shimming method
172
- """
173
-
174
- # updating default values
175
- self._parser.shimming_line_width_threshold = line_width_threshold
176
- self._parser.shimming_base_width_threshold = base_width_threshold
177
-
178
- cmd = self.req_cmd.request_shim(option)
179
- self.send_message(cmd)
180
- return self.receive_reply()
181
-
182
- @shimming
183
- def shim_on_sample(
184
- self,
185
- reference_peak,
186
- option="LockAndCalibrateOnly",
187
- *,
188
- line_width_threshold=1,
189
- base_width_threshold=40,
190
- ):
191
- """Initialise shimming on sample protocol
192
-
193
- Consider checking <Spinsolve>.cmd.get_protocol(<Spinsolve>.cmd.SHIM_ON_SAMPLE_PROTOCOL) for available options
194
-
195
- Args:
196
- reference_peak (float): A reference peak to shim and calibrate on
197
- option (str, optional): A name of the instrument shimming method
198
- line_width_threshold (float, optional): Spectrum line width at 50%, should be below 1
199
- for good quality spectrums
200
- base_width_threshold (float, optional): Spectrum line width at 0.55%, should be below 40
201
- for good quality spectrums
202
- """
203
-
204
- self._parser.shimming_line_width_threshold = line_width_threshold
205
- self._parser.shimming_base_width_threshold = base_width_threshold
206
- cmd = self.cmd.shim_on_sample(reference_peak, option)
207
- self.send_message(cmd)
208
- return self.receive_reply()
209
-
210
- def set_user_folder(self, data_path, data_folder_method="TimeStamp"):
211
- """Indicate the path and the method for saving NMR data
212
-
213
- Args:
214
- data_folder_path (str): Valid path to save the spectral data
215
- data_folder_method (str, optional): One of three methods according to the manual:
216
- 'UserFolder' - Data is saved directly in the provided path
217
- 'TimeStamp' (default) - Data is saved in newly created folder in format
218
- yyyymmddhhmmss in the provided path
219
- 'TimeStampTree' - Data is saved in the newly created folders in format
220
- yyyy/mm/dd/hh/mm/ss in the provided path
221
-
222
- Returns:
223
- bool: True if successfull
224
- """
225
-
226
- cmd = self.req_cmd.set_data_folder(data_path, data_folder_method)
227
- self.send_message(cmd)
228
- return True
229
-
230
- @property
231
- def user_data(self):
232
- """Dictionary with user specific data."""
233
- if not self._user_data:
234
- user_data_req = self.req_cmd.get_user_data()
235
- self.send_message(user_data_req)
236
- self._user_data = self.receive_reply()
237
-
238
- return self._user_data
239
-
240
- @user_data.setter
241
- def user_data(self, user_data):
242
- """Sets the user data.
243
-
244
- Args:
245
- user_data (Dict): Dictionary with user data.
246
- """
247
- # updating placeholder
248
- self._user_data.update(user_data)
249
- # sending command
250
- user_data_cmd = self.req_cmd.set_user_data(user_data)
251
- self.send_message(user_data_cmd)
252
-
253
- @user_data.deleter
254
- def user_data(self):
255
- """Removes previously stored user data."""
256
-
257
- # generating command to reset the data in spinsolve
258
- empty_user_data_command = self.req_cmd.set_user_data(
259
- {key: "" for key in self._user_data}
260
- )
261
- self.send_message(empty_user_data_command)
262
-
263
- # updating placeholder
264
- self._user_data = {}
265
-
266
- @property
267
- def solvent(self):
268
- """Solvent record to be stored with spectrum acquisition params."""
269
- if self._solvent is None:
270
- solvent_req = self.req_cmd.get_solvent()
271
- self.send_message(solvent_req)
272
- self._solvent = self.receive_reply()
273
- return self._solvent
274
-
275
- @solvent.setter
276
- def solvent(self, solvent):
277
- """Sets the solvent record for the current experiment."""
278
- self._solvent = solvent
279
- solvent_data_cmd = self.req_cmd.set_solvent_data(solvent)
280
- self.send_message(solvent_data_cmd)
281
-
282
- @solvent.deleter
283
- def solvent(self):
284
- """Removes the solvent record for the current experiment."""
285
- self._solvent = None
286
- empty_solvent_data_cmd = self.req_cmd.set_solvent_data("")
287
- self.send_message(empty_solvent_data_cmd)
288
-
289
- @property
290
- def sample(self):
291
- """Sample record to be stored with spectrum acquisition params."""
292
- if self._sample is None:
293
- sample_req = self.req_cmd.get_sample()
294
- self.send_message(sample_req)
295
- self._sample = self.receive_reply()
296
- return self._sample
297
-
298
- @sample.setter
299
- def sample(self, sample):
300
- """Sets the sample record for the current experiment.
301
-
302
- Also sets the folder to save the spectrum, so avoid special characters.
303
- """
304
- self._sample = sample
305
- sample_data_cmd = self.req_cmd.set_sample_data(sample)
306
- self.send_message(sample_data_cmd)
307
-
308
- @sample.deleter
309
- def sample(self):
310
- """Removes the sample record for the current experiment."""
311
- self._sample = None
312
- empty_sample_data_cmd = self.req_cmd.set_sample_data("")
313
- self.send_message(empty_sample_data_cmd)
314
-
315
- def get_duration(self, protocol, options):
316
- """Requests for an approximate duration of a specific protocol
317
-
318
- Args:
319
- protocol (str): A name of the specific protocol
320
- options (dict): Options for the selected protocol
321
- """
322
-
323
- cmd = self.cmd.generate_command(
324
- (protocol, options), self.cmd.ESTIMATE_DURATION_REQUEST
325
- )
326
- self.send_message(cmd)
327
- return self.receive_reply()
328
-
329
- def proton(self, option="QuickScan"):
330
- """Initialise simple 1D Proton experiment"""
331
-
332
- cmd = self.cmd.generate_command((self.cmd.PROTON, {"Scan": f"{option}"}))
333
- self.send_message(cmd)
334
- return self.receive_reply()
335
-
336
- def proton_extended(self, options):
337
- """Initialise extended 1D Proton experiment"""
338
-
339
- cmd = self.cmd.generate_command((self.cmd.PROTON_EXTENDED, options))
340
- self.send_message(cmd)
341
- return self.receive_reply()
342
-
343
- def carbon(self, options=None):
344
- """Initialise simple 1D Carbon experiment"""
345
-
346
- if options is None:
347
- options = {"Number": "128", "RepetitionTime": "2"}
348
- cmd = self.cmd.generate_command((self.cmd.CARBON, options))
349
- self.send_message(cmd)
350
- return self.receive_reply()
351
-
352
- def carbon_extended(self, options):
353
- """Initialise extended 1D Carbon experiment"""
354
-
355
- cmd = self.cmd.generate_command((self.cmd.CARBON_EXTENDED, options))
356
- self.send_message(cmd)
357
- return self.receive_reply()
358
-
359
- def fluorine(self, option="QuickScan"):
360
- """Initialise simple 1D Fluorine experiment"""
361
-
362
- cmd = self.cmd.generate_command((self.cmd.FLUORINE, option))
363
- self.send_message(cmd)
364
- return self.receive_reply()
365
-
366
- def fluorine_extended(self, options):
367
- """Initialise extended 1D Fluorine experiment"""
368
-
369
- cmd = self.cmd.generate_command((self.cmd.FLUORINE_EXTENDED, options))
370
- self.send_message(cmd)
371
- return self.receive_reply()
372
-
373
- def wait_until_ready(self):
374
- """Blocks until the instrument is ready"""
375
-
376
- self._device_ready_flag.wait()
377
-
378
- def calibrate(self, reference_peak, option="LockAndCalibrateOnly"):
379
- """Performs shimming on sample protocol"""
380
-
381
- self.logger.warning("DEPRECATION WARNING: use shim_on_sample() method instead")
382
- return self.shim_on_sample(reference_peak, option)
383
-
384
- @property
385
- def protocols_list(self):
386
- """Returns a list of all available protocols"""
387
-
388
- return list(self.cmd)
389
-
390
- def get_spectrum(self, protocol=None):
391
- """Wrapper method to load the spectral data to inner Spectrum class.
392
-
393
- Loads the last measured data. If no data previously measured, will
394
- perform self.DEFAULT_EXPERIMENT and load its data.
395
- """
396
-
397
- if self.data_folder_queue.empty():
398
- self.logger.warning("No previous data.")
399
- if protocol is None:
400
- protocol = self.DEFAULT_EXPERIMENT
401
- self.logger.warning(
402
- "Running default <%s> protocol.", self.DEFAULT_EXPERIMENT[0]
403
- )
404
- cmd = self.cmd.generate_command(protocol)
405
- self.send_message(cmd)
406
- self.receive_reply()
407
-
408
- # will block if spectrum is measuring
409
- data_folder = self.data_folder_queue.get()
410
-
411
- self.spectrum.load_spectrum(data_folder)
412
-
413
- warning_message = 'Method "get_spectrum" will no longer return the \
414
- spectropic data. Please use .spectrum class to access the spectral data and \
415
- to the documentation for its usage.'
416
-
417
- warnings.warn(warning_message, DeprecationWarning)
418
-
419
- # for backwards compatibility
420
- data1d = os.path.join(data_folder, "data.1d")
421
- _, fid_real, fid_img = self.spectrum.extract_data(data1d)
422
-
423
- fid_complex = [complex(real, img) for real, img in zip(fid_real, fid_img)]
424
-
425
- return fid_complex
@@ -1,5 +0,0 @@
1
- from .parser import ReplyParser
2
- from .connection import SpinsolveConnection
3
-
4
- # utility functions
5
- from .spinsolve_logging import get_logger
@@ -1,168 +0,0 @@
1
- """
2
- Python module for connection with Spinsolve NMR software.
3
- """
4
- import logging
5
- import socket
6
- import threading
7
- import queue
8
-
9
-
10
- class SpinsolveConnection:
11
- """Provides API for the socket connection to the Spinsolve NMR instrument"""
12
-
13
- def __init__(self, HOST=None, PORT=13000):
14
- """
15
- Args:
16
- HOST (str, optional): TCP/IP address of the local host
17
- PORT (int, optional): TCP/IP listening port for Spinsolve software, 13000 by default
18
- must be changed in the software if necessary
19
- """
20
-
21
- # Getting the localhost IP address if not provided by instantiation
22
- # refer to socket module manual for details
23
- try:
24
- CURR_HOST = socket.gethostbyname(socket.getfqdn())
25
- except socket.gaierror:
26
- CURR_HOST = socket.gethostbyname(socket.gethostname())
27
-
28
- # Connection parameters
29
- if HOST is not None:
30
- self.HOST = HOST
31
- else:
32
- self.HOST = CURR_HOST
33
- self.PORT = PORT
34
-
35
- # The buffer size is so big for the only large message sent by the instrument - whole list of
36
- # Protocol options. One day will be reduced with addition of non-blocking parser/connection
37
- # TODO
38
- self.BUFSIZE = 65536
39
-
40
- # Connection object, thread, lock and disconnection request tag
41
- self._listener = None
42
- self._connection = None
43
- self._connection_close_requested = threading.Event()
44
-
45
- # Response queue for inter threading commincation
46
- self.response_queue = queue.Queue()
47
-
48
- self.logger = logging.getLogger("spinsolve.connection")
49
-
50
- def open_connection(self):
51
- """Open a socket connection to the Spinsolve software"""
52
-
53
- if self._connection is not None:
54
- self.logger.warning(
55
- "You are trying to open connection that is already open"
56
- )
57
- return
58
-
59
- # Creating socket
60
- self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
61
- self._connection.settimeout(None)
62
-
63
- # Connecting and spawning listening thread
64
- try:
65
- self._connection.connect((self.HOST, self.PORT))
66
- except ConnectionRefusedError:
67
- self._connection = None # Resetting the internal attribute
68
- raise ConnectionRefusedError(
69
- "Please run Spinsolve software and enable remote control!"
70
- )
71
- self.logger.debug("Connection at %s:%s is opened", self.HOST, self.PORT)
72
- self._listener = threading.Thread(
73
- target=self.connection_listener,
74
- name="{}_listener".format(__name__),
75
- daemon=False,
76
- )
77
- self._listener.start()
78
- self.logger.info("Connection created")
79
-
80
- def connection_listener(self):
81
- """Checks for the new data and output it into receive buffer"""
82
-
83
- self.logger.info("Connection listener thread is starting")
84
-
85
- while True:
86
- try:
87
- # Receiving data
88
- chunk = self._connection.recv(self.BUFSIZE)
89
- self.logger.debug("New chunk %s", chunk.decode())
90
- self.response_queue.put(chunk)
91
- self.logger.debug("Message added to the response queue")
92
- except ConnectionAbortedError:
93
- self.logger.warning("Connection aborted")
94
- break
95
- except ConnectionResetError:
96
- self.logger.warning("Spinsolve app is closed")
97
- break
98
- except OSError:
99
- self.logger.warning("Connection error")
100
- break
101
- self.logger.info("Exiting listening thread")
102
-
103
- def transmit(self, msg):
104
- """Sends the message to the socket
105
-
106
- Args:
107
- msg (bytes): encoded message to be sent to the instrument
108
- """
109
-
110
- self.logger.debug("Sending the message")
111
- # This is necessary due to a random bug in the Spinsolve software with
112
- # wrong order of the messages sent.
113
- # See details in AnalyticalLabware/issues/22
114
- while True:
115
- try:
116
- unprocessed = self.response_queue.get_nowait()
117
- self.logger.error(
118
- "Unprocessed message obtained from the response queue, \
119
- see below:\n%s",
120
- unprocessed,
121
- )
122
- except queue.Empty:
123
- break
124
- self._connection.send(msg)
125
- self.logger.debug("Message sent")
126
-
127
- def receive(self):
128
- """Grabs the message from receive buffer"""
129
-
130
- self.logger.debug("Receiving the message from the responce queue")
131
- reply = self.response_queue.get()
132
- self.response_queue.task_done()
133
- self.logger.debug("Message obtained from the queue")
134
-
135
- return reply
136
-
137
- def close_connection(self):
138
- """Closes connection"""
139
-
140
- self.logger.debug("Socket connection closure requested")
141
- self._connection_close_requested.set()
142
- if self._connection is not None:
143
- self._connection.shutdown(socket.SHUT_RDWR)
144
- self._connection.close()
145
- self._connection = None # To available subsequent calls to open_connection after connection was once closed
146
- self._connection_close_requested.clear()
147
- self.logger.info("Socket connection closed")
148
- else:
149
- self.logger.warning("You are trying to close nonexistent connection")
150
- if self._listener is not None and self._listener.is_alive():
151
- self._listener.join(timeout=3)
152
-
153
- def _flush_the_queue(self):
154
- while True:
155
- try:
156
- data = self.response_queue.get_nowait()
157
- if data:
158
- self.logger.warning(
159
- "Response queue flushed, something inside %s", data
160
- )
161
- self.response_queue.task_done()
162
- except queue.Empty:
163
- break
164
-
165
- def is_connection_open(self):
166
- """Checks if the connection to the instrument is still alive"""
167
- # TODO
168
- raise NotImplementedError
@@ -1,8 +0,0 @@
1
- """ Spinsolve NMR API related constants """
2
-
3
- CURRENT_SPINSOLVE_VERSION = "1.17.6"
4
-
5
- # tags for request/response communication
6
- SAMPLE_TAG = "Sample"
7
- SOLVENT_TAG = "Solvent"
8
- USER_DATA_TAG = "UserData"
@@ -1,25 +0,0 @@
1
- """Module containts general SpinSolve errors"""
2
-
3
-
4
- class HardwareError(Exception):
5
- """Generic error in hardware operation"""
6
-
7
-
8
- class NMRError(Exception):
9
- """Generic error in NMR operation"""
10
-
11
-
12
- class ProtocolError(NMRError):
13
- """Generic error in Protocol handling"""
14
-
15
-
16
- class ProtocolOptionsError(KeyError):
17
- """Error in selecting correct options for chosen protocol"""
18
-
19
-
20
- class ShimmingError(HardwareError):
21
- """Specific error in case of poor instrument shimming"""
22
-
23
-
24
- class RequestError(KeyError):
25
- """Specific error in case of wrong request type"""