pyForceDAQ 2.0.0.dev0__tar.gz → 2.0.1__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.
Files changed (45) hide show
  1. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/PKG-INFO +2 -2
  2. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/pyproject.toml +2 -2
  3. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/__init__.py +1 -1
  4. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/__main__.py +10 -3
  5. {pyforcedaq-2.0.0.dev0/src/pyforcedaq/force → pyforcedaq-2.0.1/src/pyforcedaq/_lib}/_log.py +2 -1
  6. pyforcedaq-2.0.1/src/pyforcedaq/_lib/clock.py +40 -0
  7. {pyforcedaq-2.0.0.dev0/src/pyforcedaq/force → pyforcedaq-2.0.1/src/pyforcedaq/_lib}/data_recorder.py +41 -46
  8. pyforcedaq-2.0.1/src/pyforcedaq/_lib/lsl.py +75 -0
  9. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/_lib/misc.py +32 -23
  10. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/_lib/polling_time_profile.py +10 -8
  11. {pyforcedaq-2.0.0.dev0/src/pyforcedaq/force → pyforcedaq-2.0.1/src/pyforcedaq/_lib}/sensor.py +28 -34
  12. {pyforcedaq-2.0.0.dev0/src/pyforcedaq/force → pyforcedaq-2.0.1/src/pyforcedaq/_lib}/sensor_process.py +27 -19
  13. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/_lib/types.py +34 -22
  14. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/_lib/udp_connection.py +13 -19
  15. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/daq/__init__.py +5 -4
  16. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/daq/_mock_sensor.py +7 -7
  17. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/daq/_pyATIDAQ.py +1 -3
  18. pyforcedaq-2.0.1/src/pyforcedaq/force.py +10 -0
  19. pyforcedaq-2.0.1/src/pyforcedaq/gui/__init__.py +6 -0
  20. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/_gui_status.py +1 -1
  21. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/_run.py +48 -44
  22. pyforcedaq-2.0.1/src/pyforcedaq/gui/_settings.py +101 -0
  23. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/launcher.py +24 -23
  24. pyforcedaq-2.0.0.dev0/src/pyforcedaq/_lib/lsl.py +0 -56
  25. pyforcedaq-2.0.0.dev0/src/pyforcedaq/_lib/timer.py +0 -45
  26. pyforcedaq-2.0.0.dev0/src/pyforcedaq/force/__init__.py +0 -13
  27. pyforcedaq-2.0.0.dev0/src/pyforcedaq/gui/__init__.py +0 -6
  28. pyforcedaq-2.0.0.dev0/src/pyforcedaq/gui/_settings.py +0 -98
  29. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/_lib/__init__.py +0 -0
  30. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/_lib/process_priority_manager.py +0 -0
  31. /pyforcedaq-2.0.0.dev0/src/pyforcedaq/daq/_daq_read_analog_nidaqmx.py → /pyforcedaq-2.0.1/src/pyforcedaq/daq/_use_nidaqmx.py +0 -0
  32. /pyforcedaq-2.0.0.dev0/src/pyforcedaq/daq/_daq_read_Analog_pydaqmx.py → /pyforcedaq-2.0.1/src/pyforcedaq/daq/_use_pydaqmx.py +0 -0
  33. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/daq/config.py +0 -0
  34. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/extras/__init__.py +0 -0
  35. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/extras/convert.py +0 -0
  36. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/extras/expyriment_daq_control.py +0 -0
  37. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/extras/opensesame_daq_control.py +0 -0
  38. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/extras/read_force_data.py +0 -0
  39. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/extras/remote_control.py +0 -0
  40. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/_layout.py +0 -0
  41. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/_level_indicator.py +0 -0
  42. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/_pg_surface.py +0 -0
  43. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/_plotter.py +0 -0
  44. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/_scaling.py +0 -0
  45. {pyforcedaq-2.0.0.dev0 → pyforcedaq-2.0.1}/src/pyforcedaq/gui/forceDAQ_logo.png +0 -0
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyForceDAQ
3
- Version: 2.0.0.dev0
3
+ Version: 2.0.1
4
4
  Summary: A Python package for data acquisition and analysis in force-based experiments.
5
5
  Author: Oliver Lindemann
6
6
  Author-email: Oliver Lindemann <lindemann@essb.eur.nl>
7
7
  Requires-Dist: atiiaftt>=0.1.1
8
8
  Requires-Dist: expyriment>=1.0.1
9
- Requires-Dist: freesimplegui>=5.2.0.post1
10
9
  Requires-Dist: icecream>=2.2.0
11
10
  Requires-Dist: nidaqmx>=1.5.0
12
11
  Requires-Dist: numpy>=2.4.4
13
12
  Requires-Dist: psutil>=7.2.2
14
13
  Requires-Dist: pydaqmx>=1.4.7
15
14
  Requires-Dist: pylsl>=1.18.2
15
+ Requires-Dist: pysimplegui>=6.0
16
16
  Requires-Dist: pyxdf>=1.17.4
17
17
  Requires-Dist: tomlkit>=0.15.0
18
18
  Requires-Python: >=3.12, <3.14
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyForceDAQ"
3
- version = "2.0.0-dev0"
3
+ version = "2.0.1"
4
4
  description = "A Python package for data acquisition and analysis in force-based experiments."
5
5
  authors = [
6
6
  { name = "Oliver Lindemann", email = "lindemann@essb.eur.nl" },
@@ -9,13 +9,13 @@ requires-python = ">=3.12, <3.14"
9
9
  dependencies = [
10
10
  "atiiaftt>=0.1.1",
11
11
  "expyriment>=1.0.1",
12
- "freesimplegui>=5.2.0.post1",
13
12
  "icecream>=2.2.0",
14
13
  "nidaqmx>=1.5.0",
15
14
  "numpy>=2.4.4",
16
15
  "psutil>=7.2.2",
17
16
  "pydaqmx>=1.4.7",
18
17
  "pylsl>=1.18.2",
18
+ "pysimplegui>=6.0",
19
19
  "pyxdf>=1.17.4",
20
20
  "tomlkit>=0.15.0",
21
21
  ]
@@ -7,7 +7,7 @@ launch the GUI force from your Python program:
7
7
  ``
8
8
  from pyforcedaq import gui
9
9
 
10
- gui.run_with_options(remote_control=False,
10
+ gui.run(remote_control=False,
11
11
  ask_filename=True,
12
12
  calibration_file="FT_sensor1.cal")
13
13
  ``
@@ -24,9 +24,16 @@ def cli():
24
24
  help="Run with launcher GUI to edit settings and start recording",
25
25
  )
26
26
 
27
+ parser.add_argument(
28
+ "-o", "--omit-launcher",
29
+ action="store_true",
30
+ default=False,
31
+ help="Omit launcher GUI and start recording directly",
32
+ )
33
+
27
34
  args = parser.parse_args()
28
35
 
29
- if args.launcher:
36
+ if not args.omit_launcher:
30
37
  if len(args.SETTINGS_FILE) > 0:
31
38
  print("Can't use launcher and settings file")
32
39
  exit()
@@ -34,9 +41,9 @@ def cli():
34
41
  from .gui import launcher
35
42
  return launcher.run()
36
43
  else:
37
- gui.run(args.SETTINGS_FILE)
44
+ gui.run_settings(args.SETTINGS_FILE)
38
45
 
39
46
 
40
47
 
41
48
  if __name__ == "__main__": # required because of threading
42
- cli() # gui.run(), gui.run_with_options()
49
+ cli()
@@ -1,6 +1,7 @@
1
+ import logging
1
2
  import os
2
3
  import sys
3
- import logging
4
+
4
5
 
5
6
  def set_logging(data_directory, log_file):
6
7
  base_dir = os.path.split(sys.argv[0])[0]
@@ -0,0 +1,40 @@
1
+ """A high-resolution monotonic timer based on LSL's local_clock() function.
2
+ """
3
+
4
+
5
+ from time import sleep
6
+
7
+ from pylsl import local_clock
8
+
9
+
10
+ def local_clock_ms():
11
+ """Returns the current time in milliseconds, based on LSL's local_clock()"""
12
+ return local_clock() * 1000
13
+
14
+ def wait_ms(waiting_time: int | float, looptime : int = 200) -> None:
15
+ """Wait for a certain amount of milliseconds.
16
+ """
17
+ start = local_clock_ms()
18
+ if waiting_time > looptime:
19
+ sleep((waiting_time - looptime) / 1000)
20
+ while local_clock_ms() < start + waiting_time:
21
+ pass
22
+
23
+ class StopWatch(object):#
24
+ """A simple timer"""
25
+
26
+ def __init__(self):
27
+ self._init_time = local_clock()
28
+
29
+ def reset_stopwatch(self):
30
+ """Reset the stopwatch time to zero.
31
+ """
32
+ self._init_time = local_clock()
33
+
34
+ @property
35
+ def time(self) -> float:
36
+ return local_clock() - self._init_time
37
+
38
+ @property
39
+ def time_ms(self) -> float:
40
+ return self.time * 1000
@@ -7,16 +7,16 @@ __author__ = "Oliver Lindemann"
7
7
  import atexit
8
8
  import gzip
9
9
  import logging
10
- import os
11
- import sys
10
+ from pathlib import Path
12
11
  from time import asctime, localtime, strftime
13
12
 
14
- from pyforcedaq._lib import timer
15
-
16
13
  from .. import __version__ as forceDAQVersion
17
- from .._lib.process_priority_manager import ProcessPriorityManager
18
- from .._lib.timer import app_clock
19
- from .._lib.types import (
14
+ from . import _log
15
+ from .clock import wait_ms
16
+ from .process_priority_manager import ProcessPriorityManager
17
+ from .sensor import SensorSettings
18
+ from .sensor_process import SensorProcess
19
+ from .types import (
20
20
  TAG_COMMENTS,
21
21
  TAG_DAQEVENT,
22
22
  TAG_UDPDATA,
@@ -25,10 +25,10 @@ from .._lib.types import (
25
25
  PollingPriority,
26
26
  UDPData,
27
27
  )
28
- from .._lib.types import GUIRemoteControlCommands as RemoteCmd
29
- from .._lib.udp_connection import UDPConnectionProcess
30
- from .sensor import SensorSettings
31
- from .sensor_process import SensorProcess
28
+ from .types import GUIRemoteControlCommands as RemoteCmd
29
+ from .udp_connection import UDPConnectionProcess
30
+
31
+ _log.set_logging(data_directory="data", log_file="recording.log")
32
32
 
33
33
  NEWLINE = "\n"
34
34
 
@@ -38,7 +38,7 @@ class DataRecorder(object):
38
38
  def __init__(self, force_sensor_settings:SensorSettings | list,
39
39
  poll_udp_connection=False,
40
40
  write_deviceid = False,
41
- polling_priority=None):
41
+ polling_priority:str | None = None):
42
42
 
43
43
 
44
44
  """queue_data will be saved
@@ -82,18 +82,18 @@ class DataRecorder(object):
82
82
  self._proc_manager.add_subprocess(self.udp)
83
83
  self._proc_manager.add_subprocess(self._force_sensor_processes)
84
84
  if polling_priority is not None:
85
- self._proc_manager.set_subprocess_priorities(
86
- level=PollingPriority.get_priority(polling_priority),
87
- disable_gc=False)
85
+ level = PollingPriority.NORMAL
86
+ else:
87
+ level = PollingPriority.get_priority(polling_priority)
88
+ self._proc_manager.set_subprocess_priorities(level=level, disable_gc=False)
88
89
 
89
- logging.info("Main process priority: {}".format(
90
- self._proc_manager.get_main_priority()))
90
+ logging.info("Main process priority: %s", self._proc_manager.get_main_priority())
91
91
  #logging.info("Subprocess priorities: {}".format(self._proc_manager.get_subprocess_priorities()))
92
92
 
93
93
  self._is_recording = False
94
94
  self._file = None
95
95
  self._daq_event = []
96
- self.filename = None
96
+ self.filename: str | None = None
97
97
  atexit.register(self.quit)
98
98
 
99
99
 
@@ -102,7 +102,7 @@ class DataRecorder(object):
102
102
  """Property indicates whether the recording processes are alive"""
103
103
  try:
104
104
  return self._force_sensor_processes[0].is_alive()
105
- except:
105
+ except Exception:
106
106
  return False
107
107
 
108
108
  @property
@@ -119,7 +119,7 @@ class DataRecorder(object):
119
119
  return list(map(lambda x:x.sensor_settings,
120
120
  self._force_sensor_processes))
121
121
 
122
- def quit(self):
122
+ def quit(self) -> list | None:
123
123
  """Stop all recording processes, close data file and quit recording
124
124
 
125
125
  Notes
@@ -145,7 +145,7 @@ class DataRecorder(object):
145
145
 
146
146
  return buffer
147
147
 
148
- def process_and_write_udp_events(self):
148
+ def process_and_write_udp_events(self) -> list:
149
149
  """process udp events and return them"""
150
150
  buffer = []
151
151
  while True:
@@ -159,9 +159,9 @@ class DataRecorder(object):
159
159
  self._save_data(buffer)
160
160
  return buffer
161
161
 
162
- def _save_data(self, data_buffer,
162
+ def _save_data(self, data_buffer: list,
163
163
  recording_screen=None,
164
- float_decimal_places=4):
164
+ float_decimal_places: int = 4) -> None:
165
165
  """ writes data to disk and set counters
166
166
 
167
167
  ignores UDP remote control commands
@@ -199,21 +199,19 @@ class DataRecorder(object):
199
199
  "Writing {0} of {1} blocks".format(c//BLOCKSIZE,
200
200
  buffer_len//BLOCKSIZE)).present()
201
201
 
202
- def _file_write(self, str):
203
- self._file.write(str.encode())
202
+ def _file_write(self, s: str) -> None:
203
+ self._file.write(s.encode())
204
204
 
205
- def save_daq_event(self, code, time=None):
205
+ def save_daq_event(self, code: str | int | float, time: float | None = None) -> None:
206
206
  """Set marker code in file
207
207
 
208
208
  DAQEvent will be timestamps and occur in the data output
209
209
 
210
210
  """
211
- if time is None:
212
- time = app_clock.time
213
211
  self._daq_event.append(DAQEvents(time = time, code = code))
214
212
 
215
213
 
216
- def start_recording(self, determine_bias=False):
214
+ def start_recording(self, determine_bias: bool = False) -> None:
217
215
  """Start polling process and record
218
216
 
219
217
  See Also
@@ -233,7 +231,7 @@ class DataRecorder(object):
233
231
  list(map(lambda x:x.start_polling(), self._force_sensor_processes))
234
232
  self._is_recording = True
235
233
 
236
- def pause_recording(self, recording_screen=None):
234
+ def pause_recording(self, recording_screen=None) -> list:
237
235
  """Pauses all polling processes and process data
238
236
 
239
237
  returns
@@ -251,7 +249,7 @@ class DataRecorder(object):
251
249
  for fsp in self._force_sensor_processes:
252
250
  fsp.pause_polling()
253
251
 
254
- timer.wait(500)
252
+ wait_ms(500)
255
253
 
256
254
  # get data
257
255
  for fsp in self._force_sensor_processes:
@@ -268,7 +266,7 @@ class DataRecorder(object):
268
266
  self._daq_event = []
269
267
  return data
270
268
 
271
- def determine_biases(self, n_samples):
269
+ def determine_biases(self, n_samples: int) -> None:
272
270
  """Record n data samples (n_samples) to determine bias.
273
271
  Afterwards recording is in pause mode
274
272
 
@@ -289,12 +287,12 @@ class DataRecorder(object):
289
287
  for x in self._force_sensor_processes:
290
288
  x.event_bias_is_available.wait()
291
289
 
292
- def open_data_file(self, filename,
293
- subdirectory="data",
294
- time_stamp_filename=False,
295
- varnames = True,
296
- comment_line="",
297
- zipped=False):
290
+ def open_data_file(self, filename: str,
291
+ subdirectory: str = "data",
292
+ time_stamp_filename: bool = False,
293
+ varnames: bool = True,
294
+ comment_line: str = "",
295
+ zipped: bool = False) -> Path:
298
296
  """Create a data file
299
297
 
300
298
  Only if data file has been opened, data will be saved!
@@ -322,11 +320,8 @@ class DataRecorder(object):
322
320
  full path the actually used file (incl. timestamp)
323
321
 
324
322
  """
325
-
326
- base_dir = os.path.split(sys.argv[0])[0]
327
- data_dir = os.path.join(base_dir, subdirectory)
328
- if not os.path.isdir(data_dir):
329
- os.mkdir(data_dir)
323
+ data_dir = Path.cwd() / subdirectory
324
+ data_dir.mkdir(exist_ok=True)
330
325
  self.close_data_file()
331
326
 
332
327
  if filename is None or len(filename) == 0:
@@ -352,8 +347,8 @@ class DataRecorder(object):
352
347
  else:
353
348
  self.filename = flname + suffix
354
349
 
355
- full_path_file = os.path.join(data_dir, self.filename)
356
- if os.path.isfile(full_path_file):
350
+ full_path_file = data_dir / self.filename
351
+ if full_path_file.is_file():
357
352
  # print "data file already exists, adding counter"
358
353
  cnt += 1
359
354
  else:
@@ -388,7 +383,7 @@ class DataRecorder(object):
388
383
 
389
384
  return full_path_file
390
385
 
391
- def close_data_file(self):
386
+ def close_data_file(self) -> None:
392
387
  """Close the data file
393
388
 
394
389
  Afterwards data will not be saved anymore.
@@ -0,0 +1,75 @@
1
+ from pylsl import (
2
+ StreamInfo,
3
+ StreamOutlet,
4
+ cf_double64,
5
+ cf_float32,
6
+ cf_int8,
7
+ cf_int16,
8
+ cf_int32,
9
+ cf_int64,
10
+ cf_string,
11
+ cf_undefined,
12
+ )
13
+
14
+
15
+ class LSLSream():
16
+
17
+ def __init__(self):
18
+ self.outlet = None
19
+ self._is_init = False
20
+
21
+ @property
22
+ def is_init(self):
23
+ return self._is_init
24
+
25
+ def init(self,
26
+ name: str,
27
+ n_channels: int,
28
+ stream_id: str,
29
+ freq: int,
30
+ channel_format: int,
31
+ metadata: dict | None = None,
32
+ ):
33
+ """
34
+ Initialise a LSL stream
35
+
36
+ Args:
37
+ name: name of the stream
38
+ n_channels: number of channels per sample
39
+ channel_format: format/type of each channel (ex: string, int, ...)
40
+ same format for each channel
41
+ stream_id: unique identifier of the stream
42
+ content_type: content type of stream. By convention LSL uses the content
43
+ types defined in the XDF file format specification where
44
+ applicable
45
+ freq: sampling rate in Hz
46
+
47
+ Return:
48
+ outlet: StreamOulet to push samples with LSL
49
+ """
50
+ if self._is_init:
51
+ return
52
+
53
+ info = StreamInfo(name, "force",
54
+ channel_count=n_channels,
55
+ nominal_srate=freq,
56
+ channel_format=channel_format,
57
+ source_id=stream_id)
58
+
59
+ # Check if there is metadata to add to the lsl stream
60
+ if metadata:
61
+ # Get xml object of the stream created earlier
62
+ xml_info = info.desc()
63
+ # Add meta data to xml object
64
+ for key, data in metadata.items():
65
+ xml_info.append_child_value(key, str(data))
66
+
67
+ self._is_init = True
68
+ self.outlet = StreamOutlet(info)
69
+
70
+ def push_sample(self, sample: list):
71
+ """Push a sample to the LSL stream if it is initialized."""
72
+ if not self._is_init:
73
+ # Don't do anything
74
+ return
75
+ self.outlet.push_sample(sample) # type: ignore
@@ -1,6 +1,4 @@
1
- from os import listdir, path
2
-
3
- from .timer import get_time_ms
1
+ from .clock import local_clock_ms
4
2
 
5
3
 
6
4
  def N2g(N):
@@ -9,9 +7,9 @@ def N2g(N):
9
7
 
10
8
  class MinMaxDetector(object):
11
9
 
12
- def __init__(self, start_value, duration):
10
+ def __init__(self, start_value, duration_ms):
13
11
  self._minmax = [start_value, start_value]
14
- self._duration = duration
12
+ self._duration_ms = duration_ms
15
13
  self._level_change_time = None
16
14
 
17
15
  def process(self, value):
@@ -19,7 +17,7 @@ class MinMaxDetector(object):
19
17
  level change has occurred, otherwise None"""
20
18
 
21
19
  if self._level_change_time is not None:
22
- if (get_time_ms() - self._level_change_time) >= self._duration:
20
+ if (local_clock_ms() - self._level_change_time) >= self._duration_ms:
23
21
  return tuple(self._minmax)
24
22
 
25
23
  if value > self._minmax[1]:
@@ -28,7 +26,7 @@ class MinMaxDetector(object):
28
26
  self._minmax[0] = value
29
27
 
30
28
  elif self._minmax[0] != value: # level change just occurred
31
- self._level_change_time = get_time_ms()
29
+ self._level_change_time = local_clock_ms()
32
30
  return self.process(value)
33
31
 
34
32
  return None
@@ -37,22 +35,33 @@ class MinMaxDetector(object):
37
35
  def is_sampling_for_minmax(self):
38
36
  """true true if currently sampling for minmax"""
39
37
  return (self._level_change_time is not None) and \
40
- (get_time_ms() - self._level_change_time) < self._duration
41
-
42
- def find_calibration_file(calibration_folder, sensor_name,
43
- calibration_suffix=".cal"):
44
-
45
- needle = 'Serial="{0}"'.format(sensor_name)
46
- for x in listdir(path.abspath(calibration_folder)):
47
- filename = path.join(calibration_folder, x)
48
- if path.isfile(filename) and filename.endswith(calibration_suffix):
49
- with open(filename, "r") as fl:
50
- for l in fl:
51
- if l.find(needle)>0:
52
- return filename
53
- raise RuntimeError("Can't find calibration file for sensor '{0}' : {1}.".format(
54
- sensor_name, filename))
55
-
38
+ (local_clock_ms() - self._level_change_time) < self._duration_ms
39
+
40
+ # def find_calibration_file(calibration_folder: str, sensor_name: str,
41
+ # calibration_suffix=".cal") -> str:
42
+
43
+ # needle = 'Serial="{0}"'.format(sensor_name)
44
+ # calibration_files = []
45
+ # for x in listdir(path.abspath(calibration_folder)):
46
+ # filename = path.join(calibration_folder, x)
47
+ # if path.isfile(filename) and filename.endswith(calibration_suffix):
48
+ # with open(filename, "r") as fl:
49
+ # for l in fl:
50
+ # if l.find(needle)>0:
51
+ # print("Found calibration file for sensor '{0}' : {1}.".format(
52
+ # sensor_name, filename))
53
+ # calibration_files.append(filename)
54
+
55
+ # if len(calibration_files) == 1:
56
+ # return calibration_files[0]
57
+ # elif len(calibration_files) > 1:
58
+ # print("Multiple calibration files found for sensor '{0}'".format(sensor_name))
59
+ # for f in calibration_files:
60
+ # print(" - {0}".format(f))
61
+ # print("Please ensure that only one calibration file exists for each sensor")
62
+ # else:
63
+ # print("No calibration file found for sensor '{0}'.".format(sensor_name))
64
+ # exit()
56
65
 
57
66
  #Sensor History with moving average filtering and distance, velocity
58
67
  class SensorHistory(object):
@@ -1,26 +1,28 @@
1
1
  import numpy as np
2
2
 
3
- from .timer import get_time_ms
3
+ from .clock import local_clock
4
4
 
5
5
 
6
6
  class PollingTimeProfile(object):
7
7
 
8
- def __init__(self, timing_range=10):
8
+ def __init__(self, timing_range_ms=10):
9
9
  self._last = None
10
- self._timing_range = 10
10
+ self._timing_range_ms = 10
11
11
  self._zero_cnt = 0
12
12
 
13
13
  #self._zero_time_polling_frequency = {}
14
- self.profile_frequency = np.array([0] * (timing_range + 1))
14
+ self.profile_frequency = np.array([0] * (timing_range_ms + 1))
15
15
 
16
16
  def stop(self):
17
17
  self._last = None
18
18
 
19
- def update(self, time_ms):
19
+ def update(self, time: float):
20
+
21
+ time_ms = int(time * 1000)
20
22
  if self._last is not None:
21
23
  d = time_ms - self._last
22
- if d > self._timing_range:
23
- d = self._timing_range
24
+ if d > self._timing_range_ms:
25
+ d = self._timing_range_ms
24
26
  self.profile_frequency[d] += 1
25
27
 
26
28
  #if d == 0:
@@ -35,7 +37,7 @@ class PollingTimeProfile(object):
35
37
  self._last = time_ms
36
38
 
37
39
  def tick(self):
38
- self.update(get_time_ms())
40
+ self.update(local_clock())
39
41
 
40
42
  @property
41
43
  def profile_percent(self):