pytrms 0.9.3__tar.gz → 0.9.6__tar.gz
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.
- {pytrms-0.9.3 → pytrms-0.9.6}/PKG-INFO +1 -1
- {pytrms-0.9.3 → pytrms-0.9.6}/pyproject.toml +1 -1
- pytrms-0.9.6/pytrms/__init__.py +42 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/clients/db_api.py +1 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/clients/mqtt.py +27 -7
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/data/ParaIDs.csv +8 -8
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/instrument.py +22 -14
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/measurement.py +77 -25
- pytrms-0.9.3/pytrms/__init__.py +0 -38
- {pytrms-0.9.3 → pytrms-0.9.6}/LICENSE +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/_base/__init__.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/_base/ioniclient.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/_base/mqttclient.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/_version.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/clients/__init__.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/clients/ioniclient.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/clients/modbus.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/clients/ssevent.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/compose/__init__.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/compose/composition.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/data/IoniTofPrefs.ini +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/helpers.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/peaktable.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/plotting/__init__.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/plotting/plotting.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/readers/__init__.py +0 -0
- {pytrms-0.9.3 → pytrms-0.9.6}/pytrms/readers/ionitof_reader.py +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
_version = '0.9.6'
|
|
2
|
+
|
|
3
|
+
__all__ = ['load', 'connect']
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load(path):
|
|
7
|
+
'''Open a datafile for post-analysis or batch processing.
|
|
8
|
+
|
|
9
|
+
`path` may be a glob-expression to collect a whole batch.
|
|
10
|
+
|
|
11
|
+
returns a `Measurement` instance.
|
|
12
|
+
'''
|
|
13
|
+
import glob
|
|
14
|
+
from .measurement import FinishedMeasurement
|
|
15
|
+
|
|
16
|
+
files = glob.glob(path)
|
|
17
|
+
|
|
18
|
+
return FinishedMeasurement(*files)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def connect(host='localhost', port=None, method='mqtt'):
|
|
22
|
+
'''Connect a client to a running measurement server.
|
|
23
|
+
|
|
24
|
+
'method' is the preferred connection, either 'mqtt' (default), 'webapi' or 'modbus'.
|
|
25
|
+
|
|
26
|
+
returns an `Instrument` if connected successfully.
|
|
27
|
+
'''
|
|
28
|
+
from .instrument import Instrument
|
|
29
|
+
|
|
30
|
+
if method.lower() == 'mqtt':
|
|
31
|
+
from .clients.mqtt import MqttClient as _client
|
|
32
|
+
elif method.lower() == 'webapi':
|
|
33
|
+
from .clients.ioniclient import IoniClient as _client
|
|
34
|
+
elif method.lower() == 'modbus':
|
|
35
|
+
from .modbus import IoniconModbus as _client
|
|
36
|
+
else:
|
|
37
|
+
raise NotImplementedError(str(method))
|
|
38
|
+
|
|
39
|
+
backend = _client(host, port) if port is not None else _client(host)
|
|
40
|
+
|
|
41
|
+
return Instrument(backend)
|
|
42
|
+
|
|
@@ -123,6 +123,7 @@ class IoniConnect(IoniClientBase):
|
|
|
123
123
|
'high': lambda p: p.borders[1],
|
|
124
124
|
'shift': attrgetter('shift'),
|
|
125
125
|
'multiplier': attrgetter('multiplier'),
|
|
126
|
+
'resolution': attrgetter('resolution'),
|
|
126
127
|
}
|
|
127
128
|
# normalize the input argument and create a hashable set:
|
|
128
129
|
updates = dict()
|
|
@@ -326,12 +326,14 @@ def follow_state(client, self, msg):
|
|
|
326
326
|
# replace the current state with the new element:
|
|
327
327
|
self._server_state.append(state)
|
|
328
328
|
meas_running = (state == "ACQ_Aquire") # yes, there's a typo, plz keep it :)
|
|
329
|
-
just_started = (meas_running and not msg.retain)
|
|
330
329
|
if meas_running:
|
|
331
330
|
# signal the relevant thread(s) that we need an update:
|
|
332
331
|
self._calcconzinfo.append(_NOT_INIT)
|
|
333
|
-
if
|
|
334
|
-
#
|
|
332
|
+
if not meas_running:
|
|
333
|
+
# Note: the user-interface in `.current_sourcefile` checks for the
|
|
334
|
+
# above _server_state and expects an initialized filename if and
|
|
335
|
+
# only if the server is running! Therefore, we can safely
|
|
336
|
+
# invalidate the source-file until we get a new one:
|
|
335
337
|
self._sf_filename.append(_NOT_INIT)
|
|
336
338
|
|
|
337
339
|
follow_state.topics = ["DataCollection/Act/ACQ_SRV_CurrentState"]
|
|
@@ -359,7 +361,8 @@ def follow_act_set_values(client, self, msg):
|
|
|
359
361
|
server, kind, parID = msg.topic.split('/')
|
|
360
362
|
if server == "DataCollection":
|
|
361
363
|
# Note: this topic doesn't strictly follow the convention and is handled separately
|
|
362
|
-
|
|
364
|
+
if kind != "Set":
|
|
365
|
+
return
|
|
363
366
|
|
|
364
367
|
if server == "Sequencer":
|
|
365
368
|
# Note: this is a separate program and will be ignored (has its own AUTO_-numbers et.c.)
|
|
@@ -730,6 +733,7 @@ class MqttClient(MqttClientBase):
|
|
|
730
733
|
* Elements will be buffered up to a maximum of `buffer_size` cycles (default: 300).
|
|
731
734
|
* Cycles recorded prior to calling `next()` on the iterator may be missed,
|
|
732
735
|
so ideally this should be set up before any measurement is running.
|
|
736
|
+
* Once the measurement stops, this iterator will raise `StopIteration`.
|
|
733
737
|
* [Important]: When the buffer runs full, a `queue.Full` exception will be raised!
|
|
734
738
|
Therefore, the caller should consume the iterator as soon as possible while the
|
|
735
739
|
measurement is running.
|
|
@@ -740,7 +744,10 @@ class MqttClient(MqttClientBase):
|
|
|
740
744
|
|
|
741
745
|
def callback(client, self, msg):
|
|
742
746
|
try:
|
|
743
|
-
|
|
747
|
+
_fc = _parse_fullcycle(msg.payload, need_add_data=True)
|
|
748
|
+
# IoniTOF cycle-indexing starts at 1, while 0 marks idle state:
|
|
749
|
+
if _fc.timecycle.abs_cycle > 0:
|
|
750
|
+
q.put_nowait(_fc)
|
|
744
751
|
log.debug(f"received fullcycle, buffer at ({q.qsize()}/{q.maxsize})")
|
|
745
752
|
except queue.Full:
|
|
746
753
|
# DO NOT FAIL INSIDE THE CALLBACK!
|
|
@@ -764,7 +771,20 @@ class MqttClient(MqttClientBase):
|
|
|
764
771
|
# if block is true and timeout is None, [the q.get()] operation goes into an
|
|
765
772
|
# uninterruptible wait on an underlying lock. This means that no exceptions
|
|
766
773
|
# can occur, and in particular a SIGINT will not trigger a KeyboardInterrupt!
|
|
767
|
-
|
|
774
|
+
if timeout_s is None and not self.is_running:
|
|
775
|
+
log.warn(f"waiting indefinitely for measurement to run...")
|
|
776
|
+
|
|
777
|
+
yield q.get(block=True, timeout=timeout_s)
|
|
778
|
+
|
|
779
|
+
# make double sure that there's more to come..
|
|
780
|
+
_started_at = time.monotonic()
|
|
781
|
+
while timeout_s is None or time.monotonic() < _started_at + timeout_s:
|
|
782
|
+
if self.is_running:
|
|
783
|
+
break
|
|
784
|
+
|
|
785
|
+
time.sleep(10e-3)
|
|
786
|
+
else:
|
|
787
|
+
raise TimeoutError(f"[{self}] received specdata, but measurement won't start");
|
|
768
788
|
|
|
769
789
|
while self.is_running or not q.empty():
|
|
770
790
|
if q.full():
|
|
@@ -782,7 +802,7 @@ class MqttClient(MqttClientBase):
|
|
|
782
802
|
|
|
783
803
|
except queue.Empty:
|
|
784
804
|
assert timeout_s is not None, "this should never happen"
|
|
785
|
-
raise TimeoutError("no measurement running after {timeout_s} seconds")
|
|
805
|
+
raise TimeoutError(f"no measurement running after {timeout_s} seconds")
|
|
786
806
|
|
|
787
807
|
finally:
|
|
788
808
|
# ...also, when using more than one iterator, the first to finish will
|
|
@@ -38,9 +38,9 @@ ID Name DataType Access ServerName LVWriteQueueName SharedVariable PrettyName Un
|
|
|
38
38
|
36 DPS_IhcOnOff BOOL RW PTR PTR_Write Ihc On/Off
|
|
39
39
|
37 DPS_Pdrift_Ctrl_OnOff RW PTR PTR_Write p-drift controlled
|
|
40
40
|
38 DPS_Pdrift_Ctrl_Val RW PTR PTR_Write p-drift ctrl Value
|
|
41
|
-
39 SourceValve
|
|
42
|
-
40 PrimionIdx
|
|
43
|
-
41
|
|
41
|
+
39 SourceValve DBL RW PTR PTR_Write Source Valve
|
|
42
|
+
40 PrimionIdx I32 RW PTR PTR_Write Prim Ion Idx
|
|
43
|
+
41 E_N DBL R E/N Td
|
|
44
44
|
42 FC_H2O DBL R PTR PTR_Write FC H2O sccm
|
|
45
45
|
43 SmartGAS1_Conc DBL R PTR Conz1 SG
|
|
46
46
|
44 SmartGAS2_Conc DBL R PTR Conz2 SG
|
|
@@ -66,16 +66,16 @@ ID Name DataType Access ServerName LVWriteQueueName SharedVariable PrettyName Un
|
|
|
66
66
|
64 Spare_64
|
|
67
67
|
65 Spare_65
|
|
68
68
|
66 Spare_66
|
|
69
|
-
67
|
|
70
|
-
68
|
|
71
|
-
69
|
|
69
|
+
67 MPV_Dir_1 I32 PTR PTR_Write MPV Dir
|
|
70
|
+
68 MPV_Dir_2 I32 PTR PTR_Write MPV Dir2
|
|
71
|
+
69 MPV_Dir_3 I32 PTR PTR_Write MPV Dir3
|
|
72
72
|
70 MPV_1 PTR PTR_Write MPValve1
|
|
73
73
|
71 MPV_2 PTR PTR_Write MPValve2
|
|
74
74
|
72 MPV_3 PTR PTR_Write MPValve3
|
|
75
75
|
73 DPS_Uf DBL RW PTR PTR_Write Uf V
|
|
76
76
|
74 DPS_U1 DBL RW PTR PTR_Write UF1 V
|
|
77
77
|
75 DPS_U2 DBL RW PTR PTR_Write UF2 V
|
|
78
|
-
76
|
|
78
|
+
76 DPS_IhcOnOff_Neg BOOL RW PTR PTR_Write Ihc On/Off
|
|
79
79
|
77 Spare_77
|
|
80
80
|
78 Spare_78
|
|
81
81
|
79 Spare_79
|
|
@@ -323,7 +323,7 @@ ID Name DataType Access ServerName LVWriteQueueName SharedVariable PrettyName Un
|
|
|
323
323
|
321 GC2_ConstantTemperature DBL RW PTR GC2 Temp Set
|
|
324
324
|
322 GC2_FC DBL RW PTR FC GC
|
|
325
325
|
323 GC2_FC_Makeup DBL RW PTR FC FastGC Makeup
|
|
326
|
-
324
|
|
326
|
+
324 GC2_FC_Ri DBL RW PTR FC FastGC Ri
|
|
327
327
|
325 Spare325
|
|
328
328
|
326 Spare326
|
|
329
329
|
327 Spare327
|
|
@@ -2,7 +2,12 @@ import os.path
|
|
|
2
2
|
import time
|
|
3
3
|
from abc import abstractmethod, ABC
|
|
4
4
|
|
|
5
|
-
from .measurement import
|
|
5
|
+
from .measurement import (
|
|
6
|
+
RunningMeasurement,
|
|
7
|
+
FinishedMeasurement,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = ['Instrument']
|
|
6
11
|
|
|
7
12
|
|
|
8
13
|
class Instrument(ABC):
|
|
@@ -24,25 +29,26 @@ class Instrument(ABC):
|
|
|
24
29
|
self.__class__ = newstate
|
|
25
30
|
|
|
26
31
|
def __new__(cls, backend):
|
|
27
|
-
#
|
|
32
|
+
# Note (reminder): If __new__() does not return an instance of cls,
|
|
33
|
+
# then the new instance’s __init__() method will *not* be invoked!
|
|
34
|
+
#
|
|
35
|
+
# This aside, we override the __new__ method to make this class a
|
|
36
|
+
# singleton that reflects the PTR-instrument state and dispatches
|
|
37
|
+
# to one of its subclass implementations.
|
|
28
38
|
if cls._Instrument__instance is not None:
|
|
29
|
-
# quick reminder: If __new__() does not return an instance of cls, then the
|
|
30
|
-
# new instance’s __init__() method will *not* be invoked:
|
|
31
39
|
return cls._Instrument__instance
|
|
32
40
|
|
|
33
|
-
# ..that is synchronized with the PTR-instrument state:
|
|
34
41
|
if backend.is_running:
|
|
35
|
-
|
|
42
|
+
inst = object.__new__(_RunningInstrument)
|
|
36
43
|
else:
|
|
37
|
-
|
|
44
|
+
inst = object.__new__(_IdleInstrument)
|
|
38
45
|
|
|
39
|
-
inst = object.__new__(cls)
|
|
40
46
|
Instrument._Instrument__instance = inst
|
|
41
47
|
|
|
42
48
|
return inst
|
|
43
49
|
|
|
44
50
|
def __init__(self, backend):
|
|
45
|
-
#
|
|
51
|
+
# Note: this will be called *once* per Python process!
|
|
46
52
|
self.backend = backend
|
|
47
53
|
|
|
48
54
|
@property
|
|
@@ -53,15 +59,17 @@ class Instrument(ABC):
|
|
|
53
59
|
|
|
54
60
|
def get(self, varname):
|
|
55
61
|
"""Get the current value of a setting."""
|
|
56
|
-
# TODO :: this is not an interface implementation
|
|
62
|
+
# TODO :: this is not an interface implementation...
|
|
57
63
|
raw = self.backend.get(varname)
|
|
64
|
+
|
|
65
|
+
from .clients.mqtt import MqttClient
|
|
58
66
|
if not isinstance(self.backend, MqttClient):
|
|
59
67
|
import json
|
|
60
68
|
jobj = json.loads(raw)
|
|
61
69
|
|
|
62
70
|
return jobj[0]['Act']['Real']
|
|
63
71
|
|
|
64
|
-
## how it should be:
|
|
72
|
+
## ...how it should be: just:
|
|
65
73
|
return raw
|
|
66
74
|
|
|
67
75
|
def set(self, varname, value, unit='-'):
|
|
@@ -79,7 +87,7 @@ class Instrument(ABC):
|
|
|
79
87
|
raise RuntimeError("can't stop %s" % self.__class__)
|
|
80
88
|
|
|
81
89
|
|
|
82
|
-
class
|
|
90
|
+
class _IdleInstrument(Instrument):
|
|
83
91
|
|
|
84
92
|
def start_measurement(self, filename=''):
|
|
85
93
|
dirname = os.path.dirname(filename)
|
|
@@ -108,12 +116,12 @@ class IdleInstrument(Instrument):
|
|
|
108
116
|
return RunningMeasurement(self)
|
|
109
117
|
|
|
110
118
|
|
|
111
|
-
class
|
|
119
|
+
class _RunningInstrument(Instrument):
|
|
112
120
|
|
|
113
121
|
def stop_measurement(self):
|
|
114
122
|
self.backend.stop_measurement()
|
|
115
123
|
self._new_state(IdleInstrument)
|
|
116
124
|
|
|
117
125
|
# TODO :: this catches only one sourcefile.. it'll do for simple cases:
|
|
118
|
-
return FinishedMeasurement(_current_sourcefile)
|
|
126
|
+
return FinishedMeasurement(self._current_sourcefile)
|
|
119
127
|
|
|
@@ -26,6 +26,10 @@ class Measurement(ABC):
|
|
|
26
26
|
# Note: we get ourselves a nifty little state-machine :)
|
|
27
27
|
self.__class__ = newstate
|
|
28
28
|
|
|
29
|
+
@property
|
|
30
|
+
def is_running(self):
|
|
31
|
+
return self.__class__ == RunningMeasurement
|
|
32
|
+
|
|
29
33
|
def start(self, filename=''):
|
|
30
34
|
"""Start a measurement on the PTR server.
|
|
31
35
|
|
|
@@ -48,10 +52,6 @@ class Measurement(ABC):
|
|
|
48
52
|
"""Stop the current measurement on the PTR server."""
|
|
49
53
|
raise RuntimeError("can't stop %s" % self.__class__)
|
|
50
54
|
|
|
51
|
-
@abstractmethod
|
|
52
|
-
def __len__(self):
|
|
53
|
-
pass
|
|
54
|
-
|
|
55
55
|
|
|
56
56
|
class PreparingMeasurement(Measurement):
|
|
57
57
|
|
|
@@ -94,8 +94,17 @@ class PreparingMeasurement(Measurement):
|
|
|
94
94
|
self.ptr.start_measurement(filename)
|
|
95
95
|
self._new_state(RunningMeasurement)
|
|
96
96
|
|
|
97
|
-
def
|
|
98
|
-
|
|
97
|
+
def __iter__(self):
|
|
98
|
+
from .clients.mqtt import MqttClient
|
|
99
|
+
if not isinstance(self.ptr.backend, MqttClient):
|
|
100
|
+
raise NotImplementedError("iteration only provided w/ backend 'mqtt'")
|
|
101
|
+
|
|
102
|
+
print("warning: the measurement needs to be started externally")
|
|
103
|
+
while not self.ptr.backend.is_running:
|
|
104
|
+
time.sleep(50e-3)
|
|
105
|
+
|
|
106
|
+
self._new_state(RunningMeasurement)
|
|
107
|
+
yield from iter(self)
|
|
99
108
|
|
|
100
109
|
|
|
101
110
|
class RunningMeasurement(Measurement):
|
|
@@ -107,51 +116,95 @@ class RunningMeasurement(Measurement):
|
|
|
107
116
|
self.ptr.stop_measurement()
|
|
108
117
|
self._new_state(FinishedMeasurement)
|
|
109
118
|
|
|
110
|
-
def
|
|
111
|
-
|
|
119
|
+
def __iter__(self):
|
|
120
|
+
from .clients.mqtt import MqttClient
|
|
121
|
+
if not isinstance(self.ptr.backend, MqttClient):
|
|
122
|
+
raise NotImplementedError("iteration only provided w/ backend 'mqtt'")
|
|
123
|
+
|
|
124
|
+
if not self.ptr.backend.is_connected:
|
|
125
|
+
raise Exception("no connection to instrument")
|
|
126
|
+
|
|
127
|
+
timeout_s = 15
|
|
128
|
+
ssd_s = 1e-3 * float(self.ptr.get('ACQ_SRV_SpecTime_ms'))
|
|
129
|
+
last_rel_cycle = -1
|
|
130
|
+
sourcefile = ''
|
|
131
|
+
for specdata in self.ptr.backend.iter_specdata(timeout_s=timeout_s+ssd_s, buffer_size=300):
|
|
132
|
+
if last_rel_cycle == -1 or specdata.timecycle.rel_cycle < last_rel_cycle:
|
|
133
|
+
# the source-file has been switched, so wait for the new path:
|
|
134
|
+
started_at = time.monotonic()
|
|
135
|
+
while time.monotonic() < started_at + timeout_s:
|
|
136
|
+
candidate = self.ptr.backend.current_sourcefile
|
|
137
|
+
if candidate and candidate != sourcefile:
|
|
138
|
+
sourcefile = candidate
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
time.sleep(10e-3)
|
|
142
|
+
else:
|
|
143
|
+
raise TimeoutError(f"no new sourcefile after ({timeout_s = })")
|
|
144
|
+
last_rel_cycle = specdata.timecycle.rel_cycle
|
|
145
|
+
|
|
146
|
+
yield sourcefile, specdata
|
|
147
|
+
|
|
148
|
+
if not self.ptr.backend.is_running:
|
|
149
|
+
self._new_state(FinishedMeasurement)
|
|
150
|
+
## TODO :: das hier braucht noch seine .sourcefiles !!
|
|
151
|
+
self.sourcefiles = []
|
|
112
152
|
|
|
113
153
|
|
|
114
154
|
class FinishedMeasurement(Measurement):
|
|
115
155
|
|
|
116
156
|
@classmethod
|
|
117
|
-
def _check(cls,
|
|
157
|
+
def _check(cls, _readers):
|
|
118
158
|
_assumptions = ("incompatible files! "
|
|
119
|
-
"
|
|
159
|
+
"_readers must have the same number-of-timebins and "
|
|
120
160
|
"the same instrument-type to be collected as a batch")
|
|
121
161
|
|
|
122
|
-
assert 1 == len(set(sf.inst_type for sf in
|
|
123
|
-
assert 1 == len(set(sf.number_of_timebins for sf in
|
|
162
|
+
assert 1 == len(set(sf.inst_type for sf in _readers)), _assumptions
|
|
163
|
+
assert 1 == len(set(sf.number_of_timebins for sf in _readers)), _assumptions
|
|
124
164
|
|
|
125
165
|
@property
|
|
126
166
|
def number_of_timebins(self):
|
|
127
|
-
return next(iter(self.
|
|
167
|
+
return next(iter(self._readers)).number_of_timebins
|
|
128
168
|
|
|
129
169
|
@property
|
|
130
170
|
def poisson_deadtime_ns(self):
|
|
131
|
-
return next(iter(self.
|
|
171
|
+
return next(iter(self._readers)).poisson_deadtime_ns
|
|
132
172
|
|
|
133
173
|
@property
|
|
134
174
|
def pulsing_period_ns(self):
|
|
135
|
-
return next(iter(self.
|
|
175
|
+
return next(iter(self._readers)).pulsing_period_ns
|
|
136
176
|
|
|
137
177
|
@property
|
|
138
178
|
def single_spec_duration_ms(self):
|
|
139
|
-
return next(iter(self.
|
|
179
|
+
return next(iter(self._readers)).single_spec_duration_ms
|
|
140
180
|
|
|
141
181
|
@property
|
|
142
182
|
def start_delay_ns(self):
|
|
143
|
-
return next(iter(self.
|
|
183
|
+
return next(iter(self._readers)).start_delay_ns
|
|
144
184
|
|
|
145
185
|
@property
|
|
146
186
|
def timebin_width_ps(self):
|
|
147
|
-
return next(iter(self.
|
|
187
|
+
return next(iter(self._readers)).timebin_width_ps
|
|
188
|
+
|
|
189
|
+
def __new__(cls, *args, **kwargs):
|
|
190
|
+
# TODO :: das hier passt mal **ueberhaupt gar nicht** in das "state-machine"-
|
|
191
|
+
## schema hinein!! Wie soll man das init-ialisieren, wenn bloss _new_state
|
|
192
|
+
## ge-called wird ???!?!?!?!?
|
|
193
|
+
|
|
194
|
+
# quick reminder: If __new__() does not return an instance of cls, then the
|
|
195
|
+
# new instance’s __init__() method will *not* be invoked:
|
|
196
|
+
print(*args, **kwargs)
|
|
197
|
+
|
|
198
|
+
inst = object.__new__(cls)
|
|
199
|
+
|
|
200
|
+
return inst
|
|
148
201
|
|
|
149
202
|
def __init__(self, *filenames, _reader=IoniTOFReader):
|
|
150
203
|
if not len(filenames):
|
|
151
204
|
raise ValueError("no filename given")
|
|
152
205
|
|
|
153
|
-
self.
|
|
154
|
-
self._check(self.
|
|
206
|
+
self._readers = sorted((_reader(f) for f in filenames), key=attrgetter('time_of_file'))
|
|
207
|
+
self._check(self._readers)
|
|
155
208
|
|
|
156
209
|
def read_traces(self, kind='conc', index='abs_cycle', force_original=False):
|
|
157
210
|
"""Return the timeseries ("traces") of all masses, compounds and settings.
|
|
@@ -163,11 +216,10 @@ class FinishedMeasurement(Measurement):
|
|
|
163
216
|
'abs_time' or 'rel_time'.
|
|
164
217
|
|
|
165
218
|
"""
|
|
166
|
-
return pd.concat(sf.read_all(kind, index, force_original) for sf in self.
|
|
219
|
+
return pd.concat(sf.read_all(kind, index, force_original) for sf in self._readers)
|
|
167
220
|
|
|
168
221
|
def __iter__(self):
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return len(self.sourcefiles)
|
|
222
|
+
for reader in self._readers:
|
|
223
|
+
for specdata in reader.iter_specdata():
|
|
224
|
+
yield reader.filename, specdata
|
|
173
225
|
|
pytrms-0.9.3/pytrms/__init__.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
_version = '0.9.3'
|
|
2
|
-
|
|
3
|
-
__all__ = ['load', 'connect']
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def load(path):
|
|
7
|
-
'''Open a datafile for post-analysis or batch processing.
|
|
8
|
-
|
|
9
|
-
`path` may be a glob-expression to collect a whole batch.
|
|
10
|
-
|
|
11
|
-
returns a `Measurement` instance.
|
|
12
|
-
'''
|
|
13
|
-
import glob
|
|
14
|
-
from .measurement import FinishedMeasurement
|
|
15
|
-
|
|
16
|
-
files = glob.glob(path)
|
|
17
|
-
|
|
18
|
-
return FinishedMeasurement(*files)
|
|
19
|
-
|
|
20
|
-
def connect(host=None, method='webapi'):
|
|
21
|
-
'''Connect a client to a running measurement server.
|
|
22
|
-
|
|
23
|
-
'method' is the preferred connection, either 'webapi' (default) or 'modbus'.
|
|
24
|
-
|
|
25
|
-
returns an `Instrument` if connected successfully.
|
|
26
|
-
'''
|
|
27
|
-
from .instrument import Instrument
|
|
28
|
-
|
|
29
|
-
if method.lower() == 'webapi':
|
|
30
|
-
from .clients.ioniclient import IoniClient
|
|
31
|
-
return IoniClient(host)
|
|
32
|
-
|
|
33
|
-
if method.lower() == 'modbus':
|
|
34
|
-
from .modbus import IoniconModbus
|
|
35
|
-
return IoniconModbus(host)
|
|
36
|
-
|
|
37
|
-
raise NotImplementedError(str(method))
|
|
38
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|