explorepy 4.3.1__tar.gz → 4.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {explorepy-4.3.1 → explorepy-4.5.1}/CHANGELOG.rst +9 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/PKG-INFO +25 -20
- {explorepy-4.3.1 → explorepy-4.5.1}/README.rst +18 -13
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/conf.py +1 -1
- {explorepy-4.3.1 → explorepy-4.5.1}/pyproject.toml +43 -7
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/BLEClient.py +17 -1
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/__init__.py +2 -2
- explorepy-4.5.1/src/explorepy/__pycache__/packet.Packet.int24to32-125.py312.1.nbc +0 -0
- explorepy-4.5.1/src/explorepy/__pycache__/packet.Packet.int24to32-125.py312.nbi +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/_exceptions.py +17 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/command.py +45 -1
- explorepy-4.5.1/src/explorepy/csv_client.py +219 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/explore.py +27 -11
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/filters.py +13 -1
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/packet.py +94 -30
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/parser.py +26 -3
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/settings_manager.py +27 -44
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/stream_processor.py +40 -14
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/tools.py +71 -43
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy.egg-info/PKG-INFO +25 -20
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy.egg-info/SOURCES.txt +3 -1
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy.egg-info/requires.txt +5 -5
- explorepy-4.3.1/.bumpversion.cfg +0 -26
- {explorepy-4.3.1 → explorepy-4.5.1}/.cookiecutterrc +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/.coveragerc +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/.editorconfig +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/AUTHORS.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/CONTRIBUTING.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/LICENSE +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/MANIFEST.in +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/authors.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/changelog.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/contributing.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/explore_legacy_devices.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/index.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/installation.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/logo.jpg +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/readme.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/reference/explorepy.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/reference/index.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/requirements.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/spelling_wordlist.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/docs/usage.rst +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/setup.cfg +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/BTClient.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/bt_mock_client.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/bt_mock_server.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/cli.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/debug.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/log_config.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy/serial_client.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy.egg-info/dependency_links.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy.egg-info/entry_points.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/src/explorepy.egg-info/top_level.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/README.md +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/__init__.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/conftest.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/integration_test_ble.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/calibration_info +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/calibration_info_usbc +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/cmd_rcv +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/cmd_stat +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/device_info +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/device_info_ble +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/device_info_v2 +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/device_info_v2_2 +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/disconnect +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/eeg16_ble +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/eeg32 +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/eeg94 +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/eeg98 +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/eeg98_ble +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/eeg98_usbc +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/eeg98_usbc_2 +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/env +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/orn +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/orn_2 +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/orn_matrix.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/push_marker +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/in/trigger_in +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/axis_and_angle.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/calibration_info_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/calibration_info_usbc_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/cmd_rcv_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/cmd_stat_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/device_info_ble_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/device_info_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/device_info_v2_2_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/device_info_v2_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/disconnect_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg16_ble_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg32_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg94_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg98_ble_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg98_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg98_out_fake.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg98_usbc_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/eeg98_usbc_out_2.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/env_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/orn_2_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/orn_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/push_marker_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/res/out/trigger_in_out.txt +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tests/test_packet.py +0 -0
- {explorepy-4.3.1 → explorepy-4.5.1}/tox.ini +0 -0
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
|
+
4.5.1 (11.6.2025)
|
|
5
|
+
------------------
|
|
6
|
+
* Hotfix LSL metadata
|
|
7
|
+
|
|
8
|
+
4.5.0 (5.6.2025)
|
|
9
|
+
------------------
|
|
10
|
+
* Live impedance
|
|
11
|
+
* Allow pushing preprocessed data to lab streaming layer
|
|
12
|
+
* Support new FW version
|
|
4
13
|
|
|
5
14
|
4.3.1 (17.9.2025)
|
|
6
15
|
------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: explorepy
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.1
|
|
4
4
|
Author-email: MentaLab Hub <support@mentab.org>
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/Mentalab-hub/explorepy
|
|
@@ -18,14 +18,14 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
18
18
|
Classifier: Operating System :: POSIX :: Linux
|
|
19
19
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
-
Description-Content-Type: text/
|
|
21
|
+
Description-Content-Type: text/x-rst
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
License-File: AUTHORS.rst
|
|
24
|
-
Requires-Dist: numpy
|
|
25
|
-
Requires-Dist: scipy
|
|
26
|
-
Requires-Dist: pyEDFlib==0.1.
|
|
24
|
+
Requires-Dist: numpy==2.1.3
|
|
25
|
+
Requires-Dist: scipy==1.17.1
|
|
26
|
+
Requires-Dist: pyEDFlib==0.1.42
|
|
27
27
|
Requires-Dist: click==7.1.2
|
|
28
|
-
Requires-Dist: appdirs==1.4.
|
|
28
|
+
Requires-Dist: appdirs==1.4.4
|
|
29
29
|
Requires-Dist: sentry_sdk==2.8.0
|
|
30
30
|
Requires-Dist: mne
|
|
31
31
|
Requires-Dist: eeglabio
|
|
@@ -33,7 +33,7 @@ Requires-Dist: pandas
|
|
|
33
33
|
Requires-Dist: pyserial
|
|
34
34
|
Requires-Dist: pyyaml
|
|
35
35
|
Requires-Dist: bleak==0.22.3
|
|
36
|
-
Requires-Dist: pylsl
|
|
36
|
+
Requires-Dist: pylsl==1.18.2
|
|
37
37
|
Requires-Dist: numba
|
|
38
38
|
Provides-Extra: test
|
|
39
39
|
Requires-Dist: pytest==6.2.5; extra == "test"
|
|
@@ -44,9 +44,8 @@ Requires-Dist: isort==5.10.1; extra == "test"
|
|
|
44
44
|
Dynamic: license-file
|
|
45
45
|
|
|
46
46
|
.. image:: https://raw.githubusercontent.com/Mentalab-hub/explorepy/master/docs/logo.jpg
|
|
47
|
-
:
|
|
48
|
-
:
|
|
49
|
-
|
|
47
|
+
:alt: Explorepy
|
|
48
|
+
:target: https://github.com/Mentalab-hub/explorepy
|
|
50
49
|
|
|
51
50
|
.. start-badges
|
|
52
51
|
|
|
@@ -62,9 +61,9 @@ Dynamic: license-file
|
|
|
62
61
|
:target: https://pypi.org/project/explorepy
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.
|
|
64
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.5.1.svg
|
|
66
65
|
:alt: Commits since latest release
|
|
67
|
-
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.
|
|
66
|
+
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.5.1...master
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
.. |wheel| image:: https://img.shields.io/pypi/wheel/explorepy.svg
|
|
@@ -84,10 +83,10 @@ Dynamic: license-file
|
|
|
84
83
|
|
|
85
84
|
|
|
86
85
|
=========================
|
|
87
|
-
|
|
86
|
+
ExplorePy overview
|
|
88
87
|
=========================
|
|
89
88
|
|
|
90
|
-
|
|
89
|
+
ExplorePy is an open-source Python API designed to collect and process ExG data using Mentalab's Explore device. Amongst other things, ExplorePy provides the following features:
|
|
91
90
|
|
|
92
91
|
* Real-time streaming of ExG, orientation and environmental data.
|
|
93
92
|
* Data recording in CSV and BDF+ formats.
|
|
@@ -106,7 +105,7 @@ Requirements
|
|
|
106
105
|
|
|
107
106
|
Detailed installation instructions can be found on the `installation page <https://explorepy.readthedocs.io/en/latest/installation.html>`_.
|
|
108
107
|
|
|
109
|
-
To install
|
|
108
|
+
To install ExplorePy from PyPI run:
|
|
110
109
|
::
|
|
111
110
|
|
|
112
111
|
pip install explorepy
|
|
@@ -123,12 +122,14 @@ Get started
|
|
|
123
122
|
|
|
124
123
|
CLI command
|
|
125
124
|
-----------
|
|
126
|
-
To check
|
|
125
|
+
To check ExplorePy is running use:
|
|
127
126
|
::
|
|
127
|
+
|
|
128
128
|
explorepy acquire -n Explore_XXXX
|
|
129
129
|
|
|
130
130
|
For help, use:
|
|
131
131
|
::
|
|
132
|
+
|
|
132
133
|
explorepy -h
|
|
133
134
|
|
|
134
135
|
|
|
@@ -165,10 +166,12 @@ You can also create a new issue in the GitHub repository.
|
|
|
165
166
|
|
|
166
167
|
Authors
|
|
167
168
|
=======
|
|
168
|
-
- `Mohamad Atayi`_
|
|
169
169
|
- `Salman Rahman`_
|
|
170
|
-
- `Andrea Escartin`_
|
|
171
170
|
- `Sonja Stefani`_
|
|
171
|
+
- `Nayan Sharma`_
|
|
172
|
+
- `Deniz Toprak`_
|
|
173
|
+
- `Mohamad Atayi`_
|
|
174
|
+
- `Andrea Escartin`_
|
|
172
175
|
- `Alex Platt`_
|
|
173
176
|
- `Andreas Gutsche`_
|
|
174
177
|
- `Masoome Fazelian`_
|
|
@@ -177,10 +180,12 @@ Authors
|
|
|
177
180
|
- `Sebastian Herberger`_
|
|
178
181
|
|
|
179
182
|
|
|
180
|
-
.. _Mohamad Atayi: https://github.com/bmeatayi
|
|
181
183
|
.. _Salman Rahman: https://github.com/salman2135
|
|
182
|
-
.. _Andrea Escartin: https://github.com/andrea-escartin
|
|
183
184
|
.. _Sonja Stefani: https://github.com/SonjaSt
|
|
185
|
+
.. _Nayan Sharma: https://github.com/rednayan
|
|
186
|
+
.. _Deniz Toprak: https://github.com/detoprak13
|
|
187
|
+
.. _Mohamad Atayi: https://github.com/bmeatayi
|
|
188
|
+
.. _Andrea Escartin: https://github.com/andrea-escartin
|
|
184
189
|
.. _Alex Platt: https://github.com/Nujanauss
|
|
185
190
|
.. _Andreas Gutsche: https://github.com/andyman410
|
|
186
191
|
.. _Masoome Fazelian: https://github.com/fazelian
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
.. image:: https://raw.githubusercontent.com/Mentalab-hub/explorepy/master/docs/logo.jpg
|
|
2
|
-
:
|
|
3
|
-
:
|
|
4
|
-
|
|
2
|
+
:alt: Explorepy
|
|
3
|
+
:target: https://github.com/Mentalab-hub/explorepy
|
|
5
4
|
|
|
6
5
|
.. start-badges
|
|
7
6
|
|
|
@@ -17,9 +16,9 @@
|
|
|
17
16
|
:target: https://pypi.org/project/explorepy
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.
|
|
19
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.5.1.svg
|
|
21
20
|
:alt: Commits since latest release
|
|
22
|
-
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.
|
|
21
|
+
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.5.1...master
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
.. |wheel| image:: https://img.shields.io/pypi/wheel/explorepy.svg
|
|
@@ -39,10 +38,10 @@
|
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
=========================
|
|
42
|
-
|
|
41
|
+
ExplorePy overview
|
|
43
42
|
=========================
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
ExplorePy is an open-source Python API designed to collect and process ExG data using Mentalab's Explore device. Amongst other things, ExplorePy provides the following features:
|
|
46
45
|
|
|
47
46
|
* Real-time streaming of ExG, orientation and environmental data.
|
|
48
47
|
* Data recording in CSV and BDF+ formats.
|
|
@@ -61,7 +60,7 @@ Requirements
|
|
|
61
60
|
|
|
62
61
|
Detailed installation instructions can be found on the `installation page <https://explorepy.readthedocs.io/en/latest/installation.html>`_.
|
|
63
62
|
|
|
64
|
-
To install
|
|
63
|
+
To install ExplorePy from PyPI run:
|
|
65
64
|
::
|
|
66
65
|
|
|
67
66
|
pip install explorepy
|
|
@@ -78,12 +77,14 @@ Get started
|
|
|
78
77
|
|
|
79
78
|
CLI command
|
|
80
79
|
-----------
|
|
81
|
-
To check
|
|
80
|
+
To check ExplorePy is running use:
|
|
82
81
|
::
|
|
82
|
+
|
|
83
83
|
explorepy acquire -n Explore_XXXX
|
|
84
84
|
|
|
85
85
|
For help, use:
|
|
86
86
|
::
|
|
87
|
+
|
|
87
88
|
explorepy -h
|
|
88
89
|
|
|
89
90
|
|
|
@@ -120,10 +121,12 @@ You can also create a new issue in the GitHub repository.
|
|
|
120
121
|
|
|
121
122
|
Authors
|
|
122
123
|
=======
|
|
123
|
-
- `Mohamad Atayi`_
|
|
124
124
|
- `Salman Rahman`_
|
|
125
|
-
- `Andrea Escartin`_
|
|
126
125
|
- `Sonja Stefani`_
|
|
126
|
+
- `Nayan Sharma`_
|
|
127
|
+
- `Deniz Toprak`_
|
|
128
|
+
- `Mohamad Atayi`_
|
|
129
|
+
- `Andrea Escartin`_
|
|
127
130
|
- `Alex Platt`_
|
|
128
131
|
- `Andreas Gutsche`_
|
|
129
132
|
- `Masoome Fazelian`_
|
|
@@ -132,10 +135,12 @@ Authors
|
|
|
132
135
|
- `Sebastian Herberger`_
|
|
133
136
|
|
|
134
137
|
|
|
135
|
-
.. _Mohamad Atayi: https://github.com/bmeatayi
|
|
136
138
|
.. _Salman Rahman: https://github.com/salman2135
|
|
137
|
-
.. _Andrea Escartin: https://github.com/andrea-escartin
|
|
138
139
|
.. _Sonja Stefani: https://github.com/SonjaSt
|
|
140
|
+
.. _Nayan Sharma: https://github.com/rednayan
|
|
141
|
+
.. _Deniz Toprak: https://github.com/detoprak13
|
|
142
|
+
.. _Mohamad Atayi: https://github.com/bmeatayi
|
|
143
|
+
.. _Andrea Escartin: https://github.com/andrea-escartin
|
|
139
144
|
.. _Alex Platt: https://github.com/Nujanauss
|
|
140
145
|
.. _Andreas Gutsche: https://github.com/andyman410
|
|
141
146
|
.. _Masoome Fazelian: https://github.com/fazelian
|
|
@@ -4,9 +4,9 @@ build-backend = 'setuptools.build_meta'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'explorepy'
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.5.1"
|
|
8
8
|
license = { text = "MIT" }
|
|
9
|
-
readme = { file = "README.rst", content-type = "text/
|
|
9
|
+
readme = { file = "README.rst", content-type = "text/x-rst"}
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "MentaLab Hub", email = "support@mentab.org" },
|
|
12
12
|
]
|
|
@@ -29,11 +29,11 @@ classifiers = [
|
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
dependencies = [
|
|
32
|
-
'numpy',
|
|
33
|
-
'scipy',
|
|
34
|
-
'pyEDFlib==0.1.
|
|
32
|
+
'numpy==2.1.3',
|
|
33
|
+
'scipy==1.17.1',
|
|
34
|
+
'pyEDFlib==0.1.42',
|
|
35
35
|
'click==7.1.2',
|
|
36
|
-
'appdirs==1.4.
|
|
36
|
+
'appdirs==1.4.4',
|
|
37
37
|
'sentry_sdk==2.8.0',
|
|
38
38
|
'mne',
|
|
39
39
|
'eeglabio',
|
|
@@ -41,7 +41,7 @@ dependencies = [
|
|
|
41
41
|
'pyserial',
|
|
42
42
|
'pyyaml',
|
|
43
43
|
'bleak==0.22.3',
|
|
44
|
-
'pylsl',
|
|
44
|
+
'pylsl==1.18.2',
|
|
45
45
|
'numba']
|
|
46
46
|
|
|
47
47
|
[tool.setuptools]
|
|
@@ -60,3 +60,39 @@ test = [
|
|
|
60
60
|
|
|
61
61
|
[project.scripts]
|
|
62
62
|
explorepy = "explorepy.cli:cli"
|
|
63
|
+
|
|
64
|
+
# bumpoversion config
|
|
65
|
+
[tool.bumpversion]
|
|
66
|
+
current_version = "4.5.1"
|
|
67
|
+
commit = false
|
|
68
|
+
tag = false
|
|
69
|
+
|
|
70
|
+
[[tool.bumpversion.files]]
|
|
71
|
+
filename = "pyproject.toml"
|
|
72
|
+
search = 'version = "{current_version}"'
|
|
73
|
+
replace = 'version = "{new_version}"'
|
|
74
|
+
|
|
75
|
+
[[tool.bumpversion.files]]
|
|
76
|
+
filename = "README.rst"
|
|
77
|
+
search = "v{current_version}."
|
|
78
|
+
replace = "v{new_version}."
|
|
79
|
+
|
|
80
|
+
[[tool.bumpversion.files]]
|
|
81
|
+
filename = "docs/conf.py"
|
|
82
|
+
search = "version = release = '{current_version}'"
|
|
83
|
+
replace = "version = release = '{new_version}'"
|
|
84
|
+
|
|
85
|
+
[[tool.bumpversion.files]]
|
|
86
|
+
filename = "src/explorepy/__init__.py"
|
|
87
|
+
search = "__version__ = '{current_version}'"
|
|
88
|
+
replace = "__version__ = '{new_version}'"
|
|
89
|
+
|
|
90
|
+
[[tool.bumpversion.files]]
|
|
91
|
+
filename = "installer/windows/installer.cfg"
|
|
92
|
+
search = "explorepy=={current_version}"
|
|
93
|
+
replace = "explorepy=={new_version}"
|
|
94
|
+
|
|
95
|
+
[[tool.bumpversion.files]]
|
|
96
|
+
filename = "installer/windows/installer.cfg"
|
|
97
|
+
search = "version={current_version}"
|
|
98
|
+
replace = "version={new_version}"
|
|
@@ -17,6 +17,7 @@ from bleak import (
|
|
|
17
17
|
|
|
18
18
|
from explorepy._exceptions import (
|
|
19
19
|
BleDisconnectionError,
|
|
20
|
+
BleDisconnectionFailedError,
|
|
20
21
|
DeviceNotFoundError,
|
|
21
22
|
UnexpectedConnectionError
|
|
22
23
|
)
|
|
@@ -200,7 +201,22 @@ class BLEClient(BTClient):
|
|
|
200
201
|
if self.notify_task:
|
|
201
202
|
self.notify_task.cancel()
|
|
202
203
|
self.read_event.set()
|
|
203
|
-
|
|
204
|
+
|
|
205
|
+
min_time_to_wait = 0.5
|
|
206
|
+
max_time_to_wait = 5.
|
|
207
|
+
wait_start = time.time()
|
|
208
|
+
time_passed = 0.
|
|
209
|
+
if self.client is not None:
|
|
210
|
+
while self.client.is_connected:
|
|
211
|
+
time.sleep(0.1)
|
|
212
|
+
time_passed = time.time() - wait_start
|
|
213
|
+
if time_passed >= max_time_to_wait:
|
|
214
|
+
raise BleDisconnectionFailedError(f"Bleak client still not reporting disconnected after waiting "
|
|
215
|
+
f"{max_time_to_wait}.")
|
|
216
|
+
if time_passed < min_time_to_wait:
|
|
217
|
+
# Artificial delay to make the user think things are happening :)
|
|
218
|
+
time.sleep(min_time_to_wait - time_passed)
|
|
219
|
+
|
|
204
220
|
self.stop_read_loop()
|
|
205
221
|
self.ble_device = None
|
|
206
222
|
self.buffer = Queue()
|
|
@@ -20,11 +20,11 @@ from .explore import Explore # noqa
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
__all__ = ["Explore", "command", "tools", "log_config"]
|
|
23
|
-
__version__ = '4.
|
|
23
|
+
__version__ = '4.5.1'
|
|
24
24
|
|
|
25
25
|
this = sys.modules[__name__]
|
|
26
26
|
# TODO appropriate library
|
|
27
|
-
bt_interface_list = ['sdk', 'ble', 'mock', 'pyserial', 'usb']
|
|
27
|
+
bt_interface_list = ['sdk', 'ble', 'mock', 'pyserial', 'usb', 'csv']
|
|
28
28
|
this._bt_interface = 'ble'
|
|
29
29
|
|
|
30
30
|
if not sys.version_info >= (3, 6):
|
|
@@ -55,6 +55,23 @@ class BleDisconnectionError(Exception):
|
|
|
55
55
|
pass
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
class BleDisconnectionFailedError(Exception):
|
|
59
|
+
"""
|
|
60
|
+
Exception for client fails to achieve disconnected state
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ImpedanceModeActiveError(Exception):
|
|
66
|
+
"""
|
|
67
|
+
Exception for ASR, raised when impedance is running
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, message="ASR can not run because impedance mode is active.\n"
|
|
71
|
+
"Please disable impedance and try again\n"):
|
|
72
|
+
super().__init__(message)
|
|
73
|
+
|
|
74
|
+
|
|
58
75
|
class ExplorePyDeprecationError(Exception):
|
|
59
76
|
def __init__(self, message="Explorepy support for legacy devices is deprecated.\n"
|
|
60
77
|
"Please install explorepy 3.2.1 from Github or use the following command from Anaconda "
|
|
@@ -16,6 +16,7 @@ class CommandID(Enum):
|
|
|
16
16
|
"""Command ID enum class"""
|
|
17
17
|
API2BCMD = b'\xA0'
|
|
18
18
|
API4BCMD = b'\xB0'
|
|
19
|
+
API64BCMD = b'\xC0'
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class OpcodeID(Enum):
|
|
@@ -27,6 +28,7 @@ class OpcodeID(Enum):
|
|
|
27
28
|
CMD_ZM_ENABLE = b'\xA7'
|
|
28
29
|
CMD_SOFT_RESET = b'\xA8'
|
|
29
30
|
CMD_TEST_SIG = b'\xAA'
|
|
31
|
+
CMD_SET_EMMC_TIME = b'\xAB'
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class DeviceConfiguration:
|
|
@@ -182,6 +184,19 @@ class Command4B(Command):
|
|
|
182
184
|
"""prints the appropriate info about the command. """
|
|
183
185
|
|
|
184
186
|
|
|
187
|
+
class Command64B(Command):
|
|
188
|
+
"""An abstract base class for Explore 40 Byte command data length packets"""
|
|
189
|
+
|
|
190
|
+
def __init__(self):
|
|
191
|
+
super().__init__()
|
|
192
|
+
self.pid = CommandID.API64BCMD
|
|
193
|
+
self.payload_length = int2bytearray(40, 2)
|
|
194
|
+
|
|
195
|
+
@abc.abstractmethod
|
|
196
|
+
def __str__(self):
|
|
197
|
+
"""prints the appropriate info about the command. """
|
|
198
|
+
|
|
199
|
+
|
|
185
200
|
class SetSPS(Command2B):
|
|
186
201
|
"""Set the sampling rate of ExG device"""
|
|
187
202
|
|
|
@@ -213,6 +228,34 @@ class SetSPS(Command2B):
|
|
|
213
228
|
return "Set sampling rate command"
|
|
214
229
|
|
|
215
230
|
|
|
231
|
+
class SetBinaryTime(Command64B):
|
|
232
|
+
def __init__(self):
|
|
233
|
+
super().__init__()
|
|
234
|
+
self.opcode = OpcodeID.CMD_SET_EMMC_TIME
|
|
235
|
+
from datetime import datetime
|
|
236
|
+
now = datetime.now()
|
|
237
|
+
if not (2020 < now.year < 2099):
|
|
238
|
+
raise ValueError(f"Unsupported year: {now.year}")
|
|
239
|
+
date_time = bytes(
|
|
240
|
+
[
|
|
241
|
+
now.year - 2000,
|
|
242
|
+
now.month,
|
|
243
|
+
now.day,
|
|
244
|
+
now.weekday(),
|
|
245
|
+
now.hour,
|
|
246
|
+
now.minute,
|
|
247
|
+
now.second,
|
|
248
|
+
1,
|
|
249
|
+
2,
|
|
250
|
+
3,
|
|
251
|
+
]
|
|
252
|
+
)
|
|
253
|
+
self.param = date_time + bytes(41)
|
|
254
|
+
|
|
255
|
+
def __str__(self):
|
|
256
|
+
return "set time cmd"
|
|
257
|
+
|
|
258
|
+
|
|
216
259
|
class MemoryFormat(Command2B):
|
|
217
260
|
"""Format device memory"""
|
|
218
261
|
|
|
@@ -282,7 +325,8 @@ class SetChTest(Command2B):
|
|
|
282
325
|
|
|
283
326
|
COMMAND_CLASS_DICT = {
|
|
284
327
|
CommandID.API2BCMD: Command2B,
|
|
285
|
-
CommandID.API4BCMD: Command4B
|
|
328
|
+
CommandID.API4BCMD: Command4B,
|
|
329
|
+
CommandID.API64BCMD: Command64B
|
|
286
330
|
}
|
|
287
331
|
|
|
288
332
|
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from enum import (
|
|
4
|
+
Enum,
|
|
5
|
+
auto
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pylsl import local_clock
|
|
10
|
+
|
|
11
|
+
from explorepy.packet import (
|
|
12
|
+
BleImpedancePacket,
|
|
13
|
+
DeviceInfoBLE,
|
|
14
|
+
OrientationV2
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ClientState(Enum):
|
|
19
|
+
DISCONNECTED = auto()
|
|
20
|
+
CONNECTED = auto()
|
|
21
|
+
STREAMING = auto()
|
|
22
|
+
STOPPED = auto()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PacketSize(Enum):
|
|
26
|
+
EEG_8 = 40
|
|
27
|
+
EEG_32 = 112
|
|
28
|
+
ORN = 50
|
|
29
|
+
DEVICE_INFO = 38
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CsvClient:
|
|
33
|
+
def __init__(self, channel_count, file_path: str = None):
|
|
34
|
+
if file_path is None:
|
|
35
|
+
self.file_name = "32channel_semidry_artefacts_ExG.csv"
|
|
36
|
+
file_path = os.path.join("/Users/sonjastefani/Documents/dev/explore-desktop/test-data/", self.file_name)
|
|
37
|
+
else:
|
|
38
|
+
self.file_name = os.path.split(file_path)[-1]
|
|
39
|
+
|
|
40
|
+
if not os.path.isfile(file_path):
|
|
41
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
42
|
+
|
|
43
|
+
self.server = CsvServer(
|
|
44
|
+
channel_count=channel_count,
|
|
45
|
+
csv_path=file_path,
|
|
46
|
+
loop=True
|
|
47
|
+
)
|
|
48
|
+
self._state = ClientState.DISCONNECTED
|
|
49
|
+
|
|
50
|
+
def set_state(self, state: ClientState):
|
|
51
|
+
if not isinstance(state, ClientState):
|
|
52
|
+
raise ValueError("Invalid client state")
|
|
53
|
+
self._state = state
|
|
54
|
+
|
|
55
|
+
def get_state(self) -> ClientState:
|
|
56
|
+
return self._state
|
|
57
|
+
|
|
58
|
+
def connect(self):
|
|
59
|
+
if self._state != ClientState.DISCONNECTED:
|
|
60
|
+
return False
|
|
61
|
+
self.set_state(ClientState.CONNECTED)
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
def disconnect(self):
|
|
65
|
+
self.set_state(ClientState.DISCONNECTED)
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
def start_streaming(self):
|
|
69
|
+
if self._state != ClientState.CONNECTED:
|
|
70
|
+
raise RuntimeError(f"Cannot start streaming from state {self._state}")
|
|
71
|
+
self.set_state(ClientState.STREAMING)
|
|
72
|
+
|
|
73
|
+
def stop_streaming(self):
|
|
74
|
+
if self._state == ClientState.STREAMING:
|
|
75
|
+
self.set_state(ClientState.STOPPED)
|
|
76
|
+
|
|
77
|
+
def read(self):
|
|
78
|
+
if self._state != ClientState.STREAMING:
|
|
79
|
+
if self._state == ClientState.CONNECTED:
|
|
80
|
+
self.set_state(ClientState.STREAMING)
|
|
81
|
+
device_info_packet = DeviceInfoMock(timestamp=self.server.ts, payload=None)
|
|
82
|
+
device_info_packet.packet_size = PacketSize.DEVICE_INFO
|
|
83
|
+
device_info_packet.set_info(self.server.device_info)
|
|
84
|
+
return device_info_packet
|
|
85
|
+
|
|
86
|
+
self.server.tick += 1
|
|
87
|
+
if self.server.tick % 5 == 0:
|
|
88
|
+
orn_packet = OrientationMock(timestamp=self.server.ts, payload=None)
|
|
89
|
+
orn_packet.set_data(self.server.orn_value)
|
|
90
|
+
orn_packet.packet_size = PacketSize.ORN
|
|
91
|
+
return orn_packet
|
|
92
|
+
|
|
93
|
+
self.server.ts += self.server.time_period
|
|
94
|
+
sleep_time = self.server.ts - local_clock()
|
|
95
|
+
if sleep_time > 0:
|
|
96
|
+
time.sleep(sleep_time)
|
|
97
|
+
eeg_packet = BleImpedancePacket(timestamp=self.server.ts, payload=None)
|
|
98
|
+
try:
|
|
99
|
+
eeg_packet.data, eeg_packet.timestamp = self.server.read_sample()
|
|
100
|
+
except StopIteration:
|
|
101
|
+
self.set_state(ClientState.STOPPED)
|
|
102
|
+
return None
|
|
103
|
+
eeg_packet.packet_size = self.server.packet_size
|
|
104
|
+
return eeg_packet
|
|
105
|
+
|
|
106
|
+
def write(self, bytes):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class CsvServer:
|
|
111
|
+
def __init__(self, channel_count: int, csv_path: str, loop: bool = True):
|
|
112
|
+
if channel_count not in (8, 32):
|
|
113
|
+
raise ValueError("Only 8 or 32 channels supported")
|
|
114
|
+
|
|
115
|
+
self.channel_count = channel_count
|
|
116
|
+
self.loop = loop
|
|
117
|
+
self.csv_data = np.loadtxt(csv_path, delimiter=',', skiprows=1) # skip row 0
|
|
118
|
+
|
|
119
|
+
self.csv_ts = self.csv_data[:, 0]
|
|
120
|
+
self.csv_data = self.csv_data[:, 1:]
|
|
121
|
+
|
|
122
|
+
if self.csv_data.shape[1] != channel_count:
|
|
123
|
+
print('######################', self.csv_data.shape)
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"CSV has {self.csv_data.shape[1]} columns, "
|
|
126
|
+
f"expected {channel_count}"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
self.row_idx = 0
|
|
130
|
+
self.num_rows = self.csv_data.shape[0]
|
|
131
|
+
self.device_info_ble_32ch = {
|
|
132
|
+
'device_name': 'Explore_DABD',
|
|
133
|
+
'firmware_version': '9.6.9',
|
|
134
|
+
'adc_mask': [1] * 8,
|
|
135
|
+
'sampling_rate': 250,
|
|
136
|
+
'is_imp_mode': False,
|
|
137
|
+
'board_id': 'PCB_304_801p2_X',
|
|
138
|
+
'memory_info': 1,
|
|
139
|
+
'max_online_sps': 250,
|
|
140
|
+
'max_offline_sps': 2000
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
self.device_info_ble_8ch = {
|
|
144
|
+
'device_name': 'Explore_AAAQ',
|
|
145
|
+
'firmware_version': '7.6.9',
|
|
146
|
+
'adc_mask': [1] * 8,
|
|
147
|
+
'sampling_rate': 250,
|
|
148
|
+
'is_imp_mode': False,
|
|
149
|
+
'board_id': 'PCB_303_801E_XX',
|
|
150
|
+
'memory_info': 1,
|
|
151
|
+
'max_online_sps': 1000,
|
|
152
|
+
'max_offline_sps': 8000
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
self.device_info = (
|
|
156
|
+
self.device_info_ble_8ch
|
|
157
|
+
if channel_count == 8
|
|
158
|
+
else self.device_info_ble_32ch
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.fs = self.device_info['sampling_rate']
|
|
162
|
+
self.time_period = np.round(1 / self.fs, 3)
|
|
163
|
+
self.ts = local_clock()
|
|
164
|
+
self.tick = 0
|
|
165
|
+
|
|
166
|
+
self.packet_size = (
|
|
167
|
+
PacketSize.EEG_8 if channel_count == 8 else PacketSize.EEG_32
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self.orn_value = [
|
|
171
|
+
5.002, -3.904, 1001.01, 420.0, -70.0, 210.0,
|
|
172
|
+
103.36, 804.08, -532.0, -0.0023,
|
|
173
|
+
-0.0028, 0.0371, 0.9993
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
def read_sample(self):
|
|
177
|
+
if self.row_idx >= self.num_rows:
|
|
178
|
+
if not self.loop:
|
|
179
|
+
raise StopIteration("End of CSV reached")
|
|
180
|
+
self.row_idx = 0
|
|
181
|
+
|
|
182
|
+
ts = self.csv_ts[self.row_idx]
|
|
183
|
+
sample = self.csv_data[self.row_idx]
|
|
184
|
+
self.row_idx += 1
|
|
185
|
+
|
|
186
|
+
return sample.reshape(self.channel_count, 1), ts
|
|
187
|
+
|
|
188
|
+
def read_device_info(self):
|
|
189
|
+
return self.device_info
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class DeviceInfoMock(DeviceInfoBLE):
|
|
193
|
+
def __init__(self, timestamp, payload, time_offset=0):
|
|
194
|
+
self.timestamp = timestamp
|
|
195
|
+
|
|
196
|
+
def _convert(self, bin_data):
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
def set_info(self, info):
|
|
200
|
+
self.info = info
|
|
201
|
+
|
|
202
|
+
def get_info(self):
|
|
203
|
+
return self.info
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class OrientationMock(OrientationV2):
|
|
207
|
+
"""Orientation data packet"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, timestamp, payload, time_offset=0):
|
|
210
|
+
self.timestamp = timestamp
|
|
211
|
+
|
|
212
|
+
def _convert(self, bin_data):
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
def get_data(self, srate=None):
|
|
216
|
+
return [self.timestamp], self.data
|
|
217
|
+
|
|
218
|
+
def set_data(self, data):
|
|
219
|
+
self.data = data
|