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.
Files changed (68) hide show
  1. qldsdk_serial-0.1.0/PKG-INFO +30 -0
  2. qldsdk_serial-0.1.0/README.md +8 -0
  3. qldsdk_serial-0.1.0/setup.cfg +32 -0
  4. qldsdk_serial-0.1.0/setup.py +26 -0
  5. qldsdk_serial-0.1.0/src/qldsdk/__init__.py +4 -0
  6. qldsdk_serial-0.1.0/src/qldsdk/core/__init__.py +8 -0
  7. qldsdk_serial-0.1.0/src/qldsdk/core/crc/__init__.py +5 -0
  8. qldsdk_serial-0.1.0/src/qldsdk/core/crc/crctools.py +95 -0
  9. qldsdk_serial-0.1.0/src/qldsdk/core/device.py +34 -0
  10. qldsdk_serial-0.1.0/src/qldsdk/core/dict_utils.py +12 -0
  11. qldsdk_serial-0.1.0/src/qldsdk/core/entity/__init__.py +222 -0
  12. qldsdk_serial-0.1.0/src/qldsdk/core/exception.py +0 -0
  13. qldsdk_serial-0.1.0/src/qldsdk/core/filter/__init__.py +1 -0
  14. qldsdk_serial-0.1.0/src/qldsdk/core/filter/norch.py +59 -0
  15. qldsdk_serial-0.1.0/src/qldsdk/core/interface/__init__.py +10 -0
  16. qldsdk_serial-0.1.0/src/qldsdk/core/interface/analyzer.py +2 -0
  17. qldsdk_serial-0.1.0/src/qldsdk/core/interface/collector.py +10 -0
  18. qldsdk_serial-0.1.0/src/qldsdk/core/interface/device.py +2 -0
  19. qldsdk_serial-0.1.0/src/qldsdk/core/interface/parser.py +13 -0
  20. qldsdk_serial-0.1.0/src/qldsdk/core/interface/stimulator.py +2 -0
  21. qldsdk_serial-0.1.0/src/qldsdk/core/interface/store.py +2 -0
  22. qldsdk_serial-0.1.0/src/qldsdk/core/local.py +37 -0
  23. qldsdk_serial-0.1.0/src/qldsdk/core/message/__init__.py +2 -0
  24. qldsdk_serial-0.1.0/src/qldsdk/core/message/command.py +333 -0
  25. qldsdk_serial-0.1.0/src/qldsdk/core/message/tcp.py +0 -0
  26. qldsdk_serial-0.1.0/src/qldsdk/core/message/udp.py +104 -0
  27. qldsdk_serial-0.1.0/src/qldsdk/core/network/__init__.py +34 -0
  28. qldsdk_serial-0.1.0/src/qldsdk/core/network/monitor.py +55 -0
  29. qldsdk_serial-0.1.0/src/qldsdk/core/persist/__init__.py +3 -0
  30. qldsdk_serial-0.1.0/src/qldsdk/core/persist/ars_edf.py +257 -0
  31. qldsdk_serial-0.1.0/src/qldsdk/core/persist/edf.py +205 -0
  32. qldsdk_serial-0.1.0/src/qldsdk/core/persist/rsc_edf.py +346 -0
  33. qldsdk_serial-0.1.0/src/qldsdk/core/persist/stream.py +161 -0
  34. qldsdk_serial-0.1.0/src/qldsdk/core/persist/trigger_log.py +86 -0
  35. qldsdk_serial-0.1.0/src/qldsdk/core/rsc/__init__.py +2 -0
  36. qldsdk_serial-0.1.0/src/qldsdk/core/rsc/paradigm.py +570 -0
  37. qldsdk_serial-0.1.0/src/qldsdk/core/rsc/type.py +87 -0
  38. qldsdk_serial-0.1.0/src/qldsdk/core/serial.py +188 -0
  39. qldsdk_serial-0.1.0/src/qldsdk/core/utils.py +75 -0
  40. qldsdk_serial-0.1.0/src/qldsdk/extends/__init__.py +1 -0
  41. qldsdk_serial-0.1.0/src/qldsdk/extends/check_and_enhance.py +104 -0
  42. qldsdk_serial-0.1.0/src/qldsdk/serial/__init__.py +2 -0
  43. qldsdk_serial-0.1.0/src/qldsdk/serial/lse/__init__.py +3 -0
  44. qldsdk_serial-0.1.0/src/qldsdk/serial/lse/app.py +281 -0
  45. qldsdk_serial-0.1.0/src/qldsdk/serial/lse/ars_lse.py +349 -0
  46. qldsdk_serial-0.1.0/src/qldsdk/serial/lse/config.py +419 -0
  47. qldsdk_serial-0.1.0/src/qldsdk/serial/lse/exception.py +13 -0
  48. qldsdk_serial-0.1.0/src/qldsdk/serial/lse/message.py +397 -0
  49. qldsdk_serial-0.1.0/src/qldsdk/serial/lse/type.py +103 -0
  50. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/__init__.py +3 -0
  51. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/command/__init__.py +383 -0
  52. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/device/__init__.py +2 -0
  53. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/device/c64_rs.py +15 -0
  54. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/device/serial_base.py +759 -0
  55. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/__init__.py +3 -0
  56. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/command.py +10 -0
  57. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/device.py +85 -0
  58. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/handler.py +9 -0
  59. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/interface/parser.py +18 -0
  60. qldsdk_serial-0.1.0/src/qldsdk/serial/rsc/message.py +264 -0
  61. qldsdk_serial-0.1.0/src/qldsdk/third/__init__.py +1 -0
  62. qldsdk_serial-0.1.0/src/qldsdk/third/alpha_alg.py +247 -0
  63. qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/PKG-INFO +30 -0
  64. qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/SOURCES.txt +67 -0
  65. qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/dependency_links.txt +1 -0
  66. qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/requires.txt +10 -0
  67. qldsdk_serial-0.1.0/src/qldsdk_serial.egg-info/top_level.txt +1 -0
  68. 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,8 @@
1
+ ## 1. 项目名称
2
+ qldsdk-serial
3
+
4
+ ## 2. 项目说明
5
+
6
+ qldsdk-serial 是一个功能完整的脑电设备控制和数据采集工具包,采用模块化设计,支持多种设备类型和功能。通过工厂模式和接口定义,实现了设备的统一管理和扩展。系统架构清晰,核心功能完善,为脑电设备的使用提供了便捷的编程接口。
7
+
8
+ 该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,4 @@
1
+
2
+ from .core import *
3
+ from .serial import *
4
+ from .third import *
@@ -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,5 @@
1
+ from .crctools import crc16
2
+
3
+ # packet crc validate
4
+ def check_crc(data):
5
+ return int.from_bytes(data[-2:], 'little') == crc16(data[:-2])
@@ -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,10 @@
1
+ from .parser import IParser
2
+
3
+ from .device import IDevice
4
+
5
+
6
+ '''
7
+ 设备消息解析器
8
+ '''
9
+ class DeviceMessageParser:
10
+ pass
@@ -0,0 +1,2 @@
1
+ class IAnalyzer:
2
+ pass
@@ -0,0 +1,10 @@
1
+ class ICollector:
2
+ def __init__(self):
3
+ pass
4
+
5
+ def collect_data(self):
6
+ pass
7
+
8
+ class Collector(ICollector):
9
+ def __init__(self):
10
+ super().__init__()
@@ -0,0 +1,2 @@
1
+ class IDevice:
2
+ pass
@@ -0,0 +1,13 @@
1
+ class IParser:
2
+ pass
3
+
4
+ class Parser(IParser):
5
+ def __init__(self):
6
+ super().__init__()
7
+
8
+ class MessageParser(Parser):
9
+ def __init__(self):
10
+ super().__init__()
11
+
12
+ def parse(self, data):
13
+ pass
@@ -0,0 +1,2 @@
1
+ class IStimulator:
2
+ pass
@@ -0,0 +1,2 @@
1
+ class IStore:
2
+ pass
@@ -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()
@@ -0,0 +1,2 @@
1
+ from .command import *
2
+ from .udp import UDPMessage