qldsdk-serial 0.1.0__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.
- qldsdk_serial-0.1.0/PKG-INFO +30 -0
- qldsdk_serial-0.1.0/README.md +8 -0
- qldsdk_serial-0.1.0/setup.cfg +32 -0
- qldsdk_serial-0.1.0/setup.py +26 -0
- qldsdk_serial-0.1.0/src/qldsdk/__init__.py +4 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/__init__.py +8 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/crc/__init__.py +5 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/crc/crctools.py +95 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/device.py +34 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/dict_utils.py +12 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/entity/__init__.py +222 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/exception.py +0 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/filter/__init__.py +1 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/filter/norch.py +59 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/interface/__init__.py +10 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/interface/analyzer.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/interface/collector.py +10 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/interface/device.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/interface/parser.py +13 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/interface/stimulator.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/interface/store.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/local.py +37 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/message/__init__.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/message/command.py +333 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/message/tcp.py +0 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/message/udp.py +104 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/network/__init__.py +34 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/network/monitor.py +55 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/persist/__init__.py +3 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/persist/ars_edf.py +257 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/persist/edf.py +205 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/persist/rsc_edf.py +346 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/persist/stream.py +161 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/persist/trigger_log.py +86 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/rsc/__init__.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/rsc/paradigm.py +570 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/rsc/type.py +87 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/serial.py +188 -0
- qldsdk_serial-0.1.0/src/qldsdk/core/utils.py +75 -0
- qldsdk_serial-0.1.0/src/qldsdk/extends/__init__.py +1 -0
- qldsdk_serial-0.1.0/src/qldsdk/extends/check_and_enhance.py +104 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/__init__.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/lse/__init__.py +3 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/lse/app.py +281 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/lse/ars_lse.py +349 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/lse/config.py +419 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/lse/exception.py +13 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/lse/message.py +397 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/lse/type.py +103 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/__init__.py +3 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/command/__init__.py +383 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/device/__init__.py +2 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/device/c64_rs.py +15 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/device/serial_base.py +759 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/__init__.py +3 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/command.py +10 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/device.py +85 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/handler.py +9 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/parser.py +18 -0
- qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/message.py +264 -0
- qldsdk_serial-0.1.0/src/qldsdk/third/__init__.py +1 -0
- qldsdk_serial-0.1.0/src/qldsdk/third/alpha_alg.py +247 -0
- qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/PKG-INFO +30 -0
- qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/SOURCES.txt +67 -0
- qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/dependency_links.txt +1 -0
- qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/requires.txt +10 -0
- qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/top_level.txt +1 -0
- qldsdk_serial-0.1.0/tests/test_c64rs_serial.py +135 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qldsdk-serial
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK for quanlan device with serial communication
|
|
5
|
+
Home-page: https://github.com/hehuajun/qlsdk
|
|
6
|
+
Author: hehuajun
|
|
7
|
+
Author-email: hehuajun@eegion.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: loguru>=0.7.3
|
|
14
|
+
Requires-Dist: numpy>=1.23.5
|
|
15
|
+
Requires-Dist: bitarray>=1.5.3
|
|
16
|
+
Requires-Dist: pyserial>=3.5.0
|
|
17
|
+
Requires-Dist: pyedflib>=0.1.42
|
|
18
|
+
Requires-Dist: scipy>=1.10.1
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
21
|
+
Requires-Dist: twine>=3.0; extra == "dev"
|
|
22
|
+
|
|
23
|
+
## 1. 项目名称
|
|
24
|
+
qldsdk-serial
|
|
25
|
+
|
|
26
|
+
## 2. 项目说明
|
|
27
|
+
|
|
28
|
+
qldsdk-serial 是一个功能完整的脑电设备控制和数据采集工具包,采用模块化设计,支持多种设备类型和功能。通过工厂模式和接口定义,实现了设备的统一管理和扩展。系统架构清晰,核心功能完善,为脑电设备的使用提供了便捷的编程接口。
|
|
29
|
+
|
|
30
|
+
该SDK不仅支持基本的数据采集和存储功能,还提供了丰富的刺激控制选项,满足不同实验场景的需求。未来通过完善接口定义、增强错误处理、添加更多设备支持等方式进一步提升系统的稳定性和可扩展性。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = qldsdk-serial
|
|
3
|
+
version = 0.1.0
|
|
4
|
+
author = hehuajun
|
|
5
|
+
author_email = hehuajun@eegion.com
|
|
6
|
+
description = Quan Lan Device SDK for Serial Communication
|
|
7
|
+
long_description = file: README.md
|
|
8
|
+
long_description_content_type = text/markdown
|
|
9
|
+
url = https://github.com/yourusername/qlsdk
|
|
10
|
+
|
|
11
|
+
[options]
|
|
12
|
+
package_dir = = src
|
|
13
|
+
packages = find:
|
|
14
|
+
python_requires = >=3.10
|
|
15
|
+
install_requires =
|
|
16
|
+
loguru>=0.6.0
|
|
17
|
+
|
|
18
|
+
[options.packages.find]
|
|
19
|
+
where = src
|
|
20
|
+
exclude =
|
|
21
|
+
tests*
|
|
22
|
+
examples*
|
|
23
|
+
|
|
24
|
+
[options.extras_require]
|
|
25
|
+
dev =
|
|
26
|
+
pytest>=6.0
|
|
27
|
+
twine>=3.0
|
|
28
|
+
|
|
29
|
+
[egg_info]
|
|
30
|
+
tag_build =
|
|
31
|
+
tag_date = 0
|
|
32
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# setup.py
|
|
2
|
+
import setuptools
|
|
3
|
+
|
|
4
|
+
with open("README.md", "r", encoding='utf-8' ) as fh:
|
|
5
|
+
long_description = fh.read()
|
|
6
|
+
|
|
7
|
+
setuptools.setup(
|
|
8
|
+
name="qldsdk-serial",
|
|
9
|
+
version="0.1.0",
|
|
10
|
+
author="hehuajun",
|
|
11
|
+
author_email="hehuajun@eegion.com",
|
|
12
|
+
description="SDK for quanlan device with serial communication",
|
|
13
|
+
long_description=long_description,
|
|
14
|
+
long_description_content_type="text/markdown",
|
|
15
|
+
url="https://github.com/hehuajun/qlsdk",
|
|
16
|
+
package_dir={"": "src"},
|
|
17
|
+
packages=setuptools.find_packages(where="src"),
|
|
18
|
+
classifiers=[
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: Microsoft :: Windows :: Windows 10",
|
|
22
|
+
],
|
|
23
|
+
python_requires='>=3.10',
|
|
24
|
+
install_requires=open("requirements.txt").read().splitlines(),
|
|
25
|
+
include_package_data=True,
|
|
26
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from qldsdk.core.crc import *
|
|
2
|
+
from qldsdk.core.message import *
|
|
3
|
+
from qldsdk.core.local import *
|
|
4
|
+
from qldsdk.core.utils import *
|
|
5
|
+
from qldsdk.core.entity import *
|
|
6
|
+
from qldsdk.core.serial import *
|
|
7
|
+
from qldsdk.core.dict_utils import filter_dict_keys
|
|
8
|
+
from qldsdk.core.rsc import *
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# len=256
|
|
2
|
+
CRC_HI = [
|
|
3
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
4
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
5
|
+
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
6
|
+
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
7
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
8
|
+
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
|
9
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
10
|
+
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
11
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
12
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
|
|
13
|
+
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
14
|
+
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
15
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
16
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
|
|
17
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
18
|
+
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
19
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
20
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
21
|
+
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
22
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
23
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
|
24
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
|
|
25
|
+
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
|
26
|
+
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
27
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
|
28
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# len=256
|
|
32
|
+
CRC_LO = [
|
|
33
|
+
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
|
|
34
|
+
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
|
|
35
|
+
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
|
36
|
+
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
|
|
37
|
+
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
|
|
38
|
+
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
|
39
|
+
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
|
|
40
|
+
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
|
|
41
|
+
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
|
|
42
|
+
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
|
|
43
|
+
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
|
|
44
|
+
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
|
45
|
+
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
|
|
46
|
+
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
|
|
47
|
+
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
|
|
48
|
+
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
|
|
49
|
+
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
|
|
50
|
+
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
|
51
|
+
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
|
|
52
|
+
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
|
|
53
|
+
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
|
54
|
+
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
|
|
55
|
+
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
|
|
56
|
+
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
|
57
|
+
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
|
|
58
|
+
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
def crc16(data):
|
|
62
|
+
crcHi = 0xFF
|
|
63
|
+
crcLo = 0xFF
|
|
64
|
+
for i in range(len(data)):
|
|
65
|
+
index = crcHi ^ data[i]
|
|
66
|
+
crcHi = crcLo ^ CRC_HI[index]
|
|
67
|
+
crcLo = CRC_LO[index]
|
|
68
|
+
|
|
69
|
+
return crcHi << 8 | crcLo
|
|
70
|
+
|
|
71
|
+
class CRCEnum(object):
|
|
72
|
+
CRC8 = 8
|
|
73
|
+
CRC16 = 16
|
|
74
|
+
CRC32 = 32
|
|
75
|
+
|
|
76
|
+
class CRC(object):
|
|
77
|
+
def __init__(self, width=CRCEnum.CRC16) :
|
|
78
|
+
self.width = width
|
|
79
|
+
self.crcHi = 0xFF
|
|
80
|
+
self.crcLo = 0xFF
|
|
81
|
+
|
|
82
|
+
def _init(self):
|
|
83
|
+
if self.width == CRCEnum.CRC16:
|
|
84
|
+
self.crcHi = 0xFF
|
|
85
|
+
self.crcLo = 0xFF
|
|
86
|
+
|
|
87
|
+
def calc(self, data):
|
|
88
|
+
for i in range(len(data)):
|
|
89
|
+
index = self.crcHi ^ data[i]
|
|
90
|
+
self.crcHi = self.crcLo ^ CRC_HI[index]
|
|
91
|
+
self.crcLo = CRC_LO[index]
|
|
92
|
+
|
|
93
|
+
def checksum(self):
|
|
94
|
+
return self.crcHi << 8 | self.crcLo
|
|
95
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
class BaseDevice(object):
|
|
4
|
+
def __init__(self, socket = None):
|
|
5
|
+
self.socket = socket
|
|
6
|
+
self.device_name = None
|
|
7
|
+
self._device_type = None
|
|
8
|
+
self.device_id = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def device_type(self) -> int:
|
|
13
|
+
return self._device_type
|
|
14
|
+
|
|
15
|
+
@device_type.setter
|
|
16
|
+
def device_type(self, value: int):
|
|
17
|
+
self._device_type = value
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def acq_channels(self) :
|
|
21
|
+
return None
|
|
22
|
+
@property
|
|
23
|
+
def sample_range(self) -> int:
|
|
24
|
+
return None
|
|
25
|
+
@property
|
|
26
|
+
def sample_rate(self) -> int:
|
|
27
|
+
return None
|
|
28
|
+
@property
|
|
29
|
+
def sample_num(self) -> int:
|
|
30
|
+
return 10
|
|
31
|
+
@property
|
|
32
|
+
def resolution(self):
|
|
33
|
+
return 24
|
|
34
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
def filter_dict_keys(source_dict: dict, keys_to_keep: set) -> dict:
|
|
2
|
+
"""
|
|
3
|
+
从源字典中提取指定key集合
|
|
4
|
+
|
|
5
|
+
Args:
|
|
6
|
+
source_dict: 源字典,key不固定
|
|
7
|
+
keys_to_keep: 要保留的key集合
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
只包含指定key的新字典
|
|
11
|
+
"""
|
|
12
|
+
return {k: v for k, v in source_dict.items() if k in keys_to_keep}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from qldsdk.core.utils import to_channels
|
|
2
|
+
from loguru import logger
|
|
3
|
+
|
|
4
|
+
class Packet(object):
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.time_stamp = None
|
|
7
|
+
self.pkg_id = None
|
|
8
|
+
self.result = None
|
|
9
|
+
self.channels = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RscPacket(Packet):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.origin_sample_rate = None
|
|
16
|
+
self.sample_rate = None
|
|
17
|
+
self.sample_num = None
|
|
18
|
+
self.resolution = None
|
|
19
|
+
self.filter = None
|
|
20
|
+
self.data_len = None
|
|
21
|
+
self.trigger = None
|
|
22
|
+
self.eeg = None # 数字信号
|
|
23
|
+
self.eeg_p = None # 物理值
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def transfer(body: bytes) -> 'RscPacket':
|
|
27
|
+
packet = RscPacket()
|
|
28
|
+
packet.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
29
|
+
packet.result = body[8]
|
|
30
|
+
packet.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
31
|
+
# logger.trace(f"pkg_id: {packet.pkg_id}")
|
|
32
|
+
packet.channels = to_channels(body[13: 45])
|
|
33
|
+
packet.origin_sample_rate = int.from_bytes(body[45: 49], 'little')
|
|
34
|
+
packet.sample_rate = int.from_bytes(body[49: 53], 'little')
|
|
35
|
+
packet.sample_num = int.from_bytes(body[53: 57], 'little')
|
|
36
|
+
packet.resolution = int(int(body[57]) / 8)
|
|
37
|
+
packet.filter = body[58]
|
|
38
|
+
packet.data_len = int.from_bytes(body[59: 63], 'little')
|
|
39
|
+
# 步径 相同通道的点间隔
|
|
40
|
+
step = int(len(packet.channels) * packet.resolution + 4)
|
|
41
|
+
b_eeg = body[63:]
|
|
42
|
+
ch_num = len(packet.channels)
|
|
43
|
+
# 字节序列(4Cn{channel_size}){sample_num})
|
|
44
|
+
packet.trigger = [int.from_bytes(b_eeg[j * step : j * step + 4], 'little', signed=False) for j in range(packet.sample_num)]
|
|
45
|
+
|
|
46
|
+
packet.eeg = [
|
|
47
|
+
[
|
|
48
|
+
int.from_bytes(b_eeg[i * packet.resolution + 4 + j * step:i * packet.resolution + 4 + j * step + 3], 'big', signed=True)
|
|
49
|
+
for j in range(packet.sample_num)
|
|
50
|
+
]
|
|
51
|
+
for i in range(ch_num)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# logger.trace(packet)
|
|
55
|
+
|
|
56
|
+
return packet
|
|
57
|
+
|
|
58
|
+
def copy(self) -> 'RscPacket':
|
|
59
|
+
packet = RscPacket()
|
|
60
|
+
packet.time_stamp = self.time_stamp
|
|
61
|
+
packet.pkg_id = self.pkg_id
|
|
62
|
+
packet.result = self.result
|
|
63
|
+
packet.channels = self.channels
|
|
64
|
+
packet.origin_sample_rate = self.origin_sample_rate
|
|
65
|
+
packet.sample_rate = self.sample_rate
|
|
66
|
+
packet.sample_num = self.sample_num
|
|
67
|
+
packet.resolution = self.resolution
|
|
68
|
+
packet.filter = self.filter
|
|
69
|
+
packet.data_len = self.data_len
|
|
70
|
+
packet.trigger = self.trigger
|
|
71
|
+
packet.eeg = self.eeg
|
|
72
|
+
return packet
|
|
73
|
+
|
|
74
|
+
def __str__(self):
|
|
75
|
+
return f"""[
|
|
76
|
+
"time_stamp": {self.time_stamp}
|
|
77
|
+
"pkg_id": {self.pkg_id}
|
|
78
|
+
"channels": {self.channels}
|
|
79
|
+
"trigger": {self.trigger}
|
|
80
|
+
"eeg": {self.eeg}
|
|
81
|
+
]"""
|
|
82
|
+
|
|
83
|
+
class ImpedancePacket(Packet):
|
|
84
|
+
def __init__(self):
|
|
85
|
+
super().__init__()
|
|
86
|
+
self.impedance = None
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def transfer(body:bytes) -> 'ImpedancePacket':
|
|
90
|
+
packet = ImpedancePacket()
|
|
91
|
+
packet.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
92
|
+
# packet.result = body[8]
|
|
93
|
+
packet.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
94
|
+
packet.channels = to_channels(body[13: 45])
|
|
95
|
+
# packet.sample_rate = int.from_bytes(body[45: 49], 'little')
|
|
96
|
+
# packet.sample_len = int.from_bytes(body[49: 53], 'little')
|
|
97
|
+
# packet.resolution = int(int(body[53]) / 8)
|
|
98
|
+
# packet.filter = int(int(body[54]) / 8)
|
|
99
|
+
# packet.wave_type = int(int(body[55]) / 8)
|
|
100
|
+
# packet.wave_freq = int.from_bytes(body[56: 60], 'little')
|
|
101
|
+
# packet.data_len = int.from_bytes(body[60: 64], 'little')
|
|
102
|
+
b_impedance = body[64:]
|
|
103
|
+
packet.impedance = [int.from_bytes(b_impedance[j * 4 : j * 4 + 4], 'little', signed=False) for j in range(len(packet.channels))]
|
|
104
|
+
|
|
105
|
+
# logger.trace(f"impedance: {packet}")
|
|
106
|
+
|
|
107
|
+
return packet
|
|
108
|
+
|
|
109
|
+
def __str__(self):
|
|
110
|
+
return f"""[
|
|
111
|
+
"time_stamp": {self.time_stamp}
|
|
112
|
+
"pkg_id": {self.pkg_id}
|
|
113
|
+
"channels": {self.channels}
|
|
114
|
+
"impedance": {self.impedance}
|
|
115
|
+
]"""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class C256RSPacket(Packet):
|
|
119
|
+
def __init__(self):
|
|
120
|
+
super().__init__()
|
|
121
|
+
self.origin_sample_rate = None
|
|
122
|
+
self.sample_rate = None
|
|
123
|
+
self.sample_num = None
|
|
124
|
+
self.resolution = None
|
|
125
|
+
self.filter = None
|
|
126
|
+
self.data_len = None
|
|
127
|
+
self.trigger = None
|
|
128
|
+
self.eeg = None
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def transfer(body: bytes) -> 'RscPacket':
|
|
132
|
+
packet = RscPacket()
|
|
133
|
+
packet.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
134
|
+
packet.result = body[8]
|
|
135
|
+
packet.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
136
|
+
packet.channels = to_channels(body[13: 45])
|
|
137
|
+
logger.trace(f"pkg_id: {packet.pkg_id}, channels: {packet.channels}")
|
|
138
|
+
|
|
139
|
+
packet.origin_sample_rate = int.from_bytes(body[45: 49], 'little')
|
|
140
|
+
packet.sample_rate = int.from_bytes(body[49: 53], 'little')
|
|
141
|
+
packet.sample_num = int.from_bytes(body[53: 57], 'little')
|
|
142
|
+
packet.resolution = int(int(body[57]) / 8)
|
|
143
|
+
packet.filter = body[58]
|
|
144
|
+
packet.data_len = int.from_bytes(body[59: 63], 'little')
|
|
145
|
+
|
|
146
|
+
# 数据块
|
|
147
|
+
b_eeg = body[63:]
|
|
148
|
+
# 根据值域分割数组-代表设备的4个模块
|
|
149
|
+
ranges = [(1, 64), (65, 128), (129, 192), (193, 256)]
|
|
150
|
+
sub_channels = [[x for x in packet.channels if low <= x <= high] for low, high in ranges]
|
|
151
|
+
# 只处理选中的模块
|
|
152
|
+
sub_channels = [_ for _ in sub_channels if len(_) > 0]
|
|
153
|
+
# 步径 相同通道的点间隔
|
|
154
|
+
step = int(len(packet.channels) * packet.resolution + 4 * len(sub_channels))
|
|
155
|
+
offset = 0
|
|
156
|
+
|
|
157
|
+
# 分按子模块处理
|
|
158
|
+
for channels in sub_channels:
|
|
159
|
+
logger.trace(f"子数组: {channels} 长度: {len(channels)}")
|
|
160
|
+
channel_size = len(channels)
|
|
161
|
+
|
|
162
|
+
# 模块没有选中通道的,跳过
|
|
163
|
+
if channel_size == 0:
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# 只保留第一个有效模块的trigger,其他的模块是冗余信息,无实际含义
|
|
167
|
+
if packet.trigger is None:
|
|
168
|
+
packet.trigger = [int.from_bytes(b_eeg[j * step : j * step + 4], 'little', signed=False) for j in range(packet.sample_num)]
|
|
169
|
+
logger.trace(f"trigger: {packet.trigger}")
|
|
170
|
+
trigger_positions = [index for index, value in enumerate(packet.trigger) if value != 0]
|
|
171
|
+
if len(trigger_positions) > 0:
|
|
172
|
+
logger.debug(f"Trigger触发点位置: {trigger_positions}, 触发点时间戳: {[packet.time_stamp + int(pos * 1000 / packet.sample_rate) for pos in trigger_positions]}")
|
|
173
|
+
|
|
174
|
+
eeg = [
|
|
175
|
+
[
|
|
176
|
+
int.from_bytes(b_eeg[offset + step * j + 4 + k * packet.resolution : offset + step * j + 7 + k * packet.resolution], 'big', signed=True)
|
|
177
|
+
for j in range(packet.sample_num)
|
|
178
|
+
]
|
|
179
|
+
for k in range(channel_size)
|
|
180
|
+
]
|
|
181
|
+
packet.eeg = packet.eeg + eeg if packet.eeg else eeg
|
|
182
|
+
|
|
183
|
+
offset += 4 + channel_size * packet.resolution
|
|
184
|
+
|
|
185
|
+
# logger.trace(packet)
|
|
186
|
+
return packet
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class C256ImpedancePacket(Packet):
|
|
190
|
+
def __init__(self):
|
|
191
|
+
super().__init__()
|
|
192
|
+
self.impedance = None
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def transfer(body:bytes) -> 'ImpedancePacket':
|
|
196
|
+
packet = ImpedancePacket()
|
|
197
|
+
packet.time_stamp = int.from_bytes(body[0:8], 'little')
|
|
198
|
+
# packet.result = body[8]
|
|
199
|
+
packet.pkg_id = int.from_bytes(body[9: 13], 'little')
|
|
200
|
+
packet.channels = to_channels(body[13: 45])
|
|
201
|
+
# packet.sample_rate = int.from_bytes(body[45: 49], 'little')
|
|
202
|
+
# packet.sample_len = int.from_bytes(body[49: 53], 'little')
|
|
203
|
+
# packet.resolution = int(int(body[53]) / 8)
|
|
204
|
+
# packet.filter = int(int(body[54]) / 8)
|
|
205
|
+
# packet.wave_type = int(int(body[55]) / 8)
|
|
206
|
+
# packet.wave_freq = int.from_bytes(body[56: 60], 'little')
|
|
207
|
+
# packet.data_len = int.from_bytes(body[60: 64], 'little')
|
|
208
|
+
b_impedance = body[64:]
|
|
209
|
+
packet.impedance = [int.from_bytes(b_impedance[j : j + 4], 'little', signed=False) for j in range(len(packet.channels))]
|
|
210
|
+
|
|
211
|
+
# logger.trace(f"impedance: {packet}")
|
|
212
|
+
|
|
213
|
+
def __str__(self):
|
|
214
|
+
return f"""
|
|
215
|
+
time_stamp: {self.time_stamp}
|
|
216
|
+
pkg_id: {self.pkg_id}
|
|
217
|
+
result: {self.result}
|
|
218
|
+
channels: {self.channels}
|
|
219
|
+
data len: {self.data_len}
|
|
220
|
+
impedance: {self.impedance}
|
|
221
|
+
"""
|
|
222
|
+
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .norch import notch_filter_50hz
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def notch_filter_50hz(data: np.ndarray,
|
|
4
|
+
fs: float,
|
|
5
|
+
notch_width: float = 2.0,
|
|
6
|
+
max_harmonics: int = 5) -> np.ndarray:
|
|
7
|
+
"""
|
|
8
|
+
多通道50Hz谐波陷波滤波器
|
|
9
|
+
|
|
10
|
+
参数:
|
|
11
|
+
data : 输入信号,形状为 [通道数, 采样点数] 的二维数组
|
|
12
|
+
fs : 采样频率 (Hz)
|
|
13
|
+
notch_width : 陷波带宽 (Hz),默认2Hz
|
|
14
|
+
max_harmonics : 最大谐波次数,默认处理前10次谐波
|
|
15
|
+
|
|
16
|
+
返回:
|
|
17
|
+
滤波后的信号,形状与输入相同
|
|
18
|
+
"""
|
|
19
|
+
# 输入校验
|
|
20
|
+
if data.ndim != 2:
|
|
21
|
+
raise ValueError("输入必须为二维数组 [channels, samples]")
|
|
22
|
+
if fs <= 0:
|
|
23
|
+
raise ValueError("采样频率必须为正数")
|
|
24
|
+
|
|
25
|
+
n_channels, n_samples = data.shape
|
|
26
|
+
nyquist = fs / 2
|
|
27
|
+
processed = np.empty_like(data)
|
|
28
|
+
|
|
29
|
+
# 生成频率轴
|
|
30
|
+
freqs = np.fft.fftfreq(n_samples, 1/fs)
|
|
31
|
+
|
|
32
|
+
for ch in range(n_channels):
|
|
33
|
+
# FFT变换
|
|
34
|
+
fft_data = np.fft.fft(data[ch])
|
|
35
|
+
|
|
36
|
+
# 生成陷波掩模
|
|
37
|
+
mask = np.ones(n_samples, dtype=bool)
|
|
38
|
+
|
|
39
|
+
# 计算需要消除的谐波
|
|
40
|
+
for k in range(1, max_harmonics+1):
|
|
41
|
+
target_freq = 50 * k
|
|
42
|
+
|
|
43
|
+
# 超过奈奎斯特频率则停止
|
|
44
|
+
if target_freq > nyquist:
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
# 生成陷波范围
|
|
48
|
+
notch_range = (np.abs(freqs - target_freq) <= notch_width/2) | \
|
|
49
|
+
(np.abs(freqs + target_freq) <= notch_width/2)
|
|
50
|
+
|
|
51
|
+
mask &= ~notch_range
|
|
52
|
+
|
|
53
|
+
# 应用频域滤波
|
|
54
|
+
filtered_fft = fft_data * mask
|
|
55
|
+
|
|
56
|
+
# 逆变换并取实数部分
|
|
57
|
+
processed[ch] = np.real(np.fft.ifft(filtered_fft))
|
|
58
|
+
|
|
59
|
+
return processed
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
from time import time
|
|
4
|
+
|
|
5
|
+
# 读取本机全部ip列表
|
|
6
|
+
# return list
|
|
7
|
+
def get_ips():
|
|
8
|
+
ips = socket.gethostbyname_ex(socket.gethostname())[-1]
|
|
9
|
+
# 排除环回地址
|
|
10
|
+
valid_ips = [ip for ip in ips if not ip.startswith('127.')]
|
|
11
|
+
return valid_ips
|
|
12
|
+
|
|
13
|
+
# 读取本机的ip地址
|
|
14
|
+
# return str
|
|
15
|
+
def get_ip():
|
|
16
|
+
# 优先读取活跃ip地址
|
|
17
|
+
routes = os.popen('route print').readlines()
|
|
18
|
+
for idx, item in enumerate(routes):
|
|
19
|
+
if ' 0.0.0.0 ' in item and len(item.split()) > 2:
|
|
20
|
+
return item.split()[-2]
|
|
21
|
+
|
|
22
|
+
# 取第一个地址
|
|
23
|
+
ips = get_ips()
|
|
24
|
+
if len(ips) > 0 :
|
|
25
|
+
return ips[0]
|
|
26
|
+
|
|
27
|
+
raise ValueError("Ip address not exists.")
|
|
28
|
+
|
|
29
|
+
def get_cache(fname=None):
|
|
30
|
+
if fname is None:
|
|
31
|
+
fname = int(time())
|
|
32
|
+
|
|
33
|
+
cpath = os.path.abspath(os.path.abspath(__file__))
|
|
34
|
+
print(cpath)
|
|
35
|
+
|
|
36
|
+
if __name__ == '__main__':
|
|
37
|
+
get_cache()
|