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.
- docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
- docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +233 -0
- docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
- docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
- docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
- docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
- docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
- docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
- docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +239 -0
- docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
- docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
- docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
- docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
- docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
- docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +76 -0
- docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +97 -0
- docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +240 -0
- docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +233 -0
- docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +63 -0
- docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
- docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +210 -0
- docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +192 -0
- docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +219 -0
- docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +121 -0
- docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +68 -0
- docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
- docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +189 -0
- docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
- docs/source/auto_examples/plot_0_first_demo.py +189 -0
- docs/source/auto_examples/plot_1_example_BIDS.py +240 -0
- docs/source/auto_examples/plot_2_example_add_feature.py +76 -0
- docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +219 -0
- docs/source/auto_examples/plot_4_example_gridPointProjection.py +210 -0
- docs/source/auto_examples/plot_5_example_rmap_computing.py +64 -0
- docs/source/auto_examples/plot_6_real_time_demo.py +121 -0
- docs/source/conf.py +105 -0
- examples/plot_0_first_demo.py +189 -0
- examples/plot_1_example_BIDS.py +240 -0
- examples/plot_2_example_add_feature.py +76 -0
- examples/plot_3_example_sharpwave_analysis.py +219 -0
- examples/plot_4_example_gridPointProjection.py +210 -0
- examples/plot_5_example_rmap_computing.py +64 -0
- examples/plot_6_real_time_demo.py +121 -0
- packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +4 -0
- packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +104 -0
- packages/realtime_decoding/build/lib/realtime_decoding/features.py +163 -0
- packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +15 -0
- packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +345 -0
- packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +54 -0
- packages/tmsi/build/lib/TMSiFileFormats/__init__.py +37 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +36 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +200 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +496 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +236 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +977 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +35 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +116 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +294 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +229 -0
- packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +102 -0
- packages/tmsi/build/lib/TMSiPlotters/__init__.py +2 -0
- packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +39 -0
- packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +234 -0
- packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +440 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +44 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +446 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +589 -0
- packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +1326 -0
- packages/tmsi/build/lib/TMSiSDK/__init__.py +54 -0
- packages/tmsi/build/lib/TMSiSDK/device.py +588 -0
- packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +34 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +1764 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +34 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +1366 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +520 -0
- packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +165 -0
- packages/tmsi/build/lib/TMSiSDK/error.py +95 -0
- packages/tmsi/build/lib/TMSiSDK/sample_data.py +63 -0
- packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +99 -0
- packages/tmsi/build/lib/TMSiSDK/settings.py +45 -0
- packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +111 -0
- packages/tmsi/build/lib/__init__.py +4 -0
- packages/tmsi/build/lib/apex_sdk/__init__.py +34 -0
- packages/tmsi/build/lib/apex_sdk/device/__init__.py +41 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +1009 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +239 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +668 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +1611 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +38 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +57 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +44 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +150 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +36 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +48 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +108 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +39 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +77 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +150 -0
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +129 -0
- packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +59 -0
- packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +57 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +83 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +201 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +103 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +43 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +50 -0
- packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +118 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +33 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +44 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +50 -0
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +136 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +126 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +113 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +134 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +60 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +42 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +42 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +72 -0
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +98 -0
- py_neuromodulation/{helper.py → _write_example_dataset_helper.py} +1 -1
- py_neuromodulation/nm_EpochStream.py +2 -3
- py_neuromodulation/nm_IO.py +43 -70
- py_neuromodulation/nm_RMAP.py +308 -11
- py_neuromodulation/nm_analysis.py +1 -1
- py_neuromodulation/nm_artifacts.py +25 -0
- py_neuromodulation/nm_bispectra.py +64 -29
- py_neuromodulation/nm_bursts.py +44 -30
- py_neuromodulation/nm_coherence.py +2 -1
- py_neuromodulation/nm_features.py +4 -2
- py_neuromodulation/nm_filter.py +63 -32
- py_neuromodulation/nm_filter_preprocessing.py +91 -0
- py_neuromodulation/nm_fooof.py +47 -29
- py_neuromodulation/nm_mne_connectivity.py +1 -1
- py_neuromodulation/nm_normalization.py +50 -74
- py_neuromodulation/nm_oscillatory.py +151 -31
- py_neuromodulation/nm_plots.py +13 -10
- py_neuromodulation/nm_rereference.py +10 -8
- py_neuromodulation/nm_run_analysis.py +28 -13
- py_neuromodulation/nm_sharpwaves.py +103 -136
- py_neuromodulation/nm_stats.py +44 -30
- py_neuromodulation/nm_stream_abc.py +18 -10
- py_neuromodulation/nm_stream_offline.py +181 -40
- py_neuromodulation/utils/_logging.py +24 -0
- {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/METADATA +182 -142
- py_neuromodulation-0.0.3.dist-info/RECORD +188 -0
- {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/WHEEL +2 -1
- py_neuromodulation-0.0.3.dist-info/top_level.txt +5 -0
- tests/__init__.py +0 -0
- tests/conftest.py +117 -0
- tests/test_all_examples.py +10 -0
- tests/test_all_features.py +63 -0
- tests/test_bispectra.py +70 -0
- tests/test_bursts.py +105 -0
- tests/test_feature_sampling_rates.py +143 -0
- tests/test_fooof.py +16 -0
- tests/test_initalization_offline_stream.py +41 -0
- tests/test_multiprocessing.py +58 -0
- tests/test_nan_values.py +29 -0
- tests/test_nm_filter.py +95 -0
- tests/test_nm_resample.py +63 -0
- tests/test_normalization_settings.py +146 -0
- tests/test_notch_filter.py +31 -0
- tests/test_osc_features.py +424 -0
- tests/test_preprocessing_filter.py +151 -0
- tests/test_rereference.py +171 -0
- tests/test_sampling.py +57 -0
- tests/test_settings_change_after_init.py +76 -0
- tests/test_sharpwave.py +165 -0
- tests/test_target_channel_add.py +100 -0
- tests/test_timing.py +80 -0
- py_neuromodulation/data/README +0 -6
- py_neuromodulation/data/dataset_description.json +0 -8
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/MOV_aligned_features_ch_ECOG_RIGHT_0_all.png +0 -0
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/all_feature_plt.pdf +0 -0
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_FEATURES.csv +0 -182
- 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
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SETTINGS.json +0 -273
- py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SIDECAR.json +0 -6
- 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
- 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
- py_neuromodulation/data/participants.json +0 -32
- py_neuromodulation/data/participants.tsv +0 -2
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +0 -5
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +0 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +0 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +0 -18
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +0 -35
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +0 -13
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +0 -2
- py_neuromodulation/grid_cortex.tsv +0 -40
- py_neuromodulation/grid_subcortex.tsv +0 -1429
- py_neuromodulation/nm_settings.json +0 -290
- py_neuromodulation/plots/STN_surf.mat +0 -0
- py_neuromodulation/plots/Vertices.mat +0 -0
- py_neuromodulation/plots/faces.mat +0 -0
- py_neuromodulation/plots/grid.mat +0 -0
- py_neuromodulation/py_neuromodulation.egg-info/PKG-INFO +0 -104
- py_neuromodulation/py_neuromodulation.egg-info/dependency_links.txt +0 -1
- py_neuromodulation/py_neuromodulation.egg-info/requires.txt +0 -26
- py_neuromodulation/py_neuromodulation.egg-info/top_level.txt +0 -1
- py_neuromodulation-0.0.2.dist-info/RECORD +0 -73
- /py_neuromodulation/{py_neuromodulation.egg-info/SOURCES.txt → utils/__init__.py} +0 -0
- {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)))
|