pytrms 0.9.2__py3-none-any.whl → 0.9.5__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.
- pytrms/__init__.py +42 -38
- pytrms/_base/__init__.py +24 -24
- pytrms/_base/ioniclient.py +32 -32
- pytrms/_base/mqttclient.py +119 -119
- pytrms/_version.py +26 -26
- pytrms/clients/__init__.py +33 -33
- pytrms/clients/db_api.py +200 -183
- pytrms/clients/ioniclient.py +87 -87
- pytrms/clients/modbus.py +532 -528
- pytrms/clients/mqtt.py +820 -797
- pytrms/clients/ssevent.py +82 -82
- pytrms/compose/__init__.py +2 -2
- pytrms/compose/composition.py +302 -302
- pytrms/data/IoniTofPrefs.ini +112 -112
- pytrms/data/ParaIDs.csv +731 -731
- pytrms/helpers.py +126 -120
- pytrms/instrument.py +124 -119
- pytrms/measurement.py +225 -173
- pytrms/peaktable.py +499 -501
- pytrms/plotting/__init__.py +4 -4
- pytrms/plotting/plotting.py +27 -27
- pytrms/readers/__init__.py +4 -4
- pytrms/readers/ionitof_reader.py +472 -472
- {pytrms-0.9.2.dist-info → pytrms-0.9.5.dist-info}/LICENSE +339 -339
- {pytrms-0.9.2.dist-info → pytrms-0.9.5.dist-info}/METADATA +3 -2
- pytrms-0.9.5.dist-info/RECORD +27 -0
- {pytrms-0.9.2.dist-info → pytrms-0.9.5.dist-info}/WHEEL +1 -1
- pytrms-0.9.2.dist-info/RECORD +0 -27
pytrms/clients/db_api.py
CHANGED
|
@@ -1,183 +1,200 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import json
|
|
3
|
-
|
|
4
|
-
import requests
|
|
5
|
-
|
|
6
|
-
from . import _logging
|
|
7
|
-
from .ssevent import SSEventListener
|
|
8
|
-
from .._base import IoniClientBase
|
|
9
|
-
|
|
10
|
-
log = _logging.getLogger(__name__)
|
|
11
|
-
|
|
12
|
-
# TODO :: sowas waer auch ganz cool: die DBAPI bietes sich geradezu an,
|
|
13
|
-
# da mehr object-oriented zu arbeiten:
|
|
14
|
-
# currentVariable = get_component(currentComponentNameAction, ds)
|
|
15
|
-
# currentVariable.save_value({'value': currentValue})
|
|
16
|
-
|
|
17
|
-
class IoniConnect(IoniClientBase):
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def is_connected(self):
|
|
21
|
-
'''Returns `True` if connection to IoniTOF could be established.'''
|
|
22
|
-
try:
|
|
23
|
-
self.get("/api/status")
|
|
24
|
-
return True
|
|
25
|
-
except:
|
|
26
|
-
return False
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
def is_running(self):
|
|
30
|
-
'''Returns `True` if IoniTOF is currently acquiring data.'''
|
|
31
|
-
raise NotImplementedError("is_running")
|
|
32
|
-
|
|
33
|
-
def connect(self, timeout_s):
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
def disconnect(self):
|
|
37
|
-
pass
|
|
38
|
-
|
|
39
|
-
def __init__(self, host='127.0.0.1', port=5066, session=None):
|
|
40
|
-
super().__init__(host, port)
|
|
41
|
-
self.url = f"http://{self.host}:{self.port}"
|
|
42
|
-
if session is None:
|
|
43
|
-
session = requests.sessions.Session()
|
|
44
|
-
self.session = session
|
|
45
|
-
# ??
|
|
46
|
-
self.current_avg_endpoint = None
|
|
47
|
-
self.comp_dict = dict()
|
|
48
|
-
|
|
49
|
-
def get(self, endpoint, **kwargs):
|
|
50
|
-
return self._get_object(endpoint, **kwargs).json()
|
|
51
|
-
|
|
52
|
-
def post(self, endpoint, data, **kwargs):
|
|
53
|
-
return self._create_object(endpoint, data, 'post', **kwargs).headers.get('Location')
|
|
54
|
-
|
|
55
|
-
def put(self, endpoint, data, **kwargs):
|
|
56
|
-
return self._create_object(endpoint, data, 'put', **kwargs).headers.get('Location')
|
|
57
|
-
|
|
58
|
-
def upload(self, endpoint, filename):
|
|
59
|
-
if not endpoint.startswith('/'):
|
|
60
|
-
endpoint = '/' + endpoint
|
|
61
|
-
with open(filename) as f:
|
|
62
|
-
# Note (important!): this is a "form-data" entry, where the server
|
|
63
|
-
# expects the "name" to be 'file' and rejects it otherwise:
|
|
64
|
-
name = 'file'
|
|
65
|
-
r = self.session.post(self.url + endpoint, files=[(name, (filename, f, ''))])
|
|
66
|
-
r.raise_for_status()
|
|
67
|
-
|
|
68
|
-
return r
|
|
69
|
-
|
|
70
|
-
def _get_object(self, endpoint, **kwargs):
|
|
71
|
-
if not endpoint.startswith('/'):
|
|
72
|
-
endpoint = '/' + endpoint
|
|
73
|
-
if 'headers' not in kwargs:
|
|
74
|
-
kwargs['headers'] = {'content-type': 'application/hal+json'}
|
|
75
|
-
elif 'content-type' not in (k.lower() for k in kwargs['headers']):
|
|
76
|
-
kwargs['headers'].update({'content-type': 'application/hal+json'})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
'
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from . import _logging
|
|
7
|
+
from .ssevent import SSEventListener
|
|
8
|
+
from .._base import IoniClientBase
|
|
9
|
+
|
|
10
|
+
log = _logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# TODO :: sowas waer auch ganz cool: die DBAPI bietes sich geradezu an,
|
|
13
|
+
# da mehr object-oriented zu arbeiten:
|
|
14
|
+
# currentVariable = get_component(currentComponentNameAction, ds)
|
|
15
|
+
# currentVariable.save_value({'value': currentValue})
|
|
16
|
+
|
|
17
|
+
class IoniConnect(IoniClientBase):
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def is_connected(self):
|
|
21
|
+
'''Returns `True` if connection to IoniTOF could be established.'''
|
|
22
|
+
try:
|
|
23
|
+
self.get("/api/status")
|
|
24
|
+
return True
|
|
25
|
+
except:
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def is_running(self):
|
|
30
|
+
'''Returns `True` if IoniTOF is currently acquiring data.'''
|
|
31
|
+
raise NotImplementedError("is_running")
|
|
32
|
+
|
|
33
|
+
def connect(self, timeout_s):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def disconnect(self):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def __init__(self, host='127.0.0.1', port=5066, session=None):
|
|
40
|
+
super().__init__(host, port)
|
|
41
|
+
self.url = f"http://{self.host}:{self.port}"
|
|
42
|
+
if session is None:
|
|
43
|
+
session = requests.sessions.Session()
|
|
44
|
+
self.session = session
|
|
45
|
+
# ??
|
|
46
|
+
self.current_avg_endpoint = None
|
|
47
|
+
self.comp_dict = dict()
|
|
48
|
+
|
|
49
|
+
def get(self, endpoint, **kwargs):
|
|
50
|
+
return self._get_object(endpoint, **kwargs).json()
|
|
51
|
+
|
|
52
|
+
def post(self, endpoint, data, **kwargs):
|
|
53
|
+
return self._create_object(endpoint, data, 'post', **kwargs).headers.get('Location')
|
|
54
|
+
|
|
55
|
+
def put(self, endpoint, data, **kwargs):
|
|
56
|
+
return self._create_object(endpoint, data, 'put', **kwargs).headers.get('Location')
|
|
57
|
+
|
|
58
|
+
def upload(self, endpoint, filename):
|
|
59
|
+
if not endpoint.startswith('/'):
|
|
60
|
+
endpoint = '/' + endpoint
|
|
61
|
+
with open(filename) as f:
|
|
62
|
+
# Note (important!): this is a "form-data" entry, where the server
|
|
63
|
+
# expects the "name" to be 'file' and rejects it otherwise:
|
|
64
|
+
name = 'file'
|
|
65
|
+
r = self.session.post(self.url + endpoint, files=[(name, (filename, f, ''))])
|
|
66
|
+
r.raise_for_status()
|
|
67
|
+
|
|
68
|
+
return r
|
|
69
|
+
|
|
70
|
+
def _get_object(self, endpoint, **kwargs):
|
|
71
|
+
if not endpoint.startswith('/'):
|
|
72
|
+
endpoint = '/' + endpoint
|
|
73
|
+
if 'headers' not in kwargs:
|
|
74
|
+
kwargs['headers'] = {'content-type': 'application/hal+json'}
|
|
75
|
+
elif 'content-type' not in (k.lower() for k in kwargs['headers']):
|
|
76
|
+
kwargs['headers'].update({'content-type': 'application/hal+json'})
|
|
77
|
+
if 'timeout' not in kwargs:
|
|
78
|
+
# https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
|
|
79
|
+
kwargs['timeout'] = (6.06, 27)
|
|
80
|
+
r = self.session.request('get', self.url + endpoint, **kwargs)
|
|
81
|
+
r.raise_for_status()
|
|
82
|
+
|
|
83
|
+
return r
|
|
84
|
+
|
|
85
|
+
def _create_object(self, endpoint, data, method='post', **kwargs):
|
|
86
|
+
if not endpoint.startswith('/'):
|
|
87
|
+
endpoint = '/' + endpoint
|
|
88
|
+
if not isinstance(data, str):
|
|
89
|
+
data = json.dumps(data, ensure_ascii=False) # default is `True`, escapes Umlaute!
|
|
90
|
+
if 'headers' not in kwargs:
|
|
91
|
+
kwargs['headers'] = {'content-type': 'application/hal+json'}
|
|
92
|
+
elif 'content-type' not in (k.lower() for k in kwargs['headers']):
|
|
93
|
+
kwargs['headers'].update({'content-type': 'application/hal+json'})
|
|
94
|
+
if 'timeout' not in kwargs:
|
|
95
|
+
# https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
|
|
96
|
+
kwargs['timeout'] = (6.06, 27)
|
|
97
|
+
r = self.session.request(method, self.url + endpoint, data=data, **kwargs)
|
|
98
|
+
if not r.ok:
|
|
99
|
+
log.error(f"POST {endpoint}\n{data}\n\nreturned [{r.status_code}]: {r.content}")
|
|
100
|
+
r.raise_for_status()
|
|
101
|
+
|
|
102
|
+
return r
|
|
103
|
+
|
|
104
|
+
def sync(self, peaktable):
|
|
105
|
+
"""Compare and upload any differences in `peaktable` to the database."""
|
|
106
|
+
from pytrms.peaktable import Peak, PeakTable
|
|
107
|
+
from operator import attrgetter
|
|
108
|
+
|
|
109
|
+
# Note: a `Peak` is a hashable object that serves as a key that
|
|
110
|
+
# distinguishes between peaks as defined by PyTRMS:
|
|
111
|
+
make_key = lambda peak: Peak(center=peak['center'], label=peak['name'], shift=peak['shift'])
|
|
112
|
+
|
|
113
|
+
if isinstance(peaktable, str):
|
|
114
|
+
log.info(f"loading peaktable '{peaktable}'...")
|
|
115
|
+
peaktable = PeakTable.from_file(peaktable)
|
|
116
|
+
|
|
117
|
+
# get the PyTRMS- and IoniConnect-peaks on the same page:
|
|
118
|
+
conv = {
|
|
119
|
+
'name': attrgetter('label'),
|
|
120
|
+
'center': attrgetter('center'),
|
|
121
|
+
'kRate': attrgetter('k_rate'),
|
|
122
|
+
'low': lambda p: p.borders[0],
|
|
123
|
+
'high': lambda p: p.borders[1],
|
|
124
|
+
'shift': attrgetter('shift'),
|
|
125
|
+
'multiplier': attrgetter('multiplier'),
|
|
126
|
+
}
|
|
127
|
+
# normalize the input argument and create a hashable set:
|
|
128
|
+
updates = dict()
|
|
129
|
+
for peak in peaktable:
|
|
130
|
+
payload = {k: conv[k](peak) for k in conv}
|
|
131
|
+
updates[make_key(payload)] = {'payload': payload}
|
|
132
|
+
|
|
133
|
+
log.info(f"fetching current peaktable from the server...")
|
|
134
|
+
# create a comparable collection of peaks already on the database by
|
|
135
|
+
# reducing the keys in the response to what we actually want to update:
|
|
136
|
+
db_peaks = {make_key(p): {
|
|
137
|
+
'payload': {k: p[k] for k in conv.keys()},
|
|
138
|
+
'self': p['_links']['self'],
|
|
139
|
+
'parent': p['_links'].get('parent'),
|
|
140
|
+
} for p in self.get('/api/peaks')['_embedded']['peaks']}
|
|
141
|
+
|
|
142
|
+
to_update = updates.keys() & db_peaks.keys()
|
|
143
|
+
to_upload = updates.keys() - db_peaks.keys()
|
|
144
|
+
updated = 0
|
|
145
|
+
for key in sorted(to_update):
|
|
146
|
+
# check if an existing peak needs an update
|
|
147
|
+
if db_peaks[key]['payload'] == updates[key]['payload']:
|
|
148
|
+
# nothing to do..
|
|
149
|
+
log.debug(f"up-to-date: {key}")
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
self.put(db_peaks[key]['self']['href'], updates[key]['payload'])
|
|
153
|
+
log.info(f"updated: {key}")
|
|
154
|
+
updated += 1
|
|
155
|
+
|
|
156
|
+
if len(to_upload):
|
|
157
|
+
# Note: POSTing the embedded-collection is *miles faster*
|
|
158
|
+
# than doing separate requests for each peak!
|
|
159
|
+
payload = {'_embedded': {'peaks': [updates[key]['payload'] for key in sorted(to_upload)]}}
|
|
160
|
+
self.post('/api/peaks', payload)
|
|
161
|
+
for key in sorted(to_upload):
|
|
162
|
+
log.info(f"added new: {key}")
|
|
163
|
+
|
|
164
|
+
if len(peaktable.fitted):
|
|
165
|
+
# Note: until now, we disregarded the peak-parent-relationship, so
|
|
166
|
+
# make another request to the updated peak-table from the server...
|
|
167
|
+
db_peaks = {make_key(p): {
|
|
168
|
+
'payload': {k: p[k] for k in conv.keys()},
|
|
169
|
+
'self': p['_links']['self'],
|
|
170
|
+
'parent': p['_links'].get('parent'),
|
|
171
|
+
} for p in self.get('/api/peaks')['_embedded']['peaks']}
|
|
172
|
+
|
|
173
|
+
for fitted in peaktable.fitted:
|
|
174
|
+
fitted_href = db_peaks[fitted]["self"]["href"]
|
|
175
|
+
parent_href = db_peaks[fitted.parent]["self"]["href"]
|
|
176
|
+
r = self.session.request('link', self.url + parent_href, headers={"location": fitted_href})
|
|
177
|
+
if not r.ok:
|
|
178
|
+
log.error(f"LINK {parent_href} to Location: {fitted_href} failed\n\n[{r.status_code}]: {r.content}")
|
|
179
|
+
r.raise_for_status()
|
|
180
|
+
log.debug(f"linked parent {parent_href} ~> {fitted_href}")
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
'added': len(to_upload),
|
|
184
|
+
'updated': updated,
|
|
185
|
+
'up-to-date': len(to_update) - updated,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
def iter_events(self, event_re=r".*"):
|
|
189
|
+
"""Follow the server-sent-events (SSE) on the DB-API.
|
|
190
|
+
|
|
191
|
+
`event_re` a regular expression to filter events (default: matches everything)
|
|
192
|
+
|
|
193
|
+
Note: This will block until a matching event is received.
|
|
194
|
+
Especially, it cannot be cancelled by KeyboardInterrupt (due to the `requests`
|
|
195
|
+
stream-implementation), unless the server sends a keep-alive at regular
|
|
196
|
+
intervals (as every well-behaved server should be doing)!
|
|
197
|
+
"""
|
|
198
|
+
yield from SSEventListener(event_re, host_url=self.url, endpoint="/api/events",
|
|
199
|
+
session=self.session)
|
|
200
|
+
|
pytrms/clients/ioniclient.py
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
#################################################
|
|
2
|
-
# #
|
|
3
|
-
# Ionicon WebAPI - Python client #
|
|
4
|
-
# #
|
|
5
|
-
# this module requires the 'requests' package #
|
|
6
|
-
# #
|
|
7
|
-
# >> pip install --user requests #
|
|
8
|
-
# #
|
|
9
|
-
#################################################
|
|
10
|
-
import ntpath
|
|
11
|
-
import time
|
|
12
|
-
|
|
13
|
-
import requests
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class IoniClient:
|
|
17
|
-
'''
|
|
18
|
-
Access the Ionicon WebAPI.
|
|
19
|
-
|
|
20
|
-
Usage:
|
|
21
|
-
> client = IoniClient()
|
|
22
|
-
> client.get('TPS_Pull_H')
|
|
23
|
-
{'TPS_Pull_H': 123.45, ... }
|
|
24
|
-
|
|
25
|
-
> client.set('TPS_Pull_H', 42)
|
|
26
|
-
{'TPS_Pull_H': 42.0, ... }
|
|
27
|
-
|
|
28
|
-
> client.start_measurement()
|
|
29
|
-
ACK
|
|
30
|
-
|
|
31
|
-
> client.host, client.port
|
|
32
|
-
('localhost', 8002)
|
|
33
|
-
|
|
34
|
-
'''
|
|
35
|
-
def __init__(self, host='localhost', port=8002):
|
|
36
|
-
self.host = host
|
|
37
|
-
self.port = port
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def baseurl(self):
|
|
41
|
-
return f'http://{self.host}:{self.port}/Ic_WebAPI'
|
|
42
|
-
|
|
43
|
-
def get(self, varname):
|
|
44
|
-
uri = self.baseurl + '/WebAPI_Get'
|
|
45
|
-
payload = {varname: '?'}
|
|
46
|
-
r = requests.get(uri, params=payload, timeout=10)
|
|
47
|
-
|
|
48
|
-
return r.text
|
|
49
|
-
|
|
50
|
-
def get_many(self, varnames):
|
|
51
|
-
uri = self.baseurl + '/WebAPI_Get'
|
|
52
|
-
payload = {varname: '?' for varname in varnames}
|
|
53
|
-
r = requests.get(uri, params=payload, timeout=10)
|
|
54
|
-
|
|
55
|
-
return r.text
|
|
56
|
-
|
|
57
|
-
def get_traces(self):
|
|
58
|
-
uri = self.baseurl + '/TRACES_WebAPI_Get' + '?'
|
|
59
|
-
r = requests.get(uri, timeout=10)
|
|
60
|
-
|
|
61
|
-
return r.text
|
|
62
|
-
|
|
63
|
-
def set(self, varname, value):
|
|
64
|
-
uri = self.baseurl + '/WebAPI_Set'
|
|
65
|
-
payload = {varname: value}
|
|
66
|
-
r = requests.post(uri, data=payload, timeout=10)
|
|
67
|
-
|
|
68
|
-
return r.text
|
|
69
|
-
|
|
70
|
-
def set_many(self, key_value_pairs):
|
|
71
|
-
uri = self.baseurl + '/WebAPI_Set'
|
|
72
|
-
payload = dict(key_value_pairs)
|
|
73
|
-
r = requests.post(uri, data=payload, timeout=10)
|
|
74
|
-
|
|
75
|
-
return r.text
|
|
76
|
-
|
|
77
|
-
def set_filename(self, filename):
|
|
78
|
-
return self.set('ACQ_SRV_SetFullStorageFile', ntpath.normpath(filename))
|
|
79
|
-
|
|
80
|
-
def start_measurement(self, filename=''):
|
|
81
|
-
if filename:
|
|
82
|
-
return self.set('ACQ_SRV_Start_Meas_Auto', ntpath.normpath(filename))
|
|
83
|
-
|
|
84
|
-
return self.set('ACQ_SRV_Start_Meas_Quick', 1)
|
|
85
|
-
|
|
86
|
-
def stop_measurement(self):
|
|
87
|
-
return self.set('ACQ_SRV_Stop_Meas', 1)
|
|
1
|
+
#################################################
|
|
2
|
+
# #
|
|
3
|
+
# Ionicon WebAPI - Python client #
|
|
4
|
+
# #
|
|
5
|
+
# this module requires the 'requests' package #
|
|
6
|
+
# #
|
|
7
|
+
# >> pip install --user requests #
|
|
8
|
+
# #
|
|
9
|
+
#################################################
|
|
10
|
+
import ntpath
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IoniClient:
|
|
17
|
+
'''
|
|
18
|
+
Access the Ionicon WebAPI.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
> client = IoniClient()
|
|
22
|
+
> client.get('TPS_Pull_H')
|
|
23
|
+
{'TPS_Pull_H': 123.45, ... }
|
|
24
|
+
|
|
25
|
+
> client.set('TPS_Pull_H', 42)
|
|
26
|
+
{'TPS_Pull_H': 42.0, ... }
|
|
27
|
+
|
|
28
|
+
> client.start_measurement()
|
|
29
|
+
ACK
|
|
30
|
+
|
|
31
|
+
> client.host, client.port
|
|
32
|
+
('localhost', 8002)
|
|
33
|
+
|
|
34
|
+
'''
|
|
35
|
+
def __init__(self, host='localhost', port=8002):
|
|
36
|
+
self.host = host
|
|
37
|
+
self.port = port
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def baseurl(self):
|
|
41
|
+
return f'http://{self.host}:{self.port}/Ic_WebAPI'
|
|
42
|
+
|
|
43
|
+
def get(self, varname):
|
|
44
|
+
uri = self.baseurl + '/WebAPI_Get'
|
|
45
|
+
payload = {varname: '?'}
|
|
46
|
+
r = requests.get(uri, params=payload, timeout=10)
|
|
47
|
+
|
|
48
|
+
return r.text
|
|
49
|
+
|
|
50
|
+
def get_many(self, varnames):
|
|
51
|
+
uri = self.baseurl + '/WebAPI_Get'
|
|
52
|
+
payload = {varname: '?' for varname in varnames}
|
|
53
|
+
r = requests.get(uri, params=payload, timeout=10)
|
|
54
|
+
|
|
55
|
+
return r.text
|
|
56
|
+
|
|
57
|
+
def get_traces(self):
|
|
58
|
+
uri = self.baseurl + '/TRACES_WebAPI_Get' + '?'
|
|
59
|
+
r = requests.get(uri, timeout=10)
|
|
60
|
+
|
|
61
|
+
return r.text
|
|
62
|
+
|
|
63
|
+
def set(self, varname, value):
|
|
64
|
+
uri = self.baseurl + '/WebAPI_Set'
|
|
65
|
+
payload = {varname: value}
|
|
66
|
+
r = requests.post(uri, data=payload, timeout=10)
|
|
67
|
+
|
|
68
|
+
return r.text
|
|
69
|
+
|
|
70
|
+
def set_many(self, key_value_pairs):
|
|
71
|
+
uri = self.baseurl + '/WebAPI_Set'
|
|
72
|
+
payload = dict(key_value_pairs)
|
|
73
|
+
r = requests.post(uri, data=payload, timeout=10)
|
|
74
|
+
|
|
75
|
+
return r.text
|
|
76
|
+
|
|
77
|
+
def set_filename(self, filename):
|
|
78
|
+
return self.set('ACQ_SRV_SetFullStorageFile', ntpath.normpath(filename))
|
|
79
|
+
|
|
80
|
+
def start_measurement(self, filename=''):
|
|
81
|
+
if filename:
|
|
82
|
+
return self.set('ACQ_SRV_Start_Meas_Auto', ntpath.normpath(filename))
|
|
83
|
+
|
|
84
|
+
return self.set('ACQ_SRV_Start_Meas_Quick', 1)
|
|
85
|
+
|
|
86
|
+
def stop_measurement(self):
|
|
87
|
+
return self.set('ACQ_SRV_Stop_Meas', 1)
|