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.

@@ -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