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.
Files changed (42) hide show
  1. pyforcedaq/__init__.py +43 -0
  2. pyforcedaq/__main__.py +42 -0
  3. pyforcedaq/_lib/__init__.py +1 -0
  4. pyforcedaq/_lib/lsl.py +56 -0
  5. pyforcedaq/_lib/misc.py +126 -0
  6. pyforcedaq/_lib/polling_time_profile.py +52 -0
  7. pyforcedaq/_lib/process_priority_manager.py +148 -0
  8. pyforcedaq/_lib/timer.py +45 -0
  9. pyforcedaq/_lib/types.py +400 -0
  10. pyforcedaq/_lib/udp_connection.py +326 -0
  11. pyforcedaq/daq/__init__.py +19 -0
  12. pyforcedaq/daq/_daq_read_Analog_pydaqmx.py +114 -0
  13. pyforcedaq/daq/_daq_read_analog_nidaqmx.py +84 -0
  14. pyforcedaq/daq/_mock_sensor.py +80 -0
  15. pyforcedaq/daq/_pyATIDAQ.py +306 -0
  16. pyforcedaq/daq/config.py +13 -0
  17. pyforcedaq/extras/__init__.py +0 -0
  18. pyforcedaq/extras/convert.py +275 -0
  19. pyforcedaq/extras/expyriment_daq_control.py +246 -0
  20. pyforcedaq/extras/opensesame_daq_control.py +280 -0
  21. pyforcedaq/extras/read_force_data.py +89 -0
  22. pyforcedaq/extras/remote_control.py +93 -0
  23. pyforcedaq/force/__init__.py +13 -0
  24. pyforcedaq/force/_log.py +18 -0
  25. pyforcedaq/force/data_recorder.py +400 -0
  26. pyforcedaq/force/sensor.py +200 -0
  27. pyforcedaq/force/sensor_process.py +251 -0
  28. pyforcedaq/gui/__init__.py +6 -0
  29. pyforcedaq/gui/_gui_status.py +306 -0
  30. pyforcedaq/gui/_layout.py +104 -0
  31. pyforcedaq/gui/_level_indicator.py +59 -0
  32. pyforcedaq/gui/_pg_surface.py +100 -0
  33. pyforcedaq/gui/_plotter.py +234 -0
  34. pyforcedaq/gui/_run.py +522 -0
  35. pyforcedaq/gui/_scaling.py +71 -0
  36. pyforcedaq/gui/_settings.py +98 -0
  37. pyforcedaq/gui/forceDAQ_logo.png +0 -0
  38. pyforcedaq/gui/launcher.py +249 -0
  39. pyforcedaq-2.0.0.dist-info/METADATA +15 -0
  40. pyforcedaq-2.0.0.dist-info/RECORD +42 -0
  41. pyforcedaq-2.0.0.dist-info/WHEEL +4 -0
  42. 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