pyForceDAQ 2.0.0__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.
- pyforcedaq/__init__.py +43 -0
- pyforcedaq/__main__.py +42 -0
- pyforcedaq/_lib/__init__.py +1 -0
- pyforcedaq/_lib/lsl.py +56 -0
- pyforcedaq/_lib/misc.py +126 -0
- pyforcedaq/_lib/polling_time_profile.py +52 -0
- pyforcedaq/_lib/process_priority_manager.py +148 -0
- pyforcedaq/_lib/timer.py +45 -0
- pyforcedaq/_lib/types.py +400 -0
- pyforcedaq/_lib/udp_connection.py +326 -0
- pyforcedaq/daq/__init__.py +19 -0
- pyforcedaq/daq/_daq_read_Analog_pydaqmx.py +114 -0
- pyforcedaq/daq/_daq_read_analog_nidaqmx.py +84 -0
- pyforcedaq/daq/_mock_sensor.py +80 -0
- pyforcedaq/daq/_pyATIDAQ.py +306 -0
- pyforcedaq/daq/config.py +13 -0
- pyforcedaq/extras/__init__.py +0 -0
- pyforcedaq/extras/convert.py +275 -0
- pyforcedaq/extras/expyriment_daq_control.py +246 -0
- pyforcedaq/extras/opensesame_daq_control.py +280 -0
- pyforcedaq/extras/read_force_data.py +89 -0
- pyforcedaq/extras/remote_control.py +93 -0
- pyforcedaq/force/__init__.py +13 -0
- pyforcedaq/force/_log.py +18 -0
- pyforcedaq/force/data_recorder.py +400 -0
- pyforcedaq/force/sensor.py +200 -0
- pyforcedaq/force/sensor_process.py +251 -0
- pyforcedaq/gui/__init__.py +6 -0
- pyforcedaq/gui/_gui_status.py +306 -0
- pyforcedaq/gui/_layout.py +104 -0
- pyforcedaq/gui/_level_indicator.py +59 -0
- pyforcedaq/gui/_pg_surface.py +100 -0
- pyforcedaq/gui/_plotter.py +234 -0
- pyforcedaq/gui/_run.py +522 -0
- pyforcedaq/gui/_scaling.py +71 -0
- pyforcedaq/gui/_settings.py +98 -0
- pyforcedaq/gui/forceDAQ_logo.png +0 -0
- pyforcedaq/gui/launcher.py +249 -0
- pyforcedaq-2.0.0.dist-info/METADATA +15 -0
- pyforcedaq-2.0.0.dist-info/RECORD +42 -0
- pyforcedaq-2.0.0.dist-info/WHEEL +4 -0
- pyforcedaq-2.0.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
""" A lan connect class using udp
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
__author__ = "Oliver Lindemann <oliver@expyriment.org>"
|
|
5
|
+
__version__ = "0.5"
|
|
6
|
+
|
|
7
|
+
import atexit
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import socket
|
|
11
|
+
from multiprocessing import Event, Process, Queue
|
|
12
|
+
from subprocess import check_output
|
|
13
|
+
|
|
14
|
+
from . import timer
|
|
15
|
+
from .polling_time_profile import PollingTimeProfile
|
|
16
|
+
from .process_priority_manager import get_priority
|
|
17
|
+
from .timer import Timer, app_clock, get_time_ms
|
|
18
|
+
from .types import UDPData
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_lan_ip():
|
|
22
|
+
if os.name == "nt":
|
|
23
|
+
# Windows
|
|
24
|
+
return socket.gethostbyname(socket.gethostname())
|
|
25
|
+
else:
|
|
26
|
+
# Linux and macOS
|
|
27
|
+
try:
|
|
28
|
+
# Try Linux command first
|
|
29
|
+
rtn = check_output(["hostname", "-I"]).decode().strip()
|
|
30
|
+
return rtn.split()[0] if rtn else None
|
|
31
|
+
except:
|
|
32
|
+
try:
|
|
33
|
+
# Fallback to macOS command
|
|
34
|
+
rtn = check_output(["ipconfig", "getifaddr", "en0"]).decode().strip()
|
|
35
|
+
return rtn if rtn else None
|
|
36
|
+
except:
|
|
37
|
+
# Fallback to socket method if both commands fail
|
|
38
|
+
return socket.gethostbyname(socket.gethostname())
|
|
39
|
+
|
|
40
|
+
class UDPConnection(object):
|
|
41
|
+
# DOC document the usage "connecting" "unconnecting"
|
|
42
|
+
COMMAND_CHAR = b"$"
|
|
43
|
+
CONNECT = COMMAND_CHAR + b"connect"
|
|
44
|
+
UNCONNECT = COMMAND_CHAR + b"unconnect"
|
|
45
|
+
COMMAND_REPLY = COMMAND_CHAR + b"ok"
|
|
46
|
+
PING = COMMAND_CHAR + b"ping"
|
|
47
|
+
|
|
48
|
+
MY_IP = get_lan_ip()
|
|
49
|
+
|
|
50
|
+
def __init__(self, udp_port=5005):
|
|
51
|
+
self.udp_port = udp_port
|
|
52
|
+
|
|
53
|
+
self._socket = socket.socket(socket.AF_INET, # Internet
|
|
54
|
+
socket.SOCK_DGRAM) # UDP
|
|
55
|
+
self._socket.bind((UDPConnection.MY_IP, self.udp_port))
|
|
56
|
+
self._socket.setblocking(False)
|
|
57
|
+
self.peer_ip = None
|
|
58
|
+
self.timer = Timer(sync_timer=app_clock) # own timer, because often
|
|
59
|
+
# used in own process
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def my_ip(self):
|
|
63
|
+
return UDPConnection.MY_IP
|
|
64
|
+
|
|
65
|
+
def __str__(self):
|
|
66
|
+
return "ip: {0} (port: {1}); peer: {2}".format(UDPConnection.MY_IP,
|
|
67
|
+
self.udp_port, self.peer_ip)
|
|
68
|
+
|
|
69
|
+
def receive(self, timeout):
|
|
70
|
+
"""checks for received data and returns it
|
|
71
|
+
|
|
72
|
+
In contrast to poll the function keep polling until timeout if no new
|
|
73
|
+
data are available.
|
|
74
|
+
|
|
75
|
+
timeout in seconds
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
t = get_time_ms()
|
|
80
|
+
timeout_ms = int(timeout*1000)
|
|
81
|
+
while True:
|
|
82
|
+
rtn = self.poll()
|
|
83
|
+
if rtn is not None:
|
|
84
|
+
#print("UDP receive: {0}".format(rtn))
|
|
85
|
+
return rtn
|
|
86
|
+
if (get_time_ms() - t) > timeout_ms:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def poll(self):
|
|
90
|
+
"""returns data (bytes) or None if no data found
|
|
91
|
+
process also commands
|
|
92
|
+
|
|
93
|
+
if send is unkown input is ignored
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
data, sender = self._socket.recvfrom(1024)
|
|
98
|
+
except:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
# process data
|
|
102
|
+
if data == UDPConnection.CONNECT:
|
|
103
|
+
#connection request
|
|
104
|
+
self.peer_ip = sender[0]
|
|
105
|
+
if not self.send(UDPConnection.COMMAND_REPLY):
|
|
106
|
+
self.peer_ip = None
|
|
107
|
+
elif sender[0] != self.peer_ip:
|
|
108
|
+
return None # ignore data
|
|
109
|
+
elif data == UDPConnection.PING:
|
|
110
|
+
self.send(UDPConnection.COMMAND_REPLY)
|
|
111
|
+
elif data == self.UNCONNECT:
|
|
112
|
+
self.unconnect_peer()
|
|
113
|
+
|
|
114
|
+
return data
|
|
115
|
+
|
|
116
|
+
def send(self, data, timeout=1.0):
|
|
117
|
+
"""returns if problems or not
|
|
118
|
+
timeout in seconds (default = 1.0)
|
|
119
|
+
return False if failed to send
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
timeout_ms = int(timeout*1000)
|
|
123
|
+
if self.peer_ip is None:
|
|
124
|
+
return False
|
|
125
|
+
start = get_time_ms()
|
|
126
|
+
if isinstance(data, str):
|
|
127
|
+
data = data.encode() # force to byte
|
|
128
|
+
|
|
129
|
+
while get_time_ms() - start < timeout_ms:
|
|
130
|
+
try:
|
|
131
|
+
self._socket.sendto(data, (self.peer_ip, self.udp_port))
|
|
132
|
+
#print("UDP send: {0}".format(data))
|
|
133
|
+
return True
|
|
134
|
+
except:
|
|
135
|
+
pass
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def connect_peer(self, peer_ip, timeout=1.0):
|
|
139
|
+
|
|
140
|
+
self.unconnect_peer()
|
|
141
|
+
self.peer_ip = peer_ip
|
|
142
|
+
if self.send(UDPConnection.CONNECT, timeout=timeout) and \
|
|
143
|
+
self.wait_input(UDPConnection.COMMAND_REPLY, duration=timeout):
|
|
144
|
+
return True
|
|
145
|
+
self.peer_ip = None
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
def wait_input(self, input_string, duration=1.0):
|
|
149
|
+
"""poll the connection and waits for a specific input"""
|
|
150
|
+
start = get_time_ms()
|
|
151
|
+
duration_ms = int(duration*1000)
|
|
152
|
+
while get_time_ms() - start < duration_ms:
|
|
153
|
+
in_ = self.poll()
|
|
154
|
+
if in_ == input_string:
|
|
155
|
+
return True
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
def unconnect_peer(self, timeout=1.0):
|
|
159
|
+
self.send(UDPConnection.UNCONNECT, timeout=timeout)
|
|
160
|
+
self.peer_ip = None
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def is_connected(self):
|
|
164
|
+
return self.peer_ip is not None
|
|
165
|
+
|
|
166
|
+
def ping(self, timeout=0.5):
|
|
167
|
+
"""returns boolean if succeeded and ping time in ms"""
|
|
168
|
+
|
|
169
|
+
if self.peer_ip == None:
|
|
170
|
+
return False, None
|
|
171
|
+
start = get_time_ms()
|
|
172
|
+
if self.send(UDPConnection.PING, timeout=timeout) and \
|
|
173
|
+
self.wait_input(UDPConnection.COMMAND_REPLY, duration=timeout):
|
|
174
|
+
return True, get_time_ms() - start
|
|
175
|
+
return False, None
|
|
176
|
+
|
|
177
|
+
def clear_receive_buffer(self):
|
|
178
|
+
data = ""
|
|
179
|
+
while data is not None:
|
|
180
|
+
data = self.poll()
|
|
181
|
+
|
|
182
|
+
def poll_last_data(self):
|
|
183
|
+
"""polls all data and returns only the last one
|
|
184
|
+
return None if not data found"""
|
|
185
|
+
rtn = None
|
|
186
|
+
tmp = self.poll()
|
|
187
|
+
while tmp is not None:
|
|
188
|
+
rtn = tmp
|
|
189
|
+
tmp = self.poll()
|
|
190
|
+
return rtn
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class UDPConnectionProcess(Process):
|
|
194
|
+
"""UDPConnectionProcess polls and writes to a data queue.
|
|
195
|
+
|
|
196
|
+
Example::
|
|
197
|
+
|
|
198
|
+
# Server that prints each input and echos it to the client
|
|
199
|
+
# that is currently connected
|
|
200
|
+
|
|
201
|
+
from udp_connection import UDPConnectionProcess, Queue
|
|
202
|
+
|
|
203
|
+
receive_queue = Queue()
|
|
204
|
+
udp_p = UDPConnectionProcess(receive_queue=receive_queue)
|
|
205
|
+
udp_p.start()
|
|
206
|
+
udp_p.event_polling.set() # start polling
|
|
207
|
+
|
|
208
|
+
while True:
|
|
209
|
+
data = receive_queue.get()
|
|
210
|
+
print(data)
|
|
211
|
+
if data is not None:
|
|
212
|
+
udp_p.send_queue.put(data.string)
|
|
213
|
+
|
|
214
|
+
Example::
|
|
215
|
+
|
|
216
|
+
# connecting to a server
|
|
217
|
+
""" # DOC
|
|
218
|
+
|
|
219
|
+
def __init__(self, event_trigger = (),
|
|
220
|
+
event_ignore_tag = None):
|
|
221
|
+
"""Initialize UDPConnectionProcess
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
receive_queue: multiprocessing.Queue
|
|
226
|
+
the queue to which the received data should be put
|
|
227
|
+
|
|
228
|
+
peer_ip : string
|
|
229
|
+
the IP of the peer to which the connection should be established
|
|
230
|
+
|
|
231
|
+
sync_clock : Timer
|
|
232
|
+
the internal clock for timestamps will synchronized with this clock
|
|
233
|
+
|
|
234
|
+
event_trigger: multiprocessing.Event() (or list of..)
|
|
235
|
+
event trigger(s) to be set. If Udp event is received and it is not a
|
|
236
|
+
command to set this event (typical of sensor recording processes).
|
|
237
|
+
|
|
238
|
+
event_ignore_tag:
|
|
239
|
+
udp data that start with this tag will be ignored for event triggering
|
|
240
|
+
|
|
241
|
+
""" # DOC
|
|
242
|
+
|
|
243
|
+
super(UDPConnectionProcess, self).__init__()
|
|
244
|
+
|
|
245
|
+
self.receive_queue = Queue()
|
|
246
|
+
self.send_queue = Queue()
|
|
247
|
+
self.event_is_connected = Event()
|
|
248
|
+
self._event_quit_request = Event()
|
|
249
|
+
self._event_is_polling = Event()
|
|
250
|
+
self._event_ignore_tag = event_ignore_tag
|
|
251
|
+
|
|
252
|
+
if isinstance(event_trigger, type(Event) ):
|
|
253
|
+
event_trigger = (event_trigger)
|
|
254
|
+
try:
|
|
255
|
+
self._event_trigger = tuple(event_trigger)
|
|
256
|
+
except:
|
|
257
|
+
self._event_trigger = ()
|
|
258
|
+
|
|
259
|
+
atexit.register(self.quit)
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def my_ip(self):
|
|
263
|
+
return UDPConnection.MY_IP
|
|
264
|
+
|
|
265
|
+
def quit(self):
|
|
266
|
+
self._event_quit_request.set()
|
|
267
|
+
if self.is_alive():
|
|
268
|
+
self.join()
|
|
269
|
+
|
|
270
|
+
def pause(self):
|
|
271
|
+
self._event_is_polling.clear()
|
|
272
|
+
|
|
273
|
+
def start_polling(self):
|
|
274
|
+
self._event_is_polling.set()
|
|
275
|
+
|
|
276
|
+
def run(self):
|
|
277
|
+
udp_connection = UDPConnection(udp_port=5005)
|
|
278
|
+
self.start_polling()
|
|
279
|
+
|
|
280
|
+
ptp = PollingTimeProfile()
|
|
281
|
+
prev_event_polling = None
|
|
282
|
+
|
|
283
|
+
while not self._event_quit_request.is_set():
|
|
284
|
+
|
|
285
|
+
if prev_event_polling != self._event_is_polling.is_set():
|
|
286
|
+
# event pooling changed
|
|
287
|
+
prev_event_polling = self._event_is_polling.is_set()
|
|
288
|
+
if prev_event_polling:
|
|
289
|
+
logging.warning("UDP start, pid {}, priority {}".format(
|
|
290
|
+
self.pid, get_priority(self.pid)))
|
|
291
|
+
else:
|
|
292
|
+
logging.warning("UDP stop")
|
|
293
|
+
ptp.stop()
|
|
294
|
+
|
|
295
|
+
if not self._event_is_polling.is_set():
|
|
296
|
+
self._event_is_polling.wait(timeout=0.1)
|
|
297
|
+
else:
|
|
298
|
+
data = udp_connection.poll()
|
|
299
|
+
t = udp_connection.timer.time
|
|
300
|
+
ptp.update(t)
|
|
301
|
+
if data is not None:
|
|
302
|
+
d = UDPData(string=data, time=t)
|
|
303
|
+
self.receive_queue.put(d)
|
|
304
|
+
if self._event_ignore_tag is not None and \
|
|
305
|
+
not d.startswith(self._event_ignore_tag):
|
|
306
|
+
for ev in self._event_trigger:
|
|
307
|
+
# set all connected software trigger
|
|
308
|
+
ev.set() ## FIXME LSL trigger
|
|
309
|
+
try:
|
|
310
|
+
udp_connection.send(self.send_queue.get_nowait())
|
|
311
|
+
except:
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
# has connection changed?
|
|
315
|
+
if self.event_is_connected.is_set() != udp_connection.is_connected:
|
|
316
|
+
if udp_connection.is_connected:
|
|
317
|
+
self.event_is_connected.set()
|
|
318
|
+
else:
|
|
319
|
+
self.event_is_connected.clear()
|
|
320
|
+
|
|
321
|
+
if not udp_connection.is_connected:
|
|
322
|
+
timer.wait(200)
|
|
323
|
+
|
|
324
|
+
udp_connection.unconnect_peer()
|
|
325
|
+
|
|
326
|
+
logging.warning("UDP quit, {}".format(ptp.get_profile_str()))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__author__ = "Oliver Lindemann"
|
|
2
|
+
__version__ = "0.4"
|
|
3
|
+
|
|
4
|
+
from .. import USE_MOCK_SENSOR
|
|
5
|
+
from ._pyATIDAQ import ATI_CDLL
|
|
6
|
+
from .config import DAQConfiguration
|
|
7
|
+
|
|
8
|
+
if USE_MOCK_SENSOR:
|
|
9
|
+
print("Using mock sensor instead.")
|
|
10
|
+
from ._mock_sensor import DAQReadAnalog
|
|
11
|
+
else:
|
|
12
|
+
#### change import here if you want to use nidaqmx instead of pydaymx ####
|
|
13
|
+
try:
|
|
14
|
+
from ._daq_read_analog_pydaqmx import DAQReadAnalog
|
|
15
|
+
#from ._daq_read_analog_nidaqmx import DAQReadAnalog
|
|
16
|
+
except (ImportError, ModuleNotFoundError):
|
|
17
|
+
print("Warning: PyDAQmx or nidaqmx not found. Using mock sensor instead.")
|
|
18
|
+
from ._mock_sensor import DAQReadAnalog
|
|
19
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Module to read data analog data from NI-DAQ device
|
|
2
|
+
|
|
3
|
+
A simple high-level wrapper for NI-DAQmx functions
|
|
4
|
+
|
|
5
|
+
Requires: PyDAQmx, Numpy
|
|
6
|
+
|
|
7
|
+
See COPYING file distributed along with the pyForceDAQ copyright and license terms.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = 'Oliver Lindemann'
|
|
11
|
+
|
|
12
|
+
import ctypes as ct
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import PyDAQmx
|
|
16
|
+
|
|
17
|
+
from . import DAQConfiguration
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DAQReadAnalog(PyDAQmx.Task):
|
|
21
|
+
|
|
22
|
+
NUM_SAMPS_PER_CHAN = ct.c_int32(1)
|
|
23
|
+
TIMEOUT = ct.c_longdouble(1.0) # one second
|
|
24
|
+
NI_DAQ_BUFFER_SIZE = 1000
|
|
25
|
+
DAQ_TYPE = "PyDAQmx"
|
|
26
|
+
|
|
27
|
+
def __init__(self, configuration: DAQConfiguration, read_array_size_in_samples: int ):
|
|
28
|
+
""" DOC
|
|
29
|
+
read_array_size_in_samples for ReadAnalogF64 call
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# print('init')
|
|
34
|
+
PyDAQmx.Task.__init__(self)
|
|
35
|
+
# CreateAIVoltageChan
|
|
36
|
+
self.CreateAIVoltageChan(configuration.physicalChannel,
|
|
37
|
+
# physicalChannel
|
|
38
|
+
"", # nameToAssignToChannel,
|
|
39
|
+
PyDAQmx.DAQmx_Val_Diff, # terminalConfig
|
|
40
|
+
ct.c_double(configuration.minVal),
|
|
41
|
+
ct.c_double(configuration.maxVal),
|
|
42
|
+
# min max Val
|
|
43
|
+
PyDAQmx.DAQmx_Val_Volts, # units
|
|
44
|
+
None # customScaleName
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# CfgSampClkTiming
|
|
48
|
+
self.CfgSampClkTiming("", # source
|
|
49
|
+
ct.c_double(float(configuration.rate)), # rate
|
|
50
|
+
PyDAQmx.DAQmx_Val_Rising, # activeEdge
|
|
51
|
+
PyDAQmx.DAQmx_Val_ContSamps, # sampleMode
|
|
52
|
+
ct.c_uint64(DAQReadAnalog.NI_DAQ_BUFFER_SIZE)
|
|
53
|
+
# sampsPerChanToAcquire, i.e. buffer size
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
self._task_is_started = False
|
|
57
|
+
self.read_array_size_in_samples = read_array_size_in_samples
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def is_acquiring_data(self):
|
|
61
|
+
return self._task_is_started
|
|
62
|
+
|
|
63
|
+
def start_data_acquisition(self):
|
|
64
|
+
"""Start data acquisition of the NI device
|
|
65
|
+
call always before polling
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
if not self._task_is_started:
|
|
70
|
+
self.StartTask()
|
|
71
|
+
self._task_is_started = True
|
|
72
|
+
|
|
73
|
+
def stop_data_acquisition(self):
|
|
74
|
+
""" Stop data acquisition of the NI device
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
if self._task_is_started:
|
|
78
|
+
self.StopTask()
|
|
79
|
+
self._task_is_started = False
|
|
80
|
+
|
|
81
|
+
def read_analog(self):
|
|
82
|
+
"""Polling data
|
|
83
|
+
|
|
84
|
+
Reading data from NI device
|
|
85
|
+
|
|
86
|
+
Parameter
|
|
87
|
+
---------
|
|
88
|
+
array_size_in_samps : int
|
|
89
|
+
the array size in number of samples
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
read_buffer : numpy array
|
|
94
|
+
the read data
|
|
95
|
+
read_samples : int
|
|
96
|
+
the number of read samples
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
# fill in data
|
|
101
|
+
read_samples = ct.c_int32()
|
|
102
|
+
read_buffer = np.zeros((self.read_array_size_in_samples,),
|
|
103
|
+
dtype=np.float64)
|
|
104
|
+
|
|
105
|
+
error = self.ReadAnalogF64(self.NUM_SAMPS_PER_CHAN,
|
|
106
|
+
self.TIMEOUT,
|
|
107
|
+
PyDAQmx.DAQmx_Val_GroupByScanNumber,
|
|
108
|
+
# fillMode
|
|
109
|
+
read_buffer,
|
|
110
|
+
ct.c_uint32(self.read_array_size_in_samples),
|
|
111
|
+
ct.byref(read_samples),
|
|
112
|
+
None)
|
|
113
|
+
|
|
114
|
+
return read_buffer, read_samples.value
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import nidaqmx
|
|
2
|
+
import numpy as np
|
|
3
|
+
from nidaqmx import constants as nidaq_consts
|
|
4
|
+
|
|
5
|
+
from . import DAQConfiguration
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DAQReadAnalog(nidaqmx.Task):
|
|
9
|
+
|
|
10
|
+
NUM_SAMPS_PER_CHAN = nidaq_consts.READ_ALL_AVAILABLE # or 1 FIXME?
|
|
11
|
+
TIMEOUT = 1.0
|
|
12
|
+
DAQ_TYPE = "nidaqmx"
|
|
13
|
+
|
|
14
|
+
def __init__(self, configuration: DAQConfiguration, read_array_size_in_samples: int):
|
|
15
|
+
""" DOC
|
|
16
|
+
read_array_size_in_samples for ReadAnalogF64 call
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
nidaqmx.Task.__init__(self)
|
|
21
|
+
|
|
22
|
+
# CreateAIVoltageChan
|
|
23
|
+
self.ai_channels.add_ai_voltage_chan(physical_channel=configuration.physicalChannel,
|
|
24
|
+
terminal_config=nidaq_consts.TerminalConfiguration.DIFF,
|
|
25
|
+
min_val=configuration.minVal,
|
|
26
|
+
max_val=configuration.maxVal,
|
|
27
|
+
units=nidaq_consts.VoltageUnits.VOLTS
|
|
28
|
+
)
|
|
29
|
+
print('added channels')
|
|
30
|
+
#CfgSampClkTiming
|
|
31
|
+
self.timing.cfg_samp_clk_timing(rate=float(configuration.rate),
|
|
32
|
+
active_edge=nidaq_consts.Edge.RISING,
|
|
33
|
+
sample_mode=nidaq_consts.AcquisitionType.CONTINUOUS
|
|
34
|
+
)
|
|
35
|
+
print('devices')
|
|
36
|
+
print(nidaqmx.Task.devices)
|
|
37
|
+
self._task_is_started = False
|
|
38
|
+
self.read_array_size_in_samples = read_array_size_in_samples
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_acquiring_data(self):
|
|
42
|
+
return self._task_is_started
|
|
43
|
+
|
|
44
|
+
def start_data_acquisition(self):
|
|
45
|
+
"""Start data acquisition of the NI device
|
|
46
|
+
call always before polling
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if not self._task_is_started:
|
|
51
|
+
self.start()
|
|
52
|
+
self._task_is_started = True
|
|
53
|
+
|
|
54
|
+
def stop_data_acquisition(self):
|
|
55
|
+
""" Stop data acquisition of the NI device
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
if self._task_is_started:
|
|
59
|
+
self.stop()
|
|
60
|
+
self._task_is_started = False
|
|
61
|
+
|
|
62
|
+
def read_analog(self):
|
|
63
|
+
"""Polling data
|
|
64
|
+
|
|
65
|
+
Reading data from NI device
|
|
66
|
+
|
|
67
|
+
Parameter
|
|
68
|
+
---------
|
|
69
|
+
array_size_in_samps : int
|
|
70
|
+
the array size in number of samples
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
read_buffer : numpy array
|
|
75
|
+
the read data
|
|
76
|
+
read_samples : int
|
|
77
|
+
the number of read samples
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
#fill in data
|
|
82
|
+
data = self.read(self.NUM_SAMPS_PER_CHAN, self.TIMEOUT)
|
|
83
|
+
np_data = np.reshape(np.array(data),(-1,))
|
|
84
|
+
return np_data, len(np_data)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
__author__ = 'Oliver Lindemann'
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from .._lib.timer import Timer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DAQReadAnalog(object):
|
|
11
|
+
NUM_SAMPS_PER_CHAN = 1
|
|
12
|
+
TIMEOUT = 1.0
|
|
13
|
+
NI_DAQ_BUFFER_SIZE = 1000
|
|
14
|
+
DAQ_TYPE = "mock_sensor"
|
|
15
|
+
|
|
16
|
+
def __init__(self, configuration=None,
|
|
17
|
+
read_array_size_in_samples=None):
|
|
18
|
+
self.read_array_size_in_samples = read_array_size_in_samples
|
|
19
|
+
self._task_is_started = False
|
|
20
|
+
self._last_time = 0
|
|
21
|
+
self._sample_cnt = 0
|
|
22
|
+
self._simulation_timer = Timer()
|
|
23
|
+
txt = "Using mock sensor: Maybe PyDAQmx or nidaqmx is not installed"
|
|
24
|
+
logging.warning(txt)
|
|
25
|
+
print(txt)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def is_acquiring_data(self):
|
|
30
|
+
return self._task_is_started
|
|
31
|
+
|
|
32
|
+
def start_data_acquisition(self):
|
|
33
|
+
"""Start data acquisition of the NI device
|
|
34
|
+
call always before polling
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if not self._task_is_started:
|
|
39
|
+
self._task_is_started = True
|
|
40
|
+
self._simulation_timer = Timer() #reset
|
|
41
|
+
self._sample_cnt = 0
|
|
42
|
+
|
|
43
|
+
def stop_data_acquisition(self):
|
|
44
|
+
""" Stop data acquisition of the NI device
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
if self._task_is_started:
|
|
48
|
+
self._task_is_started = False
|
|
49
|
+
|
|
50
|
+
def read_analog(self):
|
|
51
|
+
"""Reading data
|
|
52
|
+
|
|
53
|
+
Reading data from NI device
|
|
54
|
+
|
|
55
|
+
Parameter
|
|
56
|
+
---------
|
|
57
|
+
array_size_in_samps : int
|
|
58
|
+
the array size in number of samples
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
read_buffer : numpy array
|
|
63
|
+
the read data
|
|
64
|
+
read_samples : int
|
|
65
|
+
the number of read samples
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
# fill in data
|
|
70
|
+
if not self._task_is_started:
|
|
71
|
+
return None, None
|
|
72
|
+
|
|
73
|
+
n_new_samples = self._simulation_timer.time - self._sample_cnt
|
|
74
|
+
while n_new_samples <= 0:
|
|
75
|
+
n_new_samples = self._simulation_timer.time - self._sample_cnt
|
|
76
|
+
|
|
77
|
+
self._sample_cnt += 1
|
|
78
|
+
x = self._sample_cnt / 2000
|
|
79
|
+
y = 10 + np.array((np.sin(x/2), np.cos(x/5), np.sin(x)))*10
|
|
80
|
+
return np.append(y, np.array((0, 0 , 0, 0, 0))), 1
|