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,569 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections import deque
|
|
3
|
+
from queue import Queue
|
|
4
|
+
from typing import Deque, List
|
|
5
|
+
|
|
6
|
+
from sensor.gforce import DataSubscription, GForce
|
|
7
|
+
from sensor.sensor_data import DataType, Sample, SensorData
|
|
8
|
+
|
|
9
|
+
from enum import Enum, IntEnum
|
|
10
|
+
|
|
11
|
+
from sensor.sensor_device import DeviceInfo
|
|
12
|
+
from sensor.utils import timer
|
|
13
|
+
|
|
14
|
+
class SensorDataType(IntEnum):
|
|
15
|
+
DATA_TYPE_EEG = 0
|
|
16
|
+
DATA_TYPE_ECG = 1
|
|
17
|
+
DATA_TYPE_ACC = 2
|
|
18
|
+
DATA_TYPE_GYRO = 3
|
|
19
|
+
DATA_TYPE_BRTH = 4
|
|
20
|
+
DATA_TYPE_COUNT = 5
|
|
21
|
+
|
|
22
|
+
# 枚举 FeatureMaps 的 Python 实现
|
|
23
|
+
class FeatureMaps(Enum):
|
|
24
|
+
GFD_FEAT_EMG = 0x000002000
|
|
25
|
+
GFD_FEAT_EEG = 0x000400000
|
|
26
|
+
GFD_FEAT_ECG = 0x000800000
|
|
27
|
+
GFD_FEAT_IMPEDANCE = 0x001000000
|
|
28
|
+
GFD_FEAT_IMU = 0x002000000
|
|
29
|
+
GFD_FEAT_ADS = 0x004000000
|
|
30
|
+
GFD_FEAT_BRTH = 0x008000000
|
|
31
|
+
GFD_FEAT_CONCAT_BLE = 0x80000000
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SensorProfileDataCtx:
|
|
35
|
+
def __init__(self, gForce: GForce, deviceMac: str, buf: Queue[bytes]):
|
|
36
|
+
self.featureMap = 0
|
|
37
|
+
self.notifyDataFlag:DataSubscription = 0
|
|
38
|
+
|
|
39
|
+
self.gForce = gForce
|
|
40
|
+
self.deviceMac = deviceMac
|
|
41
|
+
self._device_info: DeviceInfo = None
|
|
42
|
+
|
|
43
|
+
self._is_running = True
|
|
44
|
+
self._is_data_transfering = False
|
|
45
|
+
self.isUniversalStream: bool = gForce._is_universal_stream
|
|
46
|
+
self._rawDataBuffer: Queue[bytes] = buf
|
|
47
|
+
self._concatDataBuffer = bytearray()
|
|
48
|
+
|
|
49
|
+
self.sensorDatas: List[SensorData] = list()
|
|
50
|
+
for idx in range(0, SensorDataType.DATA_TYPE_COUNT):
|
|
51
|
+
self.sensorDatas.append(SensorData())
|
|
52
|
+
self.impedanceData: List[float] = list()
|
|
53
|
+
self.saturationData: List[float] = list()
|
|
54
|
+
|
|
55
|
+
def close(self):
|
|
56
|
+
self._is_running = False
|
|
57
|
+
|
|
58
|
+
def clear(self):
|
|
59
|
+
for sensorData in self.sensorDatas:
|
|
60
|
+
sensorData.clear()
|
|
61
|
+
self.impedanceData.clear()
|
|
62
|
+
self.saturationData.clear()
|
|
63
|
+
self._concatDataBuffer.clear()
|
|
64
|
+
self._rawDataBuffer.queue.clear()
|
|
65
|
+
|
|
66
|
+
def reset(self):
|
|
67
|
+
self.notifyDataFlag = 0
|
|
68
|
+
self.clear()
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def isDataTransfering(self) -> bool:
|
|
72
|
+
"""
|
|
73
|
+
检查传感器是否正在进行数据传输。
|
|
74
|
+
|
|
75
|
+
:return: bool: 如果传感器正在进行数据传输,返回 True;否则返回 False。
|
|
76
|
+
"""
|
|
77
|
+
return self._is_data_transfering
|
|
78
|
+
def hasInit(self):
|
|
79
|
+
return self.featureMap != 0 and self.notifyDataFlag != 0
|
|
80
|
+
def hasEMG(self):
|
|
81
|
+
return (self.featureMap & FeatureMaps.GFD_FEAT_EMG.value) != 0
|
|
82
|
+
|
|
83
|
+
def hasEEG(self):
|
|
84
|
+
return (self.featureMap & FeatureMaps.GFD_FEAT_EEG.value) != 0
|
|
85
|
+
|
|
86
|
+
def hasECG(self):
|
|
87
|
+
return (self.featureMap & FeatureMaps.GFD_FEAT_ECG.value) != 0
|
|
88
|
+
|
|
89
|
+
def hasImpedance(self):
|
|
90
|
+
return (self.featureMap & FeatureMaps.GFD_FEAT_IMPEDANCE.value) != 0
|
|
91
|
+
|
|
92
|
+
def hasIMU(self):
|
|
93
|
+
return (self.featureMap & FeatureMaps.GFD_FEAT_IMU.value) != 0
|
|
94
|
+
|
|
95
|
+
def hasBrth(self):
|
|
96
|
+
return (self.featureMap & FeatureMaps.GFD_FEAT_BRTH.value) != 0
|
|
97
|
+
|
|
98
|
+
def hasConcatBLE(self):
|
|
99
|
+
return (self.featureMap & FeatureMaps.GFD_FEAT_CONCAT_BLE.value) != 0
|
|
100
|
+
|
|
101
|
+
async def initEEG(self,packageCount:int)-> int:
|
|
102
|
+
config = await self.gForce.get_eeg_raw_data_config()
|
|
103
|
+
cap = await self.gForce.get_eeg_raw_data_cap()
|
|
104
|
+
data = SensorData()
|
|
105
|
+
data.deviceMac = self.deviceMac
|
|
106
|
+
data.dataType = DataType.NTF_EEG
|
|
107
|
+
data.sampleRate = config.fs
|
|
108
|
+
data.resolutionBits = config.resolution
|
|
109
|
+
data.channelCount = cap.channel_count
|
|
110
|
+
data.channelMask = config.channel_mask
|
|
111
|
+
data.minPackageSampleCount = packageCount
|
|
112
|
+
data.packageSampleCount = config.batch_len
|
|
113
|
+
data.K = config.K
|
|
114
|
+
data.clear()
|
|
115
|
+
self.sensorDatas[SensorDataType.DATA_TYPE_EEG] = data
|
|
116
|
+
self.notifyDataFlag |= DataSubscription.DNF_EEG
|
|
117
|
+
return data.channelCount
|
|
118
|
+
|
|
119
|
+
async def initECG(self,packageCount:int)-> int:
|
|
120
|
+
config = await self.gForce.get_ecg_raw_data_config()
|
|
121
|
+
data = SensorData()
|
|
122
|
+
data.deviceMac = self.deviceMac
|
|
123
|
+
data.dataType = DataType.NTF_ECG
|
|
124
|
+
data.sampleRate = config.fs
|
|
125
|
+
data.resolutionBits = config.resolution
|
|
126
|
+
data.channelCount = 1
|
|
127
|
+
data.channelMask = config.channel_mask
|
|
128
|
+
data.minPackageSampleCount = packageCount
|
|
129
|
+
data.packageSampleCount = config.batch_len
|
|
130
|
+
data.K = config.K
|
|
131
|
+
data.clear()
|
|
132
|
+
self.sensorDatas[SensorDataType.DATA_TYPE_ECG] = data
|
|
133
|
+
self.notifyDataFlag |= DataSubscription.DNF_ECG
|
|
134
|
+
return data.channelCount
|
|
135
|
+
|
|
136
|
+
async def initIMU(self,packageCount:int)-> int:
|
|
137
|
+
config = await self.gForce.get_imu_raw_data_config()
|
|
138
|
+
data = SensorData()
|
|
139
|
+
data.deviceMac = self.deviceMac
|
|
140
|
+
data.dataType = DataType.NTF_ACC
|
|
141
|
+
data.sampleRate = config.fs
|
|
142
|
+
data.resolutionBits = 16
|
|
143
|
+
data.channelCount = config.channel_count
|
|
144
|
+
data.channelMask = 255
|
|
145
|
+
data.minPackageSampleCount = packageCount
|
|
146
|
+
data.packageSampleCount = config.batch_len
|
|
147
|
+
data.K = config.accK
|
|
148
|
+
data.clear()
|
|
149
|
+
self.sensorDatas[SensorDataType.DATA_TYPE_ACC] = data
|
|
150
|
+
|
|
151
|
+
data = SensorData()
|
|
152
|
+
data.deviceMac = self.deviceMac
|
|
153
|
+
data.dataType = DataType.NTF_GYRO
|
|
154
|
+
data.sampleRate = config.fs
|
|
155
|
+
data.resolutionBits = 16
|
|
156
|
+
data.channelCount = config.channel_count
|
|
157
|
+
data.channelMask = 255
|
|
158
|
+
data.minPackageSampleCount = packageCount
|
|
159
|
+
data.packageSampleCount = config.batch_len
|
|
160
|
+
data.K = config.gyroK
|
|
161
|
+
data.clear()
|
|
162
|
+
self.sensorDatas[SensorDataType.DATA_TYPE_GYRO] = data
|
|
163
|
+
|
|
164
|
+
self.notifyDataFlag |= DataSubscription.DNF_IMU
|
|
165
|
+
|
|
166
|
+
return data.channelCount
|
|
167
|
+
|
|
168
|
+
async def initBrth(self,packageCount:int)-> int:
|
|
169
|
+
config = await self.gForce.get_brth_raw_data_config()
|
|
170
|
+
data = SensorData()
|
|
171
|
+
data.deviceMac = self.deviceMac
|
|
172
|
+
data.dataType = DataType.NTF_BRTH
|
|
173
|
+
data.sampleRate = config.fs
|
|
174
|
+
data.resolutionBits = config.resolution
|
|
175
|
+
data.channelCount = 1
|
|
176
|
+
data.channelMask = config.channel_mask
|
|
177
|
+
data.minPackageSampleCount = packageCount
|
|
178
|
+
data.packageSampleCount = config.batch_len
|
|
179
|
+
data.K = config.K
|
|
180
|
+
data.clear()
|
|
181
|
+
self.sensorDatas[SensorDataType.DATA_TYPE_BRTH] = data
|
|
182
|
+
self.notifyDataFlag |= DataSubscription.DNF_ECG
|
|
183
|
+
return data.channelCount
|
|
184
|
+
|
|
185
|
+
async def initDataTransfer(self,isGetFeature:bool)-> int:
|
|
186
|
+
if (isGetFeature):
|
|
187
|
+
self.featureMap = await self.gForce.get_feature_map()
|
|
188
|
+
if (self.hasImpedance()):
|
|
189
|
+
self.notifyDataFlag |= DataSubscription.DNF_IMPEDANCE
|
|
190
|
+
return self.featureMap
|
|
191
|
+
else:
|
|
192
|
+
await self.gForce.set_subscription(self.notifyDataFlag)
|
|
193
|
+
return self.notifyDataFlag
|
|
194
|
+
|
|
195
|
+
async def fetchDeviceInfo(self)->DeviceInfo:
|
|
196
|
+
info = DeviceInfo()
|
|
197
|
+
info.MTUSize = self.gForce.client.mtu_size
|
|
198
|
+
info.DeviceName = await self.gForce.get_device_name()
|
|
199
|
+
info.ModelName = await self.gForce.get_model_number()
|
|
200
|
+
info.HardwareVersion = await self.gForce.get_hardware_revision()
|
|
201
|
+
info.FirmwareVersion = await self.gForce.get_firmware_revision()
|
|
202
|
+
return info
|
|
203
|
+
|
|
204
|
+
async def init(self, packageCount:int)->bool:
|
|
205
|
+
try:
|
|
206
|
+
info = await self.fetchDeviceInfo()
|
|
207
|
+
|
|
208
|
+
await self.initDataTransfer(True)
|
|
209
|
+
|
|
210
|
+
if (self.hasImpedance()):
|
|
211
|
+
self.notifyDataFlag |= DataSubscription.DNF_IMPEDANCE
|
|
212
|
+
|
|
213
|
+
if (self.hasEEG()):
|
|
214
|
+
info.EegChannelCount = await self.initEEG(packageCount)
|
|
215
|
+
|
|
216
|
+
if (self.hasECG()):
|
|
217
|
+
info.EcgChannelCount = await self.initECG(packageCount)
|
|
218
|
+
|
|
219
|
+
if (self.hasBrth()):
|
|
220
|
+
info.BrthChannelCount = await self.initBrth(packageCount)
|
|
221
|
+
|
|
222
|
+
if (self.hasIMU()):
|
|
223
|
+
imuChannelCount = await self.initIMU(packageCount)
|
|
224
|
+
info.AccChannelCount = imuChannelCount
|
|
225
|
+
info.GyroChannelCount = imuChannelCount
|
|
226
|
+
|
|
227
|
+
self._device_info = info
|
|
228
|
+
|
|
229
|
+
if (not self.isUniversalStream):
|
|
230
|
+
await self.initDataTransfer(False)
|
|
231
|
+
|
|
232
|
+
return True
|
|
233
|
+
except Exception as e:
|
|
234
|
+
print(e)
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
async def start_streaming(self) -> bool:
|
|
238
|
+
if (self._is_data_transfering):
|
|
239
|
+
return True
|
|
240
|
+
self._is_data_transfering = True
|
|
241
|
+
self._rawDataBuffer.queue.clear()
|
|
242
|
+
if (not self.isUniversalStream):
|
|
243
|
+
await self.gForce.start_streaming(self._rawDataBuffer)
|
|
244
|
+
return True
|
|
245
|
+
else:
|
|
246
|
+
await self.gForce.set_subscription(self.notifyDataFlag)
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
async def stop_streaming(self) -> bool:
|
|
250
|
+
if (not self._is_data_transfering):
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
self._is_data_transfering = False
|
|
254
|
+
|
|
255
|
+
if (not self.isUniversalStream):
|
|
256
|
+
await self.gForce.stop_streaming()
|
|
257
|
+
return True
|
|
258
|
+
else:
|
|
259
|
+
await self.gForce.set_subscription(0)
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
def process_data(self, buf: Queue[SensorData]):
|
|
263
|
+
try:
|
|
264
|
+
data: bytes = self._rawDataBuffer.get_nowait()
|
|
265
|
+
except Exception as e:
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
self._processDataPackage(data, buf)
|
|
269
|
+
self._rawDataBuffer.task_done()
|
|
270
|
+
|
|
271
|
+
def _processDataPackage(self,data: bytes, buf: Queue[SensorData]):
|
|
272
|
+
v = data[0]
|
|
273
|
+
if v == DataType.NTF_IMPEDANCE:
|
|
274
|
+
offset = 1
|
|
275
|
+
# packageIndex = ((data[offset + 1] & 0xff) << 8) | (data[offset] & 0xff)
|
|
276
|
+
offset += 2
|
|
277
|
+
|
|
278
|
+
impedanceData = []
|
|
279
|
+
saturationData = []
|
|
280
|
+
|
|
281
|
+
dataCount = (len(data) - 3) // 4 // 2
|
|
282
|
+
for index in range(dataCount):
|
|
283
|
+
impedance_bytes = data[offset:offset + 4]
|
|
284
|
+
impedance = int.from_bytes(impedance_bytes, byteorder='little')
|
|
285
|
+
offset += 4
|
|
286
|
+
impedanceData.append(impedance)
|
|
287
|
+
|
|
288
|
+
for index in range(dataCount):
|
|
289
|
+
saturation_bytes = data[offset:offset + 4]
|
|
290
|
+
saturation = int.from_bytes(saturation_bytes, byteorder='little')
|
|
291
|
+
offset += 4
|
|
292
|
+
saturationData.append(saturation / 10) # firmware value range 0 - 1000
|
|
293
|
+
|
|
294
|
+
self.impedanceData = impedanceData
|
|
295
|
+
self.saturationData = saturationData
|
|
296
|
+
|
|
297
|
+
elif v == DataType.NTF_EEG:
|
|
298
|
+
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EEG]
|
|
299
|
+
if self.checkReadSamples(data, sensor_data, 3, 0):
|
|
300
|
+
self.sendSensorData(sensor_data, buf)
|
|
301
|
+
elif v == DataType.NTF_ECG:
|
|
302
|
+
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_ECG]
|
|
303
|
+
if self.checkReadSamples(data, sensor_data, 3, 0):
|
|
304
|
+
self.sendSensorData(sensor_data, buf)
|
|
305
|
+
elif v == DataType.NTF_BRTH:
|
|
306
|
+
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_BRTH]
|
|
307
|
+
if self.checkReadSamples(data, sensor_data, 3, 0):
|
|
308
|
+
self.sendSensorData(sensor_data, buf)
|
|
309
|
+
elif v == DataType.NTF_IMU:
|
|
310
|
+
sensor_data_acc = self.sensorDatas[SensorDataType.DATA_TYPE_ACC]
|
|
311
|
+
if self.checkReadSamples(data, sensor_data_acc, 3, 6):
|
|
312
|
+
self.sendSensorData(sensor_data_acc, buf)
|
|
313
|
+
|
|
314
|
+
sensor_data_gyro = self.sensorDatas[SensorDataType.DATA_TYPE_GYRO]
|
|
315
|
+
if self.checkReadSamples(data, sensor_data_gyro, 9, 6):
|
|
316
|
+
self.sendSensorData(sensor_data_gyro, buf)
|
|
317
|
+
|
|
318
|
+
def checkReadSamples(self, data: bytes, sensorData: SensorData, dataOffset: int, dataGap: int):
|
|
319
|
+
offset = 1
|
|
320
|
+
v = data[0]
|
|
321
|
+
if not self._is_data_transfering:
|
|
322
|
+
return False
|
|
323
|
+
try:
|
|
324
|
+
|
|
325
|
+
packageIndex = ((data[offset + 1] & 0xff) << 8) | (data[offset] & 0xff)
|
|
326
|
+
offset += 2
|
|
327
|
+
newPackageIndex = packageIndex
|
|
328
|
+
lastPackageIndex = sensorData.lastPackageIndex
|
|
329
|
+
|
|
330
|
+
if packageIndex < lastPackageIndex:
|
|
331
|
+
packageIndex += 65536 # 包索引是 U16 类型
|
|
332
|
+
elif packageIndex == lastPackageIndex:
|
|
333
|
+
return False
|
|
334
|
+
|
|
335
|
+
deltaPackageIndex = packageIndex - lastPackageIndex
|
|
336
|
+
if deltaPackageIndex > 1:
|
|
337
|
+
lostSampleCount = sensorData.packageSampleCount * (deltaPackageIndex - 1)
|
|
338
|
+
print(f"lost dataType {sensorData.dataType} -> data {sensorData.deviceMac} {lostSampleCount}")
|
|
339
|
+
self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
|
|
340
|
+
if newPackageIndex == 0:
|
|
341
|
+
sensorData.lastPackageIndex = 65535
|
|
342
|
+
else:
|
|
343
|
+
sensorData.lastPackageIndex = newPackageIndex - 1
|
|
344
|
+
sensorData.lastPackageCounter += (deltaPackageIndex - 1)
|
|
345
|
+
|
|
346
|
+
self.readSamples(data, sensorData, dataOffset, dataGap, 0)
|
|
347
|
+
sensorData.lastPackageIndex = newPackageIndex
|
|
348
|
+
sensorData.lastPackageCounter += 1
|
|
349
|
+
except Exception:
|
|
350
|
+
return False
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
def readSamples(self, data: bytes, sensorData: SensorData, offset: int, dataGap: int, lostSampleCount: int):
|
|
354
|
+
sampleCount = sensorData.packageSampleCount
|
|
355
|
+
sampleInterval = 1000 // sensorData.sampleRate
|
|
356
|
+
if lostSampleCount > 0:
|
|
357
|
+
sampleCount = lostSampleCount
|
|
358
|
+
|
|
359
|
+
K = sensorData.K
|
|
360
|
+
lastSampleIndex = sensorData.lastPackageCounter * sensorData.packageSampleCount
|
|
361
|
+
|
|
362
|
+
_impedanceData = self.impedanceData.copy()
|
|
363
|
+
_saturationData = self.saturationData.copy()
|
|
364
|
+
|
|
365
|
+
channelSamples = sensorData.channelSamples
|
|
366
|
+
if not channelSamples:
|
|
367
|
+
for channelIndex in range(sensorData.channelCount):
|
|
368
|
+
channelSamples.append([])
|
|
369
|
+
|
|
370
|
+
for sampleIndex in range(sampleCount):
|
|
371
|
+
for channelIndex, impedanceChannelIndex in enumerate(range(sensorData.channelCount)):
|
|
372
|
+
if (sensorData.channelMask & (1 << channelIndex)) != 0:
|
|
373
|
+
samples = channelSamples[channelIndex]
|
|
374
|
+
impedance = 0.0
|
|
375
|
+
saturation = 0.0
|
|
376
|
+
if sensorData.dataType == DataType.NTF_ECG:
|
|
377
|
+
impedanceChannelIndex = self.sensorDatas[SensorDataType.DATA_TYPE_EEG].channelCount
|
|
378
|
+
impedance = _impedanceData[impedanceChannelIndex]
|
|
379
|
+
saturation = _saturationData[impedanceChannelIndex]
|
|
380
|
+
impedanceChannelIndex += 1
|
|
381
|
+
|
|
382
|
+
dataItem = Sample()
|
|
383
|
+
dataItem.channelIndex = channelIndex
|
|
384
|
+
dataItem.sampleIndex = lastSampleIndex
|
|
385
|
+
dataItem.timeStampInMs = lastSampleIndex * sampleInterval
|
|
386
|
+
if lostSampleCount > 0:
|
|
387
|
+
dataItem.rawData = 0
|
|
388
|
+
dataItem.data = 0.0
|
|
389
|
+
dataItem.impedance = impedance
|
|
390
|
+
dataItem.saturation = saturation
|
|
391
|
+
dataItem.isLost = True
|
|
392
|
+
else:
|
|
393
|
+
rawData = 0
|
|
394
|
+
if sensorData.resolutionBits == 8:
|
|
395
|
+
rawData = data[offset]
|
|
396
|
+
rawData -= 128
|
|
397
|
+
offset += 1
|
|
398
|
+
elif sensorData.resolutionBits == 16:
|
|
399
|
+
rawData = int.from_bytes(data[offset:offset + 2], byteorder='little', signed=True)
|
|
400
|
+
offset += 2
|
|
401
|
+
elif sensorData.resolutionBits == 24:
|
|
402
|
+
rawData = (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]
|
|
403
|
+
rawData -= 8388608
|
|
404
|
+
offset += 3
|
|
405
|
+
|
|
406
|
+
converted = rawData * K
|
|
407
|
+
dataItem.rawData = rawData
|
|
408
|
+
dataItem.data = converted
|
|
409
|
+
dataItem.impedance = impedance
|
|
410
|
+
dataItem.saturation = saturation
|
|
411
|
+
dataItem.isLost = False
|
|
412
|
+
|
|
413
|
+
samples.append(dataItem)
|
|
414
|
+
|
|
415
|
+
lastSampleIndex += 1
|
|
416
|
+
offset += dataGap
|
|
417
|
+
|
|
418
|
+
def sendSensorData(self, sensorData: SensorData, buf: Queue[SensorData]):
|
|
419
|
+
oldChannelSamples = sensorData.channelSamples
|
|
420
|
+
|
|
421
|
+
if not self.isDataTransfering or len(oldChannelSamples) == 0:
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
realSampleCount = 0
|
|
425
|
+
if len(oldChannelSamples) > 0:
|
|
426
|
+
realSampleCount = len(oldChannelSamples[0])
|
|
427
|
+
|
|
428
|
+
if realSampleCount < sensorData.minPackageSampleCount:
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
sensorData.channelSamples = []
|
|
432
|
+
batchCount = realSampleCount // sensorData.minPackageSampleCount
|
|
433
|
+
# leftSampleSize = realSampleCount - sensorData.minPackageSampleCount * batchCount
|
|
434
|
+
|
|
435
|
+
sensorDataList = []
|
|
436
|
+
startIndex = 0
|
|
437
|
+
for batchIndex in range(batchCount):
|
|
438
|
+
resultChannelSamples = []
|
|
439
|
+
for channelIndex in range(sensorData.channelCount):
|
|
440
|
+
oldSamples = oldChannelSamples[channelIndex]
|
|
441
|
+
newSamples = []
|
|
442
|
+
for sampleIndex in range(sensorData.minPackageSampleCount):
|
|
443
|
+
newSamples.append(oldSamples[startIndex + sampleIndex])
|
|
444
|
+
resultChannelSamples.append(newSamples)
|
|
445
|
+
|
|
446
|
+
sensorDataResult = SensorData()
|
|
447
|
+
sensorDataResult.channelSamples = resultChannelSamples
|
|
448
|
+
sensorDataResult.dataType = sensorData.dataType
|
|
449
|
+
sensorDataResult.deviceMac = sensorData.deviceMac
|
|
450
|
+
sensorDataResult.sampleRate = sensorData.sampleRate
|
|
451
|
+
sensorDataResult.channelCount = sensorData.channelCount
|
|
452
|
+
sensorDataResult.minPackageSampleCount = sensorData.minPackageSampleCount
|
|
453
|
+
sensorDataList.append(sensorDataResult)
|
|
454
|
+
|
|
455
|
+
startIndex += sensorData.minPackageSampleCount
|
|
456
|
+
|
|
457
|
+
leftChannelSamples = []
|
|
458
|
+
for channelIndex in range(sensorData.channelCount):
|
|
459
|
+
oldSamples = oldChannelSamples[channelIndex]
|
|
460
|
+
newSamples = []
|
|
461
|
+
for sampleIndex in range(startIndex, len(oldSamples)):
|
|
462
|
+
newSamples.append(oldSamples[sampleIndex])
|
|
463
|
+
|
|
464
|
+
leftChannelSamples.append(newSamples)
|
|
465
|
+
|
|
466
|
+
sensorData.channelSamples = leftChannelSamples
|
|
467
|
+
|
|
468
|
+
for sensorDataResult in sensorDataList:
|
|
469
|
+
buf.put(sensorDataResult)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def calc_crc8(self,data):
|
|
473
|
+
crc8Table = [
|
|
474
|
+
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
|
|
475
|
+
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
|
|
476
|
+
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
|
|
477
|
+
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
|
|
478
|
+
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
|
|
479
|
+
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
|
|
480
|
+
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
|
|
481
|
+
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
|
|
482
|
+
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
|
|
483
|
+
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
|
|
484
|
+
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
|
|
485
|
+
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
|
|
486
|
+
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
|
|
487
|
+
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
|
|
488
|
+
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
|
|
489
|
+
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
|
|
490
|
+
]
|
|
491
|
+
crc8 = 0
|
|
492
|
+
len_data = len(data)
|
|
493
|
+
|
|
494
|
+
for i in range(len_data):
|
|
495
|
+
crc8 ^= data[i]
|
|
496
|
+
crc8 = crc8Table[crc8]
|
|
497
|
+
|
|
498
|
+
return crc8
|
|
499
|
+
def processUniversalData(self, buf: Queue[SensorData],loop:asyncio.AbstractEventLoop, sensor, callback):
|
|
500
|
+
|
|
501
|
+
while(self._is_running):
|
|
502
|
+
try:
|
|
503
|
+
while(self._is_running and not self._rawDataBuffer.empty()):
|
|
504
|
+
data = self._rawDataBuffer.get_nowait()
|
|
505
|
+
self._concatDataBuffer.extend(data)
|
|
506
|
+
self._rawDataBuffer.task_done()
|
|
507
|
+
except Exception as e:
|
|
508
|
+
pass
|
|
509
|
+
|
|
510
|
+
index = 0
|
|
511
|
+
last_cut = -1
|
|
512
|
+
|
|
513
|
+
while self._is_running:
|
|
514
|
+
data_size = len(self._concatDataBuffer)
|
|
515
|
+
if index >= data_size:
|
|
516
|
+
break
|
|
517
|
+
|
|
518
|
+
if self._concatDataBuffer[index] == 0x55:
|
|
519
|
+
if (index + 1) >= data_size:
|
|
520
|
+
index += 1
|
|
521
|
+
continue
|
|
522
|
+
n = self._concatDataBuffer[index + 1]
|
|
523
|
+
if (index + 1 + n + 1) >= data_size:
|
|
524
|
+
index += 1
|
|
525
|
+
continue
|
|
526
|
+
crc = self._concatDataBuffer[index + 1 + n + 1]
|
|
527
|
+
calc_crc = self.calc_crc8(self._concatDataBuffer[index + 2:index + 2 + n])
|
|
528
|
+
if crc != calc_crc:
|
|
529
|
+
index += 1
|
|
530
|
+
continue
|
|
531
|
+
if self._is_data_transfering:
|
|
532
|
+
data_package = bytes(self._concatDataBuffer[index + 2:index + 2 + n])
|
|
533
|
+
self._processDataPackage(data_package, buf)
|
|
534
|
+
while(self._is_running and self.isDataTransfering):
|
|
535
|
+
sensorData: SensorData = None
|
|
536
|
+
try:
|
|
537
|
+
sensorData = buf.get_nowait()
|
|
538
|
+
except Exception as e:
|
|
539
|
+
break
|
|
540
|
+
if (sensorData != None and callback != None):
|
|
541
|
+
try:
|
|
542
|
+
loop.call_soon_threadsafe(callback,sensor, sensorData)
|
|
543
|
+
except Exception as e:
|
|
544
|
+
print(e)
|
|
545
|
+
|
|
546
|
+
buf.task_done()
|
|
547
|
+
|
|
548
|
+
last_cut = index = index + 2 + n
|
|
549
|
+
elif self._concatDataBuffer[index] == 0xAA:
|
|
550
|
+
if (index + 1) >= data_size:
|
|
551
|
+
index += 1
|
|
552
|
+
continue
|
|
553
|
+
n = self._concatDataBuffer[index + 1]
|
|
554
|
+
if (index + 1 + n + 1) >= data_size:
|
|
555
|
+
index += 1
|
|
556
|
+
continue
|
|
557
|
+
crc = self._concatDataBuffer[index + 1 + n + 1]
|
|
558
|
+
calc_crc = self.calc_crc8(self._concatDataBuffer[index + 2:index + 2 + n])
|
|
559
|
+
if crc != calc_crc:
|
|
560
|
+
index += 1
|
|
561
|
+
continue
|
|
562
|
+
data_package = bytes(self._concatDataBuffer[index + 2:index + 2 + n])
|
|
563
|
+
loop.call_soon_threadsafe(self.gForce._on_cmd_response,None, data_package)
|
|
564
|
+
last_cut = index = index + 2 + n
|
|
565
|
+
else:
|
|
566
|
+
index += 1
|
|
567
|
+
|
|
568
|
+
if last_cut > 0:
|
|
569
|
+
self._concatDataBuffer = self._concatDataBuffer[last_cut + 1:]
|
sensor/sensor_device.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# 详细设备信息
|
|
4
|
+
# 该类用于存储设备的详细信息,包括设备名称、型号、硬件和固件版本、各种通道数量以及 MTU 大小
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeviceInfo:
|
|
9
|
+
# """
|
|
10
|
+
# Initialize a DeviceInfo instance.
|
|
11
|
+
|
|
12
|
+
# :param device_name (str): 设备名称
|
|
13
|
+
# :param model_name (str): 设备型号
|
|
14
|
+
# :param hardware_version (str): 设备硬件版本
|
|
15
|
+
# :param firmware_version (str): 设备固件版本
|
|
16
|
+
# :param emg_channel_count (int): EMG 通道数量
|
|
17
|
+
# :param eeg_channel_count (int): EEG 通道数量
|
|
18
|
+
# :param ecg_channel_count (int): ECG 通道数量
|
|
19
|
+
# :param acc_channel_count (int): 加速度通道数量
|
|
20
|
+
# :param gyro_channel_count (int): 陀螺仪通道数量
|
|
21
|
+
# :param brth_channel_count (int): 呼吸通道数量
|
|
22
|
+
# :param mtu_size (int): MTU 大小
|
|
23
|
+
# """
|
|
24
|
+
# def __init__(self, device_name: str, model_name: str, hardware_version: str, firmware_version: str,
|
|
25
|
+
# emg_channel_count: int, eeg_channel_count: int, ecg_channel_count: int,
|
|
26
|
+
# acc_channel_count: int, gyro_channel_count: int, brth_channel_count: int, mtu_size: int):
|
|
27
|
+
# self.DeviceName = device_name
|
|
28
|
+
# self.ModelName = model_name
|
|
29
|
+
# self.HardwareVersion = hardware_version
|
|
30
|
+
# self.FirmwareVersion = firmware_version
|
|
31
|
+
# self.EmgChannelCount = emg_channel_count
|
|
32
|
+
# self.EegChannelCount = eeg_channel_count
|
|
33
|
+
# self.EcgChannelCount = ecg_channel_count
|
|
34
|
+
# self.AccChannelCount = acc_channel_count
|
|
35
|
+
# self.GyroChannelCount = gyro_channel_count
|
|
36
|
+
# self.BrthChannelCount = brth_channel_count
|
|
37
|
+
# self.MTUSize = mtu_size
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self.DeviceName = ""
|
|
41
|
+
self.ModelName = ""
|
|
42
|
+
self.HardwareVersion = ""
|
|
43
|
+
self.FirmwareVersion = ""
|
|
44
|
+
self.EmgChannelCount = 0
|
|
45
|
+
self.EegChannelCount = 0
|
|
46
|
+
self.EcgChannelCount = 0
|
|
47
|
+
self.AccChannelCount = 0
|
|
48
|
+
self.GyroChannelCount = 0
|
|
49
|
+
self.BrthChannelCount = 0
|
|
50
|
+
self.MTUSize = 0
|
|
51
|
+
|
|
52
|
+
class DeviceStateEx(Enum):
|
|
53
|
+
Disconnected = 0
|
|
54
|
+
Connecting = 1
|
|
55
|
+
Connected = 2
|
|
56
|
+
Ready = 3
|
|
57
|
+
Disconnecting = 4
|
|
58
|
+
Invalid = 5
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# 蓝牙设备信息
|
|
62
|
+
# 该类用于存储蓝牙设备的基本信息,包括设备名称、地址和信号强度
|
|
63
|
+
class BLEDevice:
|
|
64
|
+
"""
|
|
65
|
+
Initialize a BLEDevice instance.
|
|
66
|
+
:param name (str): 设备名称
|
|
67
|
+
:param address (str): 设备地址
|
|
68
|
+
:param rssi (int): 信号强度
|
|
69
|
+
"""
|
|
70
|
+
def __init__(self, name: str, address: str, rssi: int):
|
|
71
|
+
|
|
72
|
+
# 初始化函数,用于创建一个Beacon对象
|
|
73
|
+
self.Name = name # 设置Beacon的名称
|
|
74
|
+
self.Address = address # 设置Beacon的地址
|
|
75
|
+
self.RSSI = rssi
|