sensor-sdk 0.0.1__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.
Potentially problematic release.
This version of sensor-sdk might be problematic. Click here for more details.
- sensor/__init__.py +4 -0
- sensor/gforce.py +864 -0
- sensor/sensor_controller.py +223 -0
- sensor/sensor_data.py +91 -0
- sensor/sensor_data_context.py +569 -0
- sensor/sensor_device.py +75 -0
- sensor/sensor_profile.py +449 -0
- sensor/utils.py +28 -0
- sensor_sdk-0.0.1.dist-info/LICENSE.txt +21 -0
- sensor_sdk-0.0.1.dist-info/METADATA +300 -0
- sensor_sdk-0.0.1.dist-info/RECORD +14 -0
- sensor_sdk-0.0.1.dist-info/WHEEL +5 -0
- sensor_sdk-0.0.1.dist-info/top_level.txt +1 -0
- sensor_sdk-0.0.1.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
2
|
+
import threading
|
|
3
|
+
from typing import Callable, Dict, List, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
import bleak
|
|
6
|
+
|
|
7
|
+
from sensor import sensor_profile
|
|
8
|
+
from sensor.sensor_profile import DeviceStateEx, SensorProfile
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
from sensor.utils import start_loop, sync_timer, timer
|
|
12
|
+
from bleak import (
|
|
13
|
+
BleakScanner,
|
|
14
|
+
AdvertisementData,
|
|
15
|
+
)
|
|
16
|
+
SERVICE_GUID = "0000ffd0-0000-1000-8000-00805f9b34fb"
|
|
17
|
+
RFSTAR_SERVICE_GUID = "00001812-0000-1000-8000-00805f9b34fb"
|
|
18
|
+
|
|
19
|
+
class SensorController:
|
|
20
|
+
_instance_lock = threading.Lock()
|
|
21
|
+
def __new__(cls, *args, **kwargs):
|
|
22
|
+
if not hasattr(SensorController, "_instance"):
|
|
23
|
+
with SensorController._instance_lock:
|
|
24
|
+
if not hasattr(SensorController, "_instance"):
|
|
25
|
+
SensorController._instance = object.__new__(cls)
|
|
26
|
+
return SensorController._instance
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
SensorController 类的操作包括扫描蓝牙设备以及回调,创建SensorProfile等。
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
"""
|
|
34
|
+
初始化 SensorController 实例。
|
|
35
|
+
"""
|
|
36
|
+
self._event_loop = asyncio.new_event_loop()
|
|
37
|
+
self._event_thread = threading.Thread(target=start_loop, args=(self._event_loop,))
|
|
38
|
+
self._event_thread.daemon = True
|
|
39
|
+
self._event_thread.name = "SensorController event"
|
|
40
|
+
self._event_thread.start()
|
|
41
|
+
self._gforce_event_loop = asyncio.new_event_loop()
|
|
42
|
+
self._gforce_event_thread = threading.Thread(target=start_loop, args=(self._gforce_event_loop,))
|
|
43
|
+
self._gforce_event_thread.daemon = True
|
|
44
|
+
self._gforce_event_thread.name = "BLE operation"
|
|
45
|
+
self._gforce_event_thread.start()
|
|
46
|
+
self._scanner = BleakScanner(detection_callback=self._match_device,service_uuids=[SERVICE_GUID,RFSTAR_SERVICE_GUID])
|
|
47
|
+
self._is_scanning = False
|
|
48
|
+
self._device_callback: Callable[[List[sensor_profile.BLEDevice]], None] = None
|
|
49
|
+
self._device_callback_period = 0
|
|
50
|
+
self._enable_callback: Callable[[bool], None] = None
|
|
51
|
+
self._sensor_profiles: Dict[str, SensorProfile] = dict()
|
|
52
|
+
|
|
53
|
+
def __del__(self) -> None:
|
|
54
|
+
"""
|
|
55
|
+
反初始化 SensorController 类的实例。
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
self._event_loop.close()
|
|
59
|
+
self._gforce_event_loop.close()
|
|
60
|
+
|
|
61
|
+
def terminate(self):
|
|
62
|
+
for sensor in self._sensor_profiles.values():
|
|
63
|
+
if sensor.deviceState == DeviceStateEx.Connected or sensor.deviceState == DeviceStateEx.Ready:
|
|
64
|
+
sensor.destroy()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _match_device(self, _device: bleak.BLEDevice, adv: AdvertisementData):
|
|
68
|
+
if _device.name == None:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
SERVICE_GUID in adv.service_uuids
|
|
73
|
+
):
|
|
74
|
+
print("Device found: {0}, RSSI: {1}".format(_device.name, adv.rssi))
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def isScaning(self) -> bool:
|
|
81
|
+
"""
|
|
82
|
+
检查是否正在扫描。
|
|
83
|
+
|
|
84
|
+
:return: bool: 是否正在扫描
|
|
85
|
+
"""
|
|
86
|
+
return self._is_scanning
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def isEnable(self) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
检查蓝牙是否启用。
|
|
92
|
+
|
|
93
|
+
:return: bool: 是否启用
|
|
94
|
+
"""
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
@isEnable.setter
|
|
98
|
+
def onEnableCallback(self, callback: Callable[[bool], None]):
|
|
99
|
+
"""
|
|
100
|
+
设置蓝牙开关变化回调,当系统蓝牙开关发生变化时调用。
|
|
101
|
+
|
|
102
|
+
:param callback (Callable[[bool], None]): 扫描蓝牙开关状态回调函数
|
|
103
|
+
"""
|
|
104
|
+
self._enable_callback = callback
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def hasDeviceCallback(self) -> bool:
|
|
108
|
+
"""
|
|
109
|
+
检查是否有扫描设备回调。
|
|
110
|
+
|
|
111
|
+
:return: bool: 是否有设备回调
|
|
112
|
+
"""
|
|
113
|
+
return self._device_callback != None
|
|
114
|
+
|
|
115
|
+
@hasDeviceCallback.setter
|
|
116
|
+
def onDeviceFoundCallback(self, callback: Callable[[List[sensor_profile.BLEDevice]], None]):
|
|
117
|
+
"""
|
|
118
|
+
设置扫描设备回调。
|
|
119
|
+
|
|
120
|
+
:param callback (Callable[[List[BLEDevice]], None]): 扫描设备回调函数
|
|
121
|
+
"""
|
|
122
|
+
self._device_callback = callback
|
|
123
|
+
|
|
124
|
+
async def _device_scan_callback(self, found_devices: Dict[str, Tuple[bleak.BLEDevice, AdvertisementData]]):
|
|
125
|
+
if self._device_callback:
|
|
126
|
+
devices:List[sensor_profile.BLEDevice] = list()
|
|
127
|
+
deviceMap :Dict[str, SensorProfile] = self._sensor_profiles.copy()
|
|
128
|
+
for mac in found_devices:
|
|
129
|
+
device = found_devices[mac][0]
|
|
130
|
+
adv = found_devices[mac][1]
|
|
131
|
+
if SERVICE_GUID in adv.service_uuids:
|
|
132
|
+
if deviceMap.get(mac) != None:
|
|
133
|
+
devices.append(self._sensor_profiles[mac].BLEDevice)
|
|
134
|
+
else:
|
|
135
|
+
newSensor = SensorProfile(device, adv,self._gforce_event_loop)
|
|
136
|
+
deviceMap[mac] = newSensor
|
|
137
|
+
devices.append(newSensor.BLEDevice)
|
|
138
|
+
self._sensor_profiles = deviceMap
|
|
139
|
+
self._device_callback(devices)
|
|
140
|
+
|
|
141
|
+
if self._is_scanning:
|
|
142
|
+
timer(self._gforce_event_loop, 0, self._startScan())
|
|
143
|
+
|
|
144
|
+
async def _startScan(self) -> bool:
|
|
145
|
+
devices = await self._scanner.discover(timeout=self._device_callback_period / 1000, return_adv=True)
|
|
146
|
+
timer(self._event_loop, 0, self._device_scan_callback(devices))
|
|
147
|
+
def startScan(self, periodInMs: int) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
开始扫描。
|
|
150
|
+
|
|
151
|
+
:param periodInMs (int): 扫描时长(毫秒)
|
|
152
|
+
|
|
153
|
+
:return: bool: 扫描是否成功启动
|
|
154
|
+
"""
|
|
155
|
+
if (self._is_scanning):
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
self._is_scanning = True
|
|
159
|
+
self._device_callback_period = periodInMs
|
|
160
|
+
|
|
161
|
+
timer(self._gforce_event_loop, 0, self._startScan())
|
|
162
|
+
return True
|
|
163
|
+
|
|
164
|
+
def stopScan(self) -> None:
|
|
165
|
+
"""
|
|
166
|
+
停止扫描。
|
|
167
|
+
"""
|
|
168
|
+
if (not self._is_scanning):
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
self._is_scanning = False
|
|
172
|
+
|
|
173
|
+
def requireSensor(self, device: sensor_profile.BLEDevice) -> Optional[SensorProfile]:
|
|
174
|
+
"""
|
|
175
|
+
根据设备信息获取或创建SensorProfile。
|
|
176
|
+
|
|
177
|
+
:param device (BLEDevice): 蓝牙设备信息
|
|
178
|
+
|
|
179
|
+
:return: Optional[SensorProfile]: SensorProfile
|
|
180
|
+
"""
|
|
181
|
+
if self._sensor_profiles.get(device.Address) == None:
|
|
182
|
+
newSensor = SensorProfile(device)
|
|
183
|
+
self._sensor_profiles[device.Address] = newSensor
|
|
184
|
+
|
|
185
|
+
return self._sensor_profiles[device.Address]
|
|
186
|
+
|
|
187
|
+
def getSensor(self, deviceMac: str) -> Optional[SensorProfile]:
|
|
188
|
+
"""
|
|
189
|
+
根据设备 MAC 地址获取SensorProfile。
|
|
190
|
+
|
|
191
|
+
:params deviceMac (str): 设备 MAC 地址
|
|
192
|
+
|
|
193
|
+
:return: Optional[SensorProfile]: SensorProfile
|
|
194
|
+
"""
|
|
195
|
+
return self._sensor_profiles[deviceMac]
|
|
196
|
+
|
|
197
|
+
def getConnectedSensors(self) -> List[SensorProfile]:
|
|
198
|
+
"""
|
|
199
|
+
获取已连接的SensorProfile列表。
|
|
200
|
+
|
|
201
|
+
:return: List[SensorProfile]: 已连接的SensorProfile列表
|
|
202
|
+
"""
|
|
203
|
+
sensors:List[SensorProfile] = list()
|
|
204
|
+
for sensor in self._sensor_profiles.values():
|
|
205
|
+
if sensor.deviceState == DeviceStateEx.Connected or sensor.deviceState == DeviceStateEx.Ready:
|
|
206
|
+
sensors.append(sensor)
|
|
207
|
+
|
|
208
|
+
return sensors
|
|
209
|
+
|
|
210
|
+
def getConnectedDevices(self) -> List[sensor_profile.BLEDevice]:
|
|
211
|
+
"""
|
|
212
|
+
获取已连接的蓝牙设备列表。
|
|
213
|
+
|
|
214
|
+
:return: List[BLEDevice]: 已连接的蓝牙设备列表
|
|
215
|
+
"""
|
|
216
|
+
devices:List[sensor_profile.BLEDevice] = list()
|
|
217
|
+
for sensor in self._sensor_profiles.values():
|
|
218
|
+
if sensor.deviceState == DeviceStateEx.Connected or sensor.deviceState == DeviceStateEx.Ready:
|
|
219
|
+
devices.append(sensor.BLEDevice)
|
|
220
|
+
|
|
221
|
+
return devices
|
|
222
|
+
|
|
223
|
+
SensorControllerInstance = SensorController()
|
sensor/sensor_data.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from enum import Enum, IntEnum
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
# 一个采样数据
|
|
5
|
+
# 该类用于存储单个采样数据的相关信息,包括数据值、阻抗、饱和度、采样索引和是否丢包的标志
|
|
6
|
+
class Sample:
|
|
7
|
+
# """
|
|
8
|
+
# Initialize a Sample instance.
|
|
9
|
+
|
|
10
|
+
# :param data: 数据值,单位为 uV
|
|
11
|
+
# :param impedance: 阻抗值,单位为 Ω
|
|
12
|
+
# :param saturation: 饱和度值,单位为 % ,值 0-100
|
|
13
|
+
# :param sample_index: 采样索引,用于标识采样的顺序
|
|
14
|
+
# :param is_lost: 是否丢包的标志,True 表示丢包,False 表示正常
|
|
15
|
+
# """
|
|
16
|
+
# def __init__(self, data: int, impedance: int, saturation: int, sample_index: int, is_lost: bool):
|
|
17
|
+
# self.data = data
|
|
18
|
+
# self.impedance = impedance
|
|
19
|
+
# self.saturation = saturation
|
|
20
|
+
# self.sampleIndex = sample_index
|
|
21
|
+
# self.isLost = is_lost
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.rawData = 0
|
|
25
|
+
self.data = 0
|
|
26
|
+
self.impedance = 0
|
|
27
|
+
self.saturation = 0
|
|
28
|
+
self.sampleIndex = 0
|
|
29
|
+
self.isLost = False
|
|
30
|
+
self.timeStampInMs = 0
|
|
31
|
+
self.channelIndex = 0
|
|
32
|
+
self.sampleIndex = 0
|
|
33
|
+
|
|
34
|
+
# 对应 DataType 枚举
|
|
35
|
+
# 该枚举类定义了不同类型的数据,用于区分传感器采集的不同类型的数据
|
|
36
|
+
class DataType(IntEnum):
|
|
37
|
+
NTF_ACC = 0x1 # 加速度,用于标识加速度传感器采集的数据
|
|
38
|
+
NTF_GYRO = 0x2 # 陀螺仪,用于标识陀螺仪传感器采集的数据
|
|
39
|
+
NTF_EEG = 0x10 # EEG,用于标识脑电传感器采集的数据
|
|
40
|
+
NTF_ECG = 0x11 # ECG,用于标识心电传感器采集的数据
|
|
41
|
+
NTF_IMPEDANCE = 0x12, # 阻抗数据
|
|
42
|
+
NTF_IMU = 0x13, # 包含ACC和GYRO数据
|
|
43
|
+
NTF_ADS = 0x14, # 无单位ads数据
|
|
44
|
+
NTF_BRTH = 0x15 # 呼吸,用于标识呼吸传感器采集的数据
|
|
45
|
+
|
|
46
|
+
# 一次采样的数据,包含多个通道的数据,channal_samples 为一个二维数组, 第一个维度为通道索引,第二个维度为采样索引
|
|
47
|
+
# 该类用于存储一次采样的完整数据,包括设备 MAC 地址、数据类型、采样率、通道数量、包中采样数量以及通道采样数据
|
|
48
|
+
class SensorData:
|
|
49
|
+
# """
|
|
50
|
+
# Initialize a SensorData instance.
|
|
51
|
+
|
|
52
|
+
# :param device_mac: The MAC address of the device.
|
|
53
|
+
# :param data_type: The type of data being collected.
|
|
54
|
+
# :param sample_rate: The rate at which samples are collected.
|
|
55
|
+
# :param channel_count: The number of channels in the data.
|
|
56
|
+
# :param package_sample_count: The number of samples in the package.
|
|
57
|
+
# :param channel_samples: A list of lists containing the sample data for each channel.
|
|
58
|
+
# """
|
|
59
|
+
# def __init__(self, device_mac: str, data_type: DataType, sample_rate: int, channel_count: int,
|
|
60
|
+
# package_sample_count: int, channel_samples: List[List[Sample]]):
|
|
61
|
+
# self.deviceMac = device_mac
|
|
62
|
+
# self.dataType = data_type
|
|
63
|
+
# self.sampleRate = sample_rate
|
|
64
|
+
# self.channelCount = channel_count
|
|
65
|
+
# self.packageSampleCount = package_sample_count
|
|
66
|
+
# self.channelSamples = channel_samples
|
|
67
|
+
# self.lastPackageCounter = 0
|
|
68
|
+
# self.lastPackageIndex = 0
|
|
69
|
+
# self.resolutionBits = 0
|
|
70
|
+
# self.channelMask = 0
|
|
71
|
+
# self.minPackageSampleCount = 0
|
|
72
|
+
# self.K = 0
|
|
73
|
+
|
|
74
|
+
def __init__(self):
|
|
75
|
+
self.deviceMac = ""
|
|
76
|
+
self.dataType = DataType.NTF_EEG
|
|
77
|
+
self.sampleRate = 0
|
|
78
|
+
self.channelCount = 0
|
|
79
|
+
self.packageSampleCount = 0
|
|
80
|
+
self.channelSamples:List[List[Sample]] = list()
|
|
81
|
+
self.lastPackageCounter = 0
|
|
82
|
+
self.lastPackageIndex = 0
|
|
83
|
+
self.resolutionBits = 0
|
|
84
|
+
self.channelMask = 0
|
|
85
|
+
self.minPackageSampleCount = 0
|
|
86
|
+
self.K = 0
|
|
87
|
+
|
|
88
|
+
def clear(self):
|
|
89
|
+
self.channelSamples.clear()
|
|
90
|
+
self.lastPackageCounter = 0
|
|
91
|
+
self.lastPackageIndex = 0
|