py-neuromodulation 0.0.2__py3-none-any.whl → 0.0.3__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 (204) hide show
  1. docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
  2. docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +233 -0
  3. docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
  4. docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
  5. docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
  6. docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
  7. docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
  8. docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
  9. docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +239 -0
  10. docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
  11. docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
  12. docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
  13. docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
  14. docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
  15. docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +76 -0
  16. docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +97 -0
  17. docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +240 -0
  18. docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +233 -0
  19. docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +63 -0
  20. docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
  21. docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +210 -0
  22. docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +192 -0
  23. docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +219 -0
  24. docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +121 -0
  25. docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +68 -0
  26. docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
  27. docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +189 -0
  28. docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
  29. docs/source/auto_examples/plot_0_first_demo.py +189 -0
  30. docs/source/auto_examples/plot_1_example_BIDS.py +240 -0
  31. docs/source/auto_examples/plot_2_example_add_feature.py +76 -0
  32. docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +219 -0
  33. docs/source/auto_examples/plot_4_example_gridPointProjection.py +210 -0
  34. docs/source/auto_examples/plot_5_example_rmap_computing.py +64 -0
  35. docs/source/auto_examples/plot_6_real_time_demo.py +121 -0
  36. docs/source/conf.py +105 -0
  37. examples/plot_0_first_demo.py +189 -0
  38. examples/plot_1_example_BIDS.py +240 -0
  39. examples/plot_2_example_add_feature.py +76 -0
  40. examples/plot_3_example_sharpwave_analysis.py +219 -0
  41. examples/plot_4_example_gridPointProjection.py +210 -0
  42. examples/plot_5_example_rmap_computing.py +64 -0
  43. examples/plot_6_real_time_demo.py +121 -0
  44. packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +4 -0
  45. packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +104 -0
  46. packages/realtime_decoding/build/lib/realtime_decoding/features.py +163 -0
  47. packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +15 -0
  48. packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +345 -0
  49. packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +54 -0
  50. packages/tmsi/build/lib/TMSiFileFormats/__init__.py +37 -0
  51. packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +36 -0
  52. packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +200 -0
  53. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +496 -0
  54. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +236 -0
  55. packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +977 -0
  56. packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +35 -0
  57. packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +116 -0
  58. packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +294 -0
  59. packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +229 -0
  60. packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +102 -0
  61. packages/tmsi/build/lib/TMSiPlotters/__init__.py +2 -0
  62. packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +39 -0
  63. packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +234 -0
  64. packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +440 -0
  65. packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +44 -0
  66. packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +446 -0
  67. packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +589 -0
  68. packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +1326 -0
  69. packages/tmsi/build/lib/TMSiSDK/__init__.py +54 -0
  70. packages/tmsi/build/lib/TMSiSDK/device.py +588 -0
  71. packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +34 -0
  72. packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +1764 -0
  73. packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +34 -0
  74. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +1366 -0
  75. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +520 -0
  76. packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +165 -0
  77. packages/tmsi/build/lib/TMSiSDK/error.py +95 -0
  78. packages/tmsi/build/lib/TMSiSDK/sample_data.py +63 -0
  79. packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +99 -0
  80. packages/tmsi/build/lib/TMSiSDK/settings.py +45 -0
  81. packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +111 -0
  82. packages/tmsi/build/lib/__init__.py +4 -0
  83. packages/tmsi/build/lib/apex_sdk/__init__.py +34 -0
  84. packages/tmsi/build/lib/apex_sdk/device/__init__.py +41 -0
  85. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +1009 -0
  86. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +239 -0
  87. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +668 -0
  88. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +1611 -0
  89. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +38 -0
  90. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +57 -0
  91. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +44 -0
  92. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +150 -0
  93. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +36 -0
  94. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +48 -0
  95. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +108 -0
  96. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +39 -0
  97. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +77 -0
  98. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +150 -0
  99. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +129 -0
  100. packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +59 -0
  101. packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +57 -0
  102. packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +83 -0
  103. packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +201 -0
  104. packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +103 -0
  105. packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +43 -0
  106. packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +50 -0
  107. packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +118 -0
  108. packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +33 -0
  109. packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +44 -0
  110. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +50 -0
  111. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +136 -0
  112. packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +126 -0
  113. packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +113 -0
  114. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +134 -0
  115. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +60 -0
  116. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +42 -0
  117. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +42 -0
  118. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +72 -0
  119. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +98 -0
  120. py_neuromodulation/{helper.py → _write_example_dataset_helper.py} +1 -1
  121. py_neuromodulation/nm_EpochStream.py +2 -3
  122. py_neuromodulation/nm_IO.py +43 -70
  123. py_neuromodulation/nm_RMAP.py +308 -11
  124. py_neuromodulation/nm_analysis.py +1 -1
  125. py_neuromodulation/nm_artifacts.py +25 -0
  126. py_neuromodulation/nm_bispectra.py +64 -29
  127. py_neuromodulation/nm_bursts.py +44 -30
  128. py_neuromodulation/nm_coherence.py +2 -1
  129. py_neuromodulation/nm_features.py +4 -2
  130. py_neuromodulation/nm_filter.py +63 -32
  131. py_neuromodulation/nm_filter_preprocessing.py +91 -0
  132. py_neuromodulation/nm_fooof.py +47 -29
  133. py_neuromodulation/nm_mne_connectivity.py +1 -1
  134. py_neuromodulation/nm_normalization.py +50 -74
  135. py_neuromodulation/nm_oscillatory.py +151 -31
  136. py_neuromodulation/nm_plots.py +13 -10
  137. py_neuromodulation/nm_rereference.py +10 -8
  138. py_neuromodulation/nm_run_analysis.py +28 -13
  139. py_neuromodulation/nm_sharpwaves.py +103 -136
  140. py_neuromodulation/nm_stats.py +44 -30
  141. py_neuromodulation/nm_stream_abc.py +18 -10
  142. py_neuromodulation/nm_stream_offline.py +181 -40
  143. py_neuromodulation/utils/_logging.py +24 -0
  144. {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/METADATA +182 -142
  145. py_neuromodulation-0.0.3.dist-info/RECORD +188 -0
  146. {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/WHEEL +2 -1
  147. py_neuromodulation-0.0.3.dist-info/top_level.txt +5 -0
  148. tests/__init__.py +0 -0
  149. tests/conftest.py +117 -0
  150. tests/test_all_examples.py +10 -0
  151. tests/test_all_features.py +63 -0
  152. tests/test_bispectra.py +70 -0
  153. tests/test_bursts.py +105 -0
  154. tests/test_feature_sampling_rates.py +143 -0
  155. tests/test_fooof.py +16 -0
  156. tests/test_initalization_offline_stream.py +41 -0
  157. tests/test_multiprocessing.py +58 -0
  158. tests/test_nan_values.py +29 -0
  159. tests/test_nm_filter.py +95 -0
  160. tests/test_nm_resample.py +63 -0
  161. tests/test_normalization_settings.py +146 -0
  162. tests/test_notch_filter.py +31 -0
  163. tests/test_osc_features.py +424 -0
  164. tests/test_preprocessing_filter.py +151 -0
  165. tests/test_rereference.py +171 -0
  166. tests/test_sampling.py +57 -0
  167. tests/test_settings_change_after_init.py +76 -0
  168. tests/test_sharpwave.py +165 -0
  169. tests/test_target_channel_add.py +100 -0
  170. tests/test_timing.py +80 -0
  171. py_neuromodulation/data/README +0 -6
  172. py_neuromodulation/data/dataset_description.json +0 -8
  173. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/MOV_aligned_features_ch_ECOG_RIGHT_0_all.png +0 -0
  174. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/all_feature_plt.pdf +0 -0
  175. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_FEATURES.csv +0 -182
  176. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_LM_ML_RES.p +0 -0
  177. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SETTINGS.json +0 -273
  178. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SIDECAR.json +0 -6
  179. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_decoding_performance.png +0 -0
  180. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_nm_channels.csv +0 -11
  181. py_neuromodulation/data/participants.json +0 -32
  182. py_neuromodulation/data/participants.tsv +0 -2
  183. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +0 -5
  184. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +0 -11
  185. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +0 -11
  186. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
  187. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +0 -18
  188. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +0 -35
  189. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +0 -13
  190. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +0 -2
  191. py_neuromodulation/grid_cortex.tsv +0 -40
  192. py_neuromodulation/grid_subcortex.tsv +0 -1429
  193. py_neuromodulation/nm_settings.json +0 -290
  194. py_neuromodulation/plots/STN_surf.mat +0 -0
  195. py_neuromodulation/plots/Vertices.mat +0 -0
  196. py_neuromodulation/plots/faces.mat +0 -0
  197. py_neuromodulation/plots/grid.mat +0 -0
  198. py_neuromodulation/py_neuromodulation.egg-info/PKG-INFO +0 -104
  199. py_neuromodulation/py_neuromodulation.egg-info/dependency_links.txt +0 -1
  200. py_neuromodulation/py_neuromodulation.egg-info/requires.txt +0 -26
  201. py_neuromodulation/py_neuromodulation.egg-info/top_level.txt +0 -1
  202. py_neuromodulation-0.0.2.dist-info/RECORD +0 -73
  203. /py_neuromodulation/{py_neuromodulation.egg-info/SOURCES.txt → utils/__init__.py} +0 -0
  204. {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/LICENSE +0 -0
@@ -0,0 +1,1366 @@
1
+ """
2
+ (c) 2022 Twente Medical Systems International B.V., Oldenzaal The Netherlands
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+
16
+ ####### # # ##### #
17
+ # ## ## #
18
+ # # # # # # #
19
+ # # # # ##### #
20
+ # # # # #
21
+ # # # # #
22
+ # # # ##### #
23
+
24
+ /**
25
+ * @file ${saga_device.py}
26
+ * @brief SAGA Device Interface
27
+ *
28
+ */
29
+
30
+
31
+ """
32
+
33
+ import os
34
+
35
+ from .saga_types import *
36
+ from ...error import TMSiError, TMSiErrorCode
37
+ from ...device import (
38
+ Device,
39
+ DeviceChannel,
40
+ ChannelType,
41
+ MeasurementType,
42
+ DeviceInfo,
43
+ DeviceState,
44
+ DeviceStatus,
45
+ DeviceSensor,
46
+ DeviceInterfaceType,
47
+ )
48
+
49
+ if os.getenv("ENVIRONMENT") == "TEST":
50
+ # do not load the dll
51
+ print("Test environment recognized. Dll is not going to be loaded.")
52
+ else:
53
+ from .TMSi_Device_API import *
54
+
55
+ import array
56
+ from copy import copy, deepcopy
57
+ import datetime
58
+ import struct
59
+ import threading
60
+ import time
61
+ import queue
62
+ import warnings
63
+ import numpy as np
64
+
65
+ import tkinter as tk
66
+ from tkinter import messagebox
67
+
68
+ from ... import sample_data, sample_data_server
69
+
70
+ from .xml_saga_config import *
71
+
72
+ _tmsi_sdk = None
73
+ _device_info_list = []
74
+ _MAX_NUM_DEVICES = 2
75
+ _MAX_NUM_BATTERIES = 2
76
+
77
+
78
+ class SagaDevice(Device):
79
+ """'Device' handles the connection to a TMSi Measurement System like the SAGA.
80
+
81
+ The Device class interfaces with the measurement system to :
82
+ - open/close a connection to the system
83
+ - configure the system
84
+ - forward the received sample data to Python-clients for display and/or storage.
85
+
86
+ Args:
87
+ ds-interface: The interface-type between the PC and the docking-station.
88
+ This might be 'usb' or 'network''
89
+
90
+ dr-interface: The interface-type between the docking-station and data recorder.
91
+ This might be 'docked', 'optical' or 'wifi'
92
+ """
93
+
94
+ stream_stop = 0
95
+ stream_start = 1
96
+
97
+ def __init__(self, ds_interface, dr_interface, idx=-1):
98
+ if _tmsi_sdk == None:
99
+ initialize()
100
+ self._info = SagaInfo(ds_interface, dr_interface)
101
+ self._config = SagaConfig()
102
+ self._channels = [] # Active channel list
103
+ self._imp_channels = [] # Impedance channel list
104
+ self._id = _device_info_list[idx].id
105
+ self._device_handle = DeviceHandle(0) # TMSiDeviceHandle
106
+ self._idx_device_list_info = idx
107
+ self._last_error_code = TMSiDeviceRetVal.TMSI_OK
108
+ self._sampling_thread = None
109
+ self._measurement_type = MeasurementType.normal
110
+
111
+ @property
112
+ def id(self):
113
+ """'int' : Unique id within all available devices. The id can be used to
114
+ register as a client at the 'sample_data_server' for retrieval of
115
+ sample-data of a specific device
116
+ """
117
+ return self._id
118
+
119
+ @property
120
+ def info(self):
121
+ """'class DeviceInfo' : Static information of a device like used interfaces, serial numbers"""
122
+ dev_info = DeviceInfo(self._info.ds_interface, self._info.dr_interface)
123
+ return dev_info
124
+
125
+ @property
126
+ def status(self):
127
+ """'class DeviceStatus' : Runtime information of a device like device state"""
128
+ dev_status = DeviceStatus(self._info.state, self._last_error_code.value)
129
+ return dev_status
130
+
131
+ @property
132
+ def channels(self):
133
+ """'list of class DeviceChannel' : The list of enabled channels.
134
+ Enabled channels are active during an 'normal' measurement.
135
+ """
136
+ chan_list = []
137
+ for ch in self._config._channels:
138
+ if ch.enabled == True:
139
+ sensor = ch.sensor
140
+ if ch.sensor != None:
141
+ sensor = DeviceSensor(
142
+ ch.sensor.idx_total_channel_list,
143
+ ch.sensor.id,
144
+ ch.sensor.serial_nr,
145
+ ch.sensor.product_id,
146
+ ch.sensor.name,
147
+ ch.sensor.unit_name,
148
+ ch.sensor.exp,
149
+ )
150
+ dev_ch = DeviceChannel(
151
+ ch.type,
152
+ ch.sample_rate,
153
+ ch.alt_name,
154
+ ch.unit_name,
155
+ (ch.chan_divider != -1),
156
+ ch.bandwidth,
157
+ sensor,
158
+ )
159
+ chan_list.append(dev_ch)
160
+ return chan_list
161
+
162
+ @property
163
+ def imp_channels(self):
164
+ """'list of class DeviceChannel' : The list of impedance channels.
165
+ Impedance channels are active during an 'impedance' measurement.
166
+ """
167
+ imp_chan_list = []
168
+ for ch in self._imp_channels:
169
+ dev_ch = DeviceChannel(ch.type, 0, ch.alt_name, "kOhm", True)
170
+ imp_chan_list.append(dev_ch)
171
+ return imp_chan_list
172
+
173
+ @property
174
+ def sensors(self):
175
+ """'list of class DeviceSensor' : The complete list of sensor-information
176
+ for the sensor-type channels : BIP and AUX
177
+ """
178
+ sensor_list = []
179
+ for sensor in self._sensor_list:
180
+ dev_sensor = DeviceSensor(
181
+ sensor.idx_total_channel_list,
182
+ sensor.id,
183
+ sensor.serial_nr,
184
+ sensor.product_id,
185
+ sensor.name,
186
+ sensor.unit_name,
187
+ sensor.exp,
188
+ )
189
+ sensor_list.append(dev_sensor)
190
+ return sensor_list
191
+
192
+ @property
193
+ def config(self):
194
+ """'class DeviceConfigs' : The configuration of a device which exists
195
+ out of individual properties (like base-sample-rate) and the total
196
+ channel list (with enabled and disabled channels)
197
+ """
198
+ self._config._parent = self
199
+ return self._config
200
+
201
+ @property
202
+ def datetime(self):
203
+ """'datetime' Current date and time of the device"""
204
+ if self._info.state == DeviceState.disconnected:
205
+ raise TMSiError(TMSiErrorCode.device_not_connected)
206
+
207
+ dev_full_status_report = TMSiDevFullStatReport()
208
+ dev_bat_report = (TMSiDevBatReport * _MAX_NUM_BATTERIES)()
209
+ dev_time = TMSiTime()
210
+ dev_storage_report = TMSiDevStorageReport()
211
+
212
+ self._last_error_code = _tmsi_sdk.TMSiGetFullDeviceStatus(
213
+ self._device_handle,
214
+ pointer(dev_full_status_report),
215
+ pointer(dev_bat_report),
216
+ _MAX_NUM_BATTERIES,
217
+ pointer(dev_time),
218
+ pointer(dev_storage_report),
219
+ )
220
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
221
+ return datetime.datetime(
222
+ dev_time.Year + 1900,
223
+ dev_time.Month + 1,
224
+ dev_time.DayOfMonth,
225
+ dev_time.Hours,
226
+ dev_time.Minutes,
227
+ dev_time.Seconds,
228
+ )
229
+ else:
230
+ raise TMSiError(TMSiErrorCode.device_error)
231
+
232
+ @datetime.setter
233
+ def datetime(self, dt):
234
+ """'datetime' Sets date and time of the device"""
235
+ if self._info.state == DeviceState.disconnected:
236
+ raise TMSiError(TMSiErrorCode.device_not_connected)
237
+
238
+ dev_time = TMSiTime()
239
+ dev_time.Year = dt.year - 1900
240
+ dev_time.Month = dt.month - 1
241
+ dev_time.DayOfMonth = dt.day
242
+ dev_time.Hours = dt.hour
243
+ dev_time.Minutes = dt.minute
244
+ dev_time.Seconds = dt.second
245
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceRTC(
246
+ self._device_handle, pointer(dev_time)
247
+ )
248
+ if self._last_error_code != TMSiDeviceRetVal.TMSI_OK:
249
+ raise TMSiError(TMSiErrorCode.device_error)
250
+
251
+ def open(self):
252
+ """Opens the connection to the device.
253
+
254
+ A connection will be established with the available system.
255
+
256
+ The functionailty a device offers will only be available when a connection
257
+ to the system has been established.
258
+ """
259
+ if self._id != SagaConst.TMSI_DEVICE_ID_NONE:
260
+ # A device is found. Open the connection and adapt the information of the opened device
261
+ self._last_error_code = _tmsi_sdk.TMSiOpenDevice(
262
+ pointer(self._device_handle),
263
+ self._id,
264
+ self.info.dr_interface.value,
265
+ )
266
+ if (
267
+ self._last_error_code
268
+ == TMSiDeviceRetVal.TMSI_DS_DEVICE_ALREADY_OPEN
269
+ ):
270
+ # The found device is available but in it's open-state: Close and re-open the connection
271
+ self._last_error_code = _tmsi_sdk.TMSiCloseDevice(
272
+ self._device_handle
273
+ )
274
+ self._last_error_code = _tmsi_sdk.TMSiOpenDevice(
275
+ pointer(self._device_handle),
276
+ self._id,
277
+ self.info.dr_interface.value,
278
+ )
279
+
280
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
281
+ # The device is opened succesfully. Update the device information.
282
+ _device_info_list[
283
+ self._idx_device_list_info
284
+ ].state = DeviceState.connected
285
+
286
+ self._info.state = DeviceState.connected
287
+ self._info.id = self._id
288
+ self._info.ds_interface = _device_info_list[
289
+ self._idx_device_list_info
290
+ ].ds_interface
291
+ self._info.dr_interface = _device_info_list[
292
+ self._idx_device_list_info
293
+ ].dr_interface
294
+ self._info.ds_serial_number = _device_info_list[
295
+ self._idx_device_list_info
296
+ ].ds_serial_number
297
+ self._info.dr_serial_number = _device_info_list[
298
+ self._idx_device_list_info
299
+ ].dr_serial_number
300
+
301
+ # Read the device's configuration
302
+ self.__read_config_from_device()
303
+
304
+ _recordings_list = self.get_device_storage_list()
305
+ if _recordings_list:
306
+ warnings.warn(
307
+ "\n\n!!! \nThere is/are recordings stored on the onboard memory. Changing the configuration will clear the device's SD card!\nDo you want to continue? ('yes'/'y' continue, all others abort opening)\n!!!\n",
308
+ stacklevel=1,
309
+ )
310
+ abort = input("Continue?\n")
311
+
312
+ if abort.lower() == "yes" or abort.lower() == "y":
313
+ pass
314
+ else:
315
+ raise TMSiError(TMSiErrorCode.general_error)
316
+
317
+ if self.info.dr_interface == DeviceInterfaceType.wifi:
318
+ bandwidth = self.get_current_bandwidth()
319
+ if bandwidth > self.config._interface_bandwidth:
320
+ warnings.warn(
321
+ "\n\n!!! \nThe maximum Wi-Fi bandwidth is exceeded. Please change the device configuration by disabling channels or altering the sample rate(s).\n!!!\n",
322
+ stacklevel=1,
323
+ )
324
+
325
+ else:
326
+ raise TMSiError(TMSiErrorCode.device_error)
327
+ else:
328
+ raise TMSiError(TMSiErrorCode.no_devices_found)
329
+
330
+ def close(self):
331
+ """Closes the connection to the device."""
332
+ if self._info.state != DeviceState.disconnected:
333
+
334
+ self._last_error_code = _tmsi_sdk.TMSiCloseDevice(
335
+ self._device_handle
336
+ )
337
+ _device_info_list[
338
+ self._idx_device_list_info
339
+ ].state = DeviceState.disconnected
340
+ self._info.state = DeviceState.disconnected
341
+ else:
342
+ raise TMSiError(TMSiErrorCode.device_not_connected)
343
+
344
+ def start_measurement(self, measurement_type=MeasurementType.normal):
345
+ """Starts a measurement on the device.
346
+ Clients, which want to receive the sample-data of a measurement,
347
+ must be registered at the 'sample data server' before the measurement is started.
348
+
349
+ Args:
350
+ measurement_type : - MeasurementType.normal (default), starts a measurement
351
+ with the 'enabled' channels: 'Device.channels[]'.
352
+ - MeasurementType.impedance, starts an impedance-measurement
353
+ with all 'impedance' channels: 'Device.imp_channels[]'
354
+ """
355
+ # Only allow one measurement at the same time
356
+ if self._info.state == DeviceState.sampling:
357
+ raise TMSiError(TMSiErrorCode.api_invalid_command)
358
+ if self._info.state != DeviceState.connected:
359
+ raise TMSiError(TMSiErrorCode.device_not_connected)
360
+
361
+ if self.get_current_bandwidth() > self.config.interface_bandwidth:
362
+ print(
363
+ "\n\nA measurement could not be started.\nThe maximum Wi-Fi bandwidth is exceeded. Please change the device configuration by disabling channels or altering the sample rate(s).\n\n"
364
+ )
365
+ raise TMSiError(TMSiErrorCode.api_invalid_command)
366
+
367
+ self._measurement_type = measurement_type
368
+ _tmsi_sdk.TMSiResetDeviceDataBuffer(self._device_handle)
369
+
370
+ # Create and start the sampling-thread to capture and process incoming measurement-data,
371
+ # For a normal measurement sample_conversion must be applied
372
+ self._sampling_thread = _SamplingThread(
373
+ name="producer-" + str(self._device_handle.value)
374
+ )
375
+ if self._measurement_type == MeasurementType.normal:
376
+ self._sampling_thread.initialize(
377
+ self._device_handle, self._channels, True
378
+ )
379
+ else:
380
+ self._sampling_thread.initialize(
381
+ self._device_handle, self._imp_channels, False
382
+ )
383
+
384
+ self._conversion_thread = _ConversionThread(
385
+ sampling_thread=self._sampling_thread
386
+ )
387
+
388
+ self._sampling_thread.start()
389
+ self._conversion_thread.start()
390
+
391
+ # Request to start the measurement at the device
392
+ if self._measurement_type == MeasurementType.normal:
393
+ measurement_request = TMSiDevSampleReq()
394
+ measurement_request.SetSamplingMode = 1
395
+ measurement_request.DisableAvrRefCalc = 0
396
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceSampling(
397
+ self._device_handle, pointer(measurement_request)
398
+ )
399
+ else:
400
+ measurement_request = TMSiDevImpReq()
401
+ measurement_request.SetImpedanceMode = 1
402
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceImpedance(
403
+ self._device_handle, pointer(measurement_request)
404
+ )
405
+
406
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
407
+ self._info.state = DeviceState.sampling
408
+ else:
409
+ # Measurement could not be started. Stop the sampling-thread and report error.
410
+ self._sampling_thread.stop()
411
+ raise TMSiError(TMSiErrorCode.device_error)
412
+
413
+ def stop_measurement(self):
414
+ """Stops an ongoing measurement on the device."""
415
+ if self._info.state != DeviceState.sampling:
416
+ raise TMSiError(TMSiErrorCode.api_invalid_command)
417
+
418
+ if self._measurement_type == MeasurementType.normal:
419
+ measurement_request = TMSiDevSampleReq()
420
+ measurement_request.SetSamplingMode = 0
421
+ measurement_request.DisableAutoswitch = 0
422
+ measurement_request.DisableRepairLogging = 0
423
+ measurement_request.DisableAvrRefCalc = 0
424
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceSampling(
425
+ self._device_handle, pointer(measurement_request)
426
+ )
427
+ else:
428
+ measurement_request = TMSiDevImpReq()
429
+ measurement_request.SetImpedanceMode = 0
430
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceImpedance(
431
+ self._device_handle, pointer(measurement_request)
432
+ )
433
+
434
+ self._sampling_thread.stop()
435
+ self._conversion_thread.stop()
436
+
437
+ self._info.state = DeviceState.connected
438
+
439
+ def set_factory_defaults(self):
440
+ """Initiates a factory reset to restore the systems' default configuration."""
441
+
442
+ if self._info.state == DeviceState.disconnected:
443
+ raise TMSiError(TMSiErrorCode.device_not_connected)
444
+
445
+ dev_set_config = TMSiDevSetConfig()
446
+
447
+ dev_set_config.DRSerialNumber = 0
448
+ dev_set_config.NrOfChannels = 0
449
+ dev_set_config.SetBaseSampleRateHz = 0
450
+ dev_set_config.SetConfiguredInterface = 0
451
+ dev_set_config.SetTriggers = 0
452
+ dev_set_config.SetRefMethod = 0
453
+ dev_set_config.SetAutoRefMethod = 0
454
+ dev_set_config.SetDRSyncOutDiv = 0
455
+ dev_set_config.DRSyncOutDutyCycl = 0
456
+ dev_set_config.SetRepairLogging = 0
457
+ dev_set_config.StoreAsDefault = 0
458
+ dev_set_config.WebIfCtrl = 0
459
+
460
+ default_pin = bytearray("0000", "utf-8")
461
+ dev_set_config.PinKey[:] = default_pin[:]
462
+
463
+ dev_set_config.PerformFactoryReset = 1
464
+
465
+ dev_set_channel = TMSiDevSetChCfg()
466
+ dev_set_channel.ChanNr = 0
467
+ dev_set_channel.ChanDivider = -1
468
+ # dev_set_channel.AltChanName[0] = '\0';
469
+
470
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceConfig(
471
+ self._device_handle,
472
+ pointer(dev_set_config),
473
+ pointer(dev_set_channel),
474
+ 1,
475
+ )
476
+
477
+ # Align the internal administration with the new device's configuration
478
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
479
+ self.__read_config_from_device()
480
+ else:
481
+ raise TMSiError(TMSiErrorCode.device_error)
482
+
483
+ message = (
484
+ "Please repower the Data Recorder to activate the factory settings. \n\n"
485
+ "To do this: \n\n"
486
+ "1. Undock the Data Recorder from the Docking Station. \n"
487
+ "2. Remove the batteries from the Data Recorder.\n"
488
+ "3. Wait for 5 seconds. \n"
489
+ "4. Insert the batteries again. \n"
490
+ "5. Dock the Data Recorder onto the Docking Station. \n"
491
+ "6. Press the Power-button of the Data Recorder untill the LED-indicators start flashing.\n"
492
+ "7. The default settings are now activated."
493
+ )
494
+
495
+ root = tk.Tk()
496
+ root.withdraw()
497
+ messagebox.showwarning("Factory Reset", message)
498
+
499
+ def save_config(self, filename):
500
+ """Saves the current device configuration to file.
501
+
502
+ Args:
503
+ filename : path and filename of the file to which the current
504
+ device configuration must be saved.
505
+ """
506
+ result = xml_write_config(filename, self._config)
507
+ if result != True:
508
+ raise TMSiError(TMSiErrorCode.general_error)
509
+
510
+ def load_config(self, filename):
511
+ """Loads a device configuration from file into the attached system.
512
+
513
+ 1. The device configuration is read from the specified file.
514
+ 2. This configuration is uploaded into the attached system.
515
+ 3. The configuration is downloaded from the system to be sure that
516
+ the configuration of the Python-interface is in sync with the
517
+ configuration of the device.
518
+
519
+ Note : It is advised to "refresh" the applications' local "variables"
520
+ after a new device configuration has been load.
521
+
522
+ Args:
523
+ filename : path and filename of the file that must be loaded
524
+ """
525
+ if self._info.state == DeviceState.disconnected:
526
+ raise TMSiError(TMSiErrorCode.device_not_connected)
527
+
528
+ # The load-config exists out of the next steps:
529
+ # 1. Read the mutable device-configuration-settings from the xml-file
530
+ # 2. Write these settings to the Saga device
531
+ # 3. Read back the device configuration from the Saga-device. This to be sure
532
+ # that the configuration of the Saga-device and the Python-interface are in sync.
533
+ result, read_xml_config = xml_read_config(filename)
534
+ if result == True:
535
+ # The configuration has been successfully read, Now merge these configuration settings
536
+ # with the unmutable configuration settings of the device
537
+ # Do not overwrite configured interface
538
+ read_xml_config._configured_interface = (
539
+ self._config._configured_interface
540
+ )
541
+ self._config = read_xml_config
542
+ self._update_config()
543
+ else:
544
+ print("Can not load .xml config file")
545
+ raise TMSiError(TMSiErrorCode.general_error)
546
+
547
+ def update_sensors(self):
548
+ """Called when sensors have been attached or detached to/from the device.
549
+ The complete configuration including the new sensor-configuration
550
+ is reloaded from the device.
551
+
552
+ Note:
553
+ It is advised to "refresh" the applications' local "variables"
554
+ after the the complete configuration has been reloaded.
555
+ """
556
+ self.__read_config_from_device()
557
+
558
+ def download_recording_file(self, file_id, verbosity=True):
559
+ """download the file requested from the device
560
+
561
+ :param file_id: ID of the file wanted to be downloaded
562
+ :type file_id: string
563
+ :param verbosity: print the download percentage, defaults to True
564
+ :type verbosity: bool, optional
565
+ """
566
+ self.start_download_recording_file(file_id)
567
+
568
+ while True:
569
+ download_percentage = self.monitor_download_recording_file()
570
+ if verbosity:
571
+ print("\rProgress: % 0.1f %%" % (download_percentage), end="\r")
572
+ if download_percentage >= 100:
573
+ break
574
+ time.sleep(0.1)
575
+
576
+ self.stop_download_recording_file(file_id)
577
+
578
+ def start_download_recording_file(self, file_id):
579
+ """Starts the download stream from the device of the file requested
580
+
581
+ Args:
582
+ file_id : ID of the file wanted to be downloaded
583
+ """
584
+ # Only allow one measurement at the same time
585
+ if self._info.state == DeviceState.sampling:
586
+ raise TMSiError(TMSiErrorCode.api_invalid_command)
587
+ if self._info.state != DeviceState.connected:
588
+ raise TMSiError(TMSiErrorCode.device_not_connected)
589
+
590
+ _tmsi_sdk.TMSiResetDeviceDataBuffer(self._device_handle)
591
+
592
+ # Create and start the sampling-thread to capture and process incoming measurement-data,
593
+ # For a normal measurement sample_conversion must be applied
594
+ self._sampling_thread = _SamplingThread(
595
+ name="producer-" + str(self._device_handle.value)
596
+ )
597
+ self._sampling_thread.initialize(
598
+ self._device_handle, self._channels, True
599
+ )
600
+
601
+ self._conversion_thread = _ConversionThread(
602
+ sampling_thread=self._sampling_thread
603
+ )
604
+
605
+ # Request to stop the downstream so that we can read the details of the recording
606
+ recording_metadata = TMSiDevRecDetails()
607
+ impedance_report_list = (TMSiDevImpReport * 100)()
608
+ impedance_report_list_len = 100
609
+
610
+ self._last_error_code = _tmsi_sdk.TMSiGetRecordingFile(
611
+ self._device_handle,
612
+ file_id,
613
+ SagaDevice.stream_stop,
614
+ pointer(recording_metadata),
615
+ pointer(impedance_report_list),
616
+ impedance_report_list_len,
617
+ )
618
+
619
+ self._sampling_thread.download_samples_limit = (
620
+ recording_metadata.NoOfSamples
621
+ )
622
+ self._sampling_thread.downloaded_samples = 0
623
+ self._sampling_thread.download_percentage = 0
624
+
625
+ self._last_error_code = _tmsi_sdk.TMSiGetRecordingFile(
626
+ self._device_handle,
627
+ file_id,
628
+ SagaDevice.stream_start,
629
+ pointer(recording_metadata),
630
+ pointer(impedance_report_list),
631
+ impedance_report_list_len,
632
+ )
633
+
634
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK.value:
635
+ self._info.state = DeviceState.sampling
636
+ else:
637
+ # Measurement could not be started. Stop the sampling-thread and report error.
638
+ self._sampling_thread.stop()
639
+ raise TMSiError(TMSiErrorCode.device_error)
640
+
641
+ self._sampling_thread.start()
642
+ self._conversion_thread.start()
643
+
644
+ def monitor_download_recording_file(self):
645
+ return self._sampling_thread.download_percentage
646
+
647
+ def stop_download_recording_file(self, file_id):
648
+ """Stops the download stream from the device of the file requested
649
+
650
+ Args:
651
+ file_id : ID of the file wanted to be downloaded
652
+ """
653
+ if self._info.state != DeviceState.sampling:
654
+ raise TMSiError(TMSiErrorCode.api_invalid_command)
655
+
656
+ recording_metadata = TMSiDevRecDetails()
657
+ impedance_report_list = (TMSiDevImpReport * 100)()
658
+ impedance_report_list_len = 100
659
+
660
+ self._last_error_code = _tmsi_sdk.TMSiGetRecordingFile(
661
+ self._device_handle,
662
+ file_id,
663
+ SagaDevice.stream_stop,
664
+ pointer(recording_metadata),
665
+ pointer(impedance_report_list),
666
+ impedance_report_list_len,
667
+ )
668
+
669
+ self._sampling_thread.stop()
670
+ self._conversion_thread.stop()
671
+
672
+ self._info.state = DeviceState.connected
673
+
674
+ def get_device_memory_configuration(self):
675
+ return self._get_device_amb_config()
676
+
677
+ def set_device_recording_button(self, prefix_name=None):
678
+ """enable recording button to save on internal memory
679
+
680
+ :param prefix_name: prefix name for the files, defaults to None
681
+ :type prefix_name: string, optional
682
+ """
683
+ card_config_interface_bandwidth = 2 # MBit/s
684
+ # Check whether the bandwidth does not exceed 2 Mbit/s
685
+ if self.get_current_bandwidth() > card_config_interface_bandwidth:
686
+ print(
687
+ "\n\nButton start could not be enabled.\nThe maximum bandwidth (2Mbit/s) is exceeded. Please change the device configuration by disabling channels or altering the sample rate(s).\n\n"
688
+ )
689
+ raise TMSiError(TMSiErrorCode.api_invalid_command)
690
+
691
+ device_amb_conf = self.get_device_memory_configuration()
692
+ device_amb_conf.StartControl = 8
693
+ device_amb_conf.EndControl = 0
694
+ if prefix_name is not None:
695
+ max_len = len(prefix_name)
696
+ if max_len > 16:
697
+ max_len = 16
698
+ prefix_name = bytearray(prefix_name, "utf-8")
699
+ name = bytearray(16)
700
+ name[:max_len] = prefix_name[:max_len]
701
+ device_amb_conf.PrefixFileName[:] = name
702
+ self._last_error_code = self._set_device_amb_config(device_amb_conf)
703
+
704
+ def set_device_backup_logging(self, prefix_name=None):
705
+ """enable backup logging to save on internal memory every time a recording is performed
706
+
707
+ :param prefix_name: prefix name for the files, defaults to None
708
+ :type prefix_name: string, optional
709
+ """
710
+ device_amb_conf = self.get_device_memory_configuration()
711
+ device_amb_conf.StartControl = 16
712
+ device_amb_conf.EndControl = 0
713
+ if prefix_name is not None:
714
+ max_len = len(prefix_name)
715
+ if max_len > 16:
716
+ max_len = 16
717
+ prefix_name = bytearray(prefix_name, "utf-8")
718
+ name = bytearray(16)
719
+ name[:max_len] = prefix_name[:max_len]
720
+ device_amb_conf.PrefixFileName[:] = name
721
+ self._last_error_code = self._set_device_amb_config(device_amb_conf)
722
+
723
+ def set_device_backup_disabled(self):
724
+ """disable backup logging"""
725
+ device_amb_conf = self.get_device_memory_configuration()
726
+ device_amb_conf.StartControl = 0
727
+ self._last_error_code = self._set_device_amb_config(device_amb_conf)
728
+
729
+ def _get_device_amb_config(self):
730
+ """
731
+ Gets the configuration of the flash memory on the SAGA
732
+
733
+ :raise TMSiErrorCode.device_error: If the get configuration fails.
734
+ :return: The configuration structure.
735
+ :rtype: TMSiDevRecCfg
736
+
737
+ """
738
+ device_amb_cfg = TMSiDevRecCfg()
739
+ self._last_error_code = _tmsi_sdk.TMSiGetDeviceAmbConfig(
740
+ self._device_handle, pointer(device_amb_cfg)
741
+ )
742
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK.value:
743
+ return device_amb_cfg
744
+ else:
745
+ raise TMSiError(TMSiErrorCode.device_error)
746
+
747
+ def _set_device_amb_config(self, cfg):
748
+ """
749
+ Sets the configuration of the flash memory on the SAGA
750
+
751
+ :param cfg: configuration to send to the SAGA.
752
+ :type cfg: TMSiDevRecCfg
753
+ :raise TMSiErrorCode.device_error: If the set configuration fails.
754
+ :return: TMSiDeviceRetVal.TMSI_OK
755
+ :rtype: TMSiDeviceRetVal
756
+
757
+ """
758
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceAmbConfig(
759
+ self._device_handle, pointer(cfg)
760
+ )
761
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK.value:
762
+ return TMSiDeviceRetVal.TMSI_OK
763
+ else:
764
+ raise TMSiError(TMSiErrorCode.device_error)
765
+
766
+ def get_device_storage_list(self):
767
+ """
768
+ Returns the list of files currently available on the SAGA device
769
+
770
+ :raise TMSiErrorCode.device_error: If the get storage list fails.
771
+ :return: A list of available recording present on the Data Recorder.
772
+ :rtype: TMSiDevRecList
773
+
774
+ """
775
+ device_config = TMSiDevGetConfig()
776
+ device_channel_list = (TMSiDevChDesc * self._config.num_channels)()
777
+ self._last_error_code = _tmsi_sdk.TMSiGetDeviceConfig(
778
+ self._device_handle,
779
+ pointer(device_config),
780
+ pointer(device_channel_list),
781
+ self._config.num_channels,
782
+ )
783
+ if self._last_error_code != TMSiDeviceRetVal.TMSI_OK:
784
+ raise TMSiError(TMSiErrorCode.device_error)
785
+
786
+ available_recs = device_config.AvailableRecordings
787
+ recordings_list = (TMSiDevRecList * available_recs)()
788
+ recordings_len = c_ulong()
789
+ recordings_dict = {}
790
+ self._last_error_code = _tmsi_sdk.TMSiGetDeviceStorageList(
791
+ self._device_handle,
792
+ pointer(recordings_list),
793
+ available_recs,
794
+ pointer(recordings_len),
795
+ )
796
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
797
+ for recording in recordings_list:
798
+ recordings_dict[recording.RecFileID] = recording.RecFileName
799
+ return recordings_dict
800
+ else:
801
+ raise TMSiError(TMSiErrorCode.device_error)
802
+
803
+ def get_current_bandwidth(self):
804
+ """
805
+ Returns the bandwidth (in MBits/sec) in use with the current device
806
+ configuration
807
+ """
808
+
809
+ ch_bandwidth = 0
810
+ ch_bandwidth = sum(
811
+ [ch_bandwidth + ch.bandwidth for ch in self.channels]
812
+ )
813
+
814
+ # Get the used bandwidth in
815
+ used_bandwidth = (
816
+ 80 * self.config.get_sample_rate(ChannelType.all_types)
817
+ + ch_bandwidth
818
+ ) / 1e6
819
+ self._used_bandwidth = used_bandwidth
820
+
821
+ return used_bandwidth
822
+
823
+ def _update_config(self):
824
+ # Upload the configuration to the device and always download it again
825
+ # to certify that sdk-configuration and device-configuration keep in sync
826
+ self.__write_config_to_device()
827
+ self.__read_config_from_device()
828
+
829
+ def __read_config_from_device(self):
830
+ # Reset device configuration
831
+ self._channels = []
832
+ self._imp_channels = []
833
+ self._config._channels = []
834
+ for idx in range(0, len(self._config._sample_rates)):
835
+ self._config._sample_rates[idx].sample_rate = 0
836
+
837
+ # Retrieve configuration settings and the channel list
838
+ device_status_report = TMSiDevStatReport()
839
+ self._last_error_code = _tmsi_sdk.TMSiGetDeviceStatus(
840
+ self._device_handle, pointer(device_status_report)
841
+ )
842
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
843
+ self._config._num_channels = device_status_report.NrOfChannels
844
+
845
+ device_config = TMSiDevGetConfig()
846
+ device_channel_list = (TMSiDevChDesc * self._config.num_channels)()
847
+ self._last_error_code = _tmsi_sdk.TMSiGetDeviceConfig(
848
+ self._device_handle,
849
+ pointer(device_config),
850
+ pointer(device_channel_list),
851
+ self._config.num_channels,
852
+ )
853
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
854
+ self._config._base_sample_rate = device_config.BaseSampleRateHz
855
+ self._config._configured_interface = (
856
+ device_config.ConfiguredInterface
857
+ )
858
+ self._config._triggers = device_config.TriggersEnabled
859
+ self._config._reference_method = device_config.RefMethod
860
+ self._config._auto_reference_method = (
861
+ device_config.AutoRefMethod
862
+ )
863
+ self._config._dr_sync_out_divider = device_config.DRSyncOutDiv
864
+ self._config._dr_sync_out_duty_cycle = (
865
+ device_config.DRSyncOutDutyCycl
866
+ )
867
+ self._config._repair_logging = device_config.RepairLogging
868
+ self._config._num_sensors = device_config.NrOfSensors
869
+ self._config._interface_bandwidth = (
870
+ device_config.InterFaceBandWidth
871
+ )
872
+
873
+ for i in range(self._config.num_channels):
874
+ channel = SagaChannel()
875
+ channel.type = ChannelType(
876
+ device_channel_list[i].ChannelType
877
+ )
878
+ channel.format = device_channel_list[i].ChannelFormat
879
+ channel.chan_divider = device_channel_list[i].ChanDivider
880
+ channel.enabled = channel.chan_divider != -1
881
+ channel.sample_rate = device_config.BaseSampleRateHz
882
+ if device_channel_list[i].ChanDivider != -1:
883
+ for j in range(channel.chan_divider):
884
+ channel.sample_rate = int(channel.sample_rate / 2)
885
+ if (
886
+ self._config._sample_rates[
887
+ channel.type.value
888
+ ].sample_rate
889
+ == 0
890
+ ):
891
+ self._config._sample_rates[
892
+ channel.type.value
893
+ ].sample_rate = channel.sample_rate
894
+ self._config._sample_rates[
895
+ channel.type.value
896
+ ].chan_divider = channel.chan_divider
897
+ channel.imp_divider = device_channel_list[i].ImpDivider
898
+ channel.bandwidth = device_channel_list[i].ChannelBandWidth
899
+ channel.exp = device_channel_list[i].Exp
900
+ channel.unit_name = device_channel_list[i].UnitName.decode(
901
+ "windows-1252"
902
+ )
903
+ channel.def_name = device_channel_list[
904
+ i
905
+ ].DefChanName.decode("windows-1252")
906
+ channel.alt_name = device_channel_list[
907
+ i
908
+ ].AltChanName.decode("windows-1252")
909
+ self._config._channels.append(channel)
910
+ if channel.chan_divider != -1:
911
+ # Active channel
912
+ self._channels.append(channel)
913
+ if channel.imp_divider != -1:
914
+ # Impedance channel
915
+ self._imp_channels.append(channel)
916
+
917
+ # The systems' sample rate is equal to the sample rate of the COUNTER-channel,
918
+ # which is always the last channel in the active channel list
919
+ self._config._sample_rate = self._channels[
920
+ len(self._channels) - 1
921
+ ].sample_rate
922
+
923
+ # Read the sensor data, parse the sensor-metadata and when appropriate
924
+ # attach a SagaSensor-object to the SagaChannel.
925
+ device_sensor_list = (
926
+ TMSiDevGetSens * self._config._num_sensors
927
+ )()
928
+ sensor_list_len = c_ulong()
929
+ self._last_error_code = _tmsi_sdk.TMSiGetDeviceSensor(
930
+ self._device_handle,
931
+ pointer(device_sensor_list),
932
+ self._config._num_sensors,
933
+ pointer(sensor_list_len),
934
+ )
935
+ if self._last_error_code == TMSiDeviceRetVal.TMSI_OK:
936
+ self._update_sensor_info(
937
+ device_sensor_list, sensor_list_len
938
+ )
939
+ else:
940
+ # Failure TMSiGetDeviceSensor()
941
+ raise TMSiError(TMSiErrorCode.device_error)
942
+
943
+ else:
944
+ # Failure TMSiGetDeviceConfig()
945
+ raise TMSiError(TMSiErrorCode.device_error)
946
+
947
+ else:
948
+ # Failure TMSiGetDeviceStatus()
949
+ raise TMSiError(TMSiErrorCode.device_error)
950
+
951
+ def __write_config_to_device(self):
952
+ # Upload the current sdk-configuration to the device, mark it also
953
+ # as the new default configuration
954
+ dev_set_config = TMSiDevSetConfig()
955
+
956
+ dev_set_config.DRSerialNumber = self._info.dr_serial_number
957
+ dev_set_config.NrOfChannels = self._config._num_channels
958
+ dev_set_config.SetBaseSampleRateHz = self._config._base_sample_rate
959
+ dev_set_config.SetConfiguredInterface = (
960
+ self._config._configured_interface
961
+ )
962
+ dev_set_config.SetTriggers = self._config._triggers
963
+ dev_set_config.SetRefMethod = self._config._reference_method
964
+ dev_set_config.SetAutoRefMethod = self._config._auto_reference_method
965
+ dev_set_config.SetDRSyncOutDiv = self._config._dr_sync_out_divider
966
+ dev_set_config.DRSyncOutDutyCycl = self._config._dr_sync_out_duty_cycle
967
+ dev_set_config.SetRepairLogging = self._config._repair_logging
968
+ dev_set_config.StoreAsDefault = (
969
+ 1 # Store always as default configuration
970
+ )
971
+ dev_set_config.WebIfCtrl = 0
972
+
973
+ default_pin = bytearray("0000", "utf-8")
974
+ dev_set_config.PinKey[:] = default_pin[:]
975
+
976
+ dev_set_config.PerformFactoryReset = 0
977
+
978
+ dev_channel_list = (TMSiDevSetChCfg * self._config._num_channels)()
979
+ for idx, saga_channel in enumerate(self._config._channels):
980
+
981
+ dev_channel_list[idx].ChanNr = idx
982
+ dev_channel_list[idx].ChanDivider = saga_channel.chan_divider
983
+ max_len = len(saga_channel.alt_name)
984
+ name = bytearray(saga_channel.alt_name, "utf-8")
985
+ dev_channel_list[idx].AltChanName[:max_len] = name[:max_len]
986
+
987
+ self._last_error_code = _tmsi_sdk.TMSiSetDeviceConfig(
988
+ self._device_handle,
989
+ pointer(dev_set_config),
990
+ pointer(dev_channel_list),
991
+ self._config.num_channels,
992
+ )
993
+ if self._last_error_code != TMSiDeviceRetVal.TMSI_OK:
994
+ # Failure TMSiSetDeviceConfig()
995
+ raise TMSiError(TMSiErrorCode.device_error)
996
+
997
+ def _update_sensor_info(self, device_sensor_list, sensor_list_len):
998
+ # 1. Update the actual sensor list
999
+
1000
+ # Reset the current list
1001
+ self._sensor_list = []
1002
+ for i in range(sensor_list_len.value):
1003
+ sensor = SagaSensor()
1004
+
1005
+ sensor.idx_total_channel_list = device_sensor_list[i].ChanNr
1006
+ sensor.id = device_sensor_list[i].SensorID
1007
+ sensor.IOMode = device_sensor_list[i].IOMode
1008
+
1009
+ # 2. Parse the sensor metadata
1010
+ idx = 0
1011
+ (
1012
+ manufacturer_id,
1013
+ serial_nr,
1014
+ product_id,
1015
+ channel_count,
1016
+ additional_structs,
1017
+ ) = struct.unpack_from(
1018
+ "<HIQBB", device_sensor_list[i].SensorMetaData, idx
1019
+ )
1020
+ if channel_count > 0:
1021
+ if (
1022
+ self._config._channels[sensor.idx_total_channel_list].type
1023
+ == ChannelType.AUX
1024
+ ):
1025
+ # It concerns an AUX-channel-group: AUX-1, AUX-2 or AUX-3
1026
+ sensor.manufacturer_id = manufacturer_id
1027
+ sensor.serial_nr = serial_nr
1028
+ sensor.product_id = product_id
1029
+ idx += 16
1030
+ for j in range(channel_count):
1031
+ struct_id = struct.unpack_from(
1032
+ "<H", device_sensor_list[i].SensorMetaData, idx
1033
+ )
1034
+ # Parse the data if it concerns a 'SensorDefaultChannel'
1035
+ if struct_id[0] == 0x0000:
1036
+ idx += 2
1037
+ (
1038
+ chan_name,
1039
+ unit_name,
1040
+ exp,
1041
+ gain,
1042
+ offset,
1043
+ ) = struct.unpack_from(
1044
+ "<10s10shff",
1045
+ device_sensor_list[i].SensorMetaData,
1046
+ idx,
1047
+ )
1048
+
1049
+ sensor.name = chan_name
1050
+ sensor.unit_name = unit_name
1051
+ sensor.exp = exp
1052
+ sensor.gain = gain
1053
+ sensor.offset = offset
1054
+
1055
+ # append sensor-into to the device-sensor-list and ...
1056
+ self._sensor_list.append(copy(sensor))
1057
+ # attach a copy of the sensor-object also to the specified channel
1058
+ self._config._channels[
1059
+ sensor.idx_total_channel_list
1060
+ ].sensor = copy(sensor)
1061
+ # Overrule the channel's alt_name with the name of the sensor-channel
1062
+ self._config._channels[
1063
+ sensor.idx_total_channel_list
1064
+ ].alt_name = sensor.name
1065
+ # Overrule the channel's unit_name with the uint name of the sensor-channel
1066
+ self._config._channels[
1067
+ sensor.idx_total_channel_list
1068
+ ].unit_name = sensor.unit_name
1069
+
1070
+ # Prepare for next sensor-channel
1071
+ sensor.idx_total_channel_list += 1
1072
+ idx += 30
1073
+ else:
1074
+ # ran into an 'empty struct', no more sensor info expected
1075
+ break
1076
+ else:
1077
+ # Always add the sensor-data to the device-sensor-list
1078
+ self._sensor_list.append(copy(sensor))
1079
+ # Add sensor-object to both BIP-channels when a sensor is detected on a BIP-channel
1080
+ if (
1081
+ self._config._channels[sensor.idx_total_channel_list].type
1082
+ == ChannelType.BIP
1083
+ ) and (sensor.id != -1):
1084
+ self._config._channels[
1085
+ sensor.idx_total_channel_list
1086
+ ].sensor = copy(sensor)
1087
+ sensor.idx_total_channel_list += 1
1088
+ self._sensor_list.append(copy(sensor))
1089
+ self._config._channels[
1090
+ sensor.idx_total_channel_list + 1
1091
+ ].sensor = copy(sensor)
1092
+
1093
+
1094
+ def initialize():
1095
+ """Initialize the interface-environment."""
1096
+
1097
+ # - Load the device-interface-library
1098
+ # - Create local list of 2 devices.
1099
+ try:
1100
+ global _tmsi_sdk
1101
+ global _device_info_list
1102
+ _tmsi_sdk = SagaSDK
1103
+ print(_tmsi_sdk)
1104
+ for i in range(_MAX_NUM_DEVICES):
1105
+ _device_info_list.append(SagaInfo())
1106
+ except:
1107
+ _tmsi_sdk = None
1108
+ raise TMSiError(TMSiErrorCode.api_no_driver)
1109
+
1110
+
1111
+ def _discover(ds_interface, dr_interface):
1112
+ # 1. Executes a discovery on devices based on the given interfaces for DS and DR
1113
+ # 2. Updates the local device-list with the result
1114
+ device_list = (TMSiDevList * _MAX_NUM_DEVICES)()
1115
+
1116
+ if dr_interface == DeviceInterfaceType.wifi:
1117
+ _num_retries = 10
1118
+ else:
1119
+ _num_retries = 5
1120
+ for i in range(_MAX_NUM_DEVICES):
1121
+ device_list[i].TMSiDeviceID = SagaConst.TMSI_DEVICE_ID_NONE
1122
+
1123
+ while _num_retries > 0:
1124
+ ret = _tmsi_sdk.TMSiGetDeviceList(
1125
+ pointer(device_list),
1126
+ _MAX_NUM_DEVICES,
1127
+ ds_interface.value,
1128
+ dr_interface.value,
1129
+ )
1130
+
1131
+ if ret == TMSiDeviceRetVal.TMSI_OK:
1132
+ # Devices are found, update the local device list with the found result
1133
+ for i in range(_MAX_NUM_DEVICES):
1134
+ if device_list[i].TMSiDeviceID != SagaConst.TMSI_DEVICE_ID_NONE:
1135
+ for ii in range(_MAX_NUM_DEVICES):
1136
+ if (
1137
+ _device_info_list[ii].id
1138
+ == SagaConst.TMSI_DEVICE_ID_NONE
1139
+ ):
1140
+ _device_info_list[ii].id = device_list[
1141
+ i
1142
+ ].TMSiDeviceID
1143
+ _device_info_list[ii].ds_interface = ds_interface
1144
+ _device_info_list[ii].dr_interface = dr_interface
1145
+ _device_info_list[
1146
+ ii
1147
+ ].ds_serial_number = device_list[i].DSSerialNr
1148
+ _device_info_list[
1149
+ ii
1150
+ ].dr_serial_number = device_list[i].DRSerialNr
1151
+ _device_info_list[
1152
+ ii
1153
+ ].state = DeviceState.disconnected
1154
+
1155
+ _num_retries = 0
1156
+ break
1157
+ _num_retries -= 1
1158
+ else:
1159
+ _num_retries -= 1
1160
+ print(
1161
+ "Trying to open connection to device. Number of retries left: "
1162
+ + str(_num_retries)
1163
+ )
1164
+ time.sleep(0.5)
1165
+ if _num_retries == 0:
1166
+ raise TMSiError(TMSiErrorCode.no_devices_found)
1167
+
1168
+
1169
+ def discover(ds_interface, dr_interface):
1170
+ if not SagaDllAvailable:
1171
+ print("SAGA DLL not available.")
1172
+ raise TMSiError(code=TMSiErrorCode.missing_dll)
1173
+ if SagaDllLocked:
1174
+ print("SAGA DLL already in use.")
1175
+ raise TMSiError(code=TMSiErrorCode.already_in_use_dll)
1176
+ discoveryList = []
1177
+
1178
+ if _tmsi_sdk == None:
1179
+ initialize()
1180
+
1181
+ _discover(ds_interface, dr_interface)
1182
+ for idx in range(_MAX_NUM_DEVICES):
1183
+ if _device_info_list[idx].id != SagaConst.TMSI_DEVICE_ID_NONE:
1184
+ discoveryList.append(SagaDevice(ds_interface, dr_interface, idx))
1185
+
1186
+ return discoveryList
1187
+
1188
+
1189
+ class _SamplingThread(threading.Thread):
1190
+ def __init__(self, name):
1191
+ super(_SamplingThread, self).__init__()
1192
+ self.name = name
1193
+ self._device_handle = None
1194
+ self.sample_data_buffer_size = 409600
1195
+ self.sample_data_buffer = (c_float * self.sample_data_buffer_size)(0)
1196
+ self.retrieved_sample_sets = (c_uint)(0)
1197
+ self.retrieved_data_type = (c_int)(0)
1198
+ self.num_samples_per_set = 0
1199
+ self.channels = []
1200
+ self.sample_conversion = False
1201
+
1202
+ def run(self):
1203
+ print(self.name, " started")
1204
+ self.sampling = True
1205
+
1206
+ while self.sampling:
1207
+ ret = _tmsi_sdk.TMSiGetDeviceData(
1208
+ self._device_handle,
1209
+ pointer(self.sample_data_buffer),
1210
+ self.sample_data_buffer_size,
1211
+ pointer(self.retrieved_sample_sets),
1212
+ pointer(self.retrieved_data_type),
1213
+ )
1214
+ if ret == TMSiDeviceRetVal.TMSI_OK:
1215
+ if self.retrieved_sample_sets.value > 0:
1216
+ self.conversion_queue.put(
1217
+ (
1218
+ deepcopy(self.sample_data_buffer),
1219
+ self.retrieved_sample_sets.value,
1220
+ )
1221
+ )
1222
+
1223
+ if hasattr(self, "download_samples_limit"):
1224
+ self.downloaded_samples += self.retrieved_sample_sets.value
1225
+ # print("added {} downloaded {} expected {}".format(
1226
+ # self.retrieved_sample_sets.value,
1227
+ # self.downloaded_samples,
1228
+ # self.download_samples_limit))
1229
+ self.download_percentage = (
1230
+ self.downloaded_samples
1231
+ * 100.0
1232
+ / self.download_samples_limit
1233
+ )
1234
+ if self.downloaded_samples >= self.download_samples_limit:
1235
+ break
1236
+ time.sleep(0.050)
1237
+
1238
+ print(self.name, " ready")
1239
+
1240
+ def initialize(self, device_handle, channels, sample_conversion):
1241
+ self._device_handle = device_handle
1242
+ self.channels = channels
1243
+ self.num_samples_per_set = len(channels)
1244
+ self.sample_conversion = sample_conversion
1245
+
1246
+ _MAX_SIZE_CONVERSION_QUEUE = 50
1247
+ self.conversion_queue = queue.Queue(_MAX_SIZE_CONVERSION_QUEUE)
1248
+
1249
+ def stop(self):
1250
+ print(self.name, " stop sampling")
1251
+ self.sampling = False
1252
+
1253
+
1254
+ class _ConversionThread(threading.Thread):
1255
+ def __init__(self, sampling_thread):
1256
+ super(_ConversionThread, self).__init__()
1257
+ self.channels = sampling_thread.channels
1258
+ self._device_handle = sampling_thread._device_handle
1259
+ self.q = sampling_thread.conversion_queue
1260
+
1261
+ self.num_samples_per_set = sampling_thread.num_samples_per_set
1262
+ self.sample_conversion = sampling_thread.sample_conversion
1263
+ if self.sample_conversion:
1264
+ self._warn_message = True
1265
+ else:
1266
+ self._warn_message = False
1267
+
1268
+ # sort channels per channel type
1269
+ self._float_chan = []
1270
+ self._sensor_chan = []
1271
+ self._basic_conversion = {}
1272
+ for j in range(len(self.channels)):
1273
+ if self.channels[j].format == 0x0020:
1274
+ self._float_chan.append(j)
1275
+ else:
1276
+ if self.sample_conversion:
1277
+ if (self.channels[j].type == ChannelType.AUX) and (
1278
+ self.channels[j].sensor != None
1279
+ ):
1280
+ self._sensor_chan.append(j)
1281
+ else:
1282
+ conversion_factor = 10 ** self.channels[j].exp
1283
+ if not conversion_factor in self._basic_conversion:
1284
+ self._basic_conversion[conversion_factor] = [j]
1285
+ else:
1286
+ self._basic_conversion[conversion_factor].append(j)
1287
+
1288
+ def run(self):
1289
+ self.sampling = True
1290
+
1291
+ if self._warn_message:
1292
+ # Initialise a sample counter that keeps track of whether samples might be lost
1293
+ last_counter = 0
1294
+
1295
+ while (self.sampling) or (not self.q.empty()):
1296
+ while not self.q.empty():
1297
+
1298
+ sample_data_buffer, retrieved_sample_sets = self.q.get()
1299
+
1300
+ if retrieved_sample_sets > 0:
1301
+ # reshape data to matrix
1302
+ sample_mat = np.reshape(
1303
+ sample_data_buffer[
1304
+ : self.num_samples_per_set * retrieved_sample_sets
1305
+ ],
1306
+ (self.num_samples_per_set, retrieved_sample_sets),
1307
+ order="F",
1308
+ )
1309
+ if self.sample_conversion:
1310
+ # basic unit conversion
1311
+ for conversion_factor in self._basic_conversion.keys():
1312
+ sample_mat[
1313
+ self._basic_conversion[conversion_factor]
1314
+ ] = (
1315
+ sample_mat[
1316
+ self._basic_conversion[conversion_factor]
1317
+ ]
1318
+ / conversion_factor
1319
+ )
1320
+ # sensor data conversion
1321
+ for j in self._sensor_chan:
1322
+ x = sample_mat[j]
1323
+ sensor = self.channels[j].sensor
1324
+ x = ((x + sensor.offset) * sensor.gain) / (
1325
+ 10**sensor.exp
1326
+ )
1327
+ sample_mat[j] = x
1328
+
1329
+ # conversion of float channels
1330
+ for j in self._float_chan:
1331
+ sample_mat[j] = float_to_uint(sample_mat[j])
1332
+
1333
+ # Check sample counter integrity
1334
+ if self._warn_message:
1335
+ counterstep = np.diff(sample_mat[-1])
1336
+ if not np.all(counterstep == 1) or not (
1337
+ (sample_mat[-1, 0] - last_counter) == 1
1338
+ ):
1339
+ warnings.warn(
1340
+ "\n\n!!! \nSomething is wrong, samples might be lost..\n!!!\n",
1341
+ stacklevel=1,
1342
+ )
1343
+ self._warn_message = False
1344
+ last_counter = copy(sample_mat[-1, -1])
1345
+
1346
+ samples = sample_mat.flatten("F")
1347
+ samples = samples.tolist()
1348
+
1349
+ sd = sample_data.SampleData(
1350
+ retrieved_sample_sets, self.num_samples_per_set, samples
1351
+ )
1352
+ sample_data_server.putSampleData(
1353
+ self._device_handle.value, sd
1354
+ )
1355
+
1356
+ time.sleep(0.010)
1357
+
1358
+ def stop(self):
1359
+ self.sampling = False
1360
+
1361
+
1362
+ def float_to_uint(f):
1363
+ fmt_pack = "f" * len(f)
1364
+ fmt_unpack = "I" * len(f)
1365
+ pack_struct = struct.Struct(fmt_pack)
1366
+ return struct.unpack(fmt_unpack, pack_struct.pack(*list(f)))