sensor-sdk 0.0.13__py3-none-any.whl → 0.0.16__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/gforce.py CHANGED
@@ -728,6 +728,17 @@ class GForce:
728
728
  )
729
729
  )
730
730
 
731
+ async def set_package_id(self, switchStatus):
732
+ body = [switchStatus == True]
733
+ body = bytes(body)
734
+ ret = await self._send_request(
735
+ Request(
736
+ cmd=Command.PACKAGE_ID_CONTROL,
737
+ body=body,
738
+ has_res=True,
739
+ )
740
+ )
741
+
731
742
  async def set_log_level(self, logLevel):
732
743
  body = [0xFF & logLevel]
733
744
  body = bytes(body)
@@ -853,10 +864,10 @@ class GForce:
853
864
 
854
865
  async def stop_streaming(self):
855
866
  exceptions = []
856
- try:
857
- await asyncio.wait_for(self.set_subscription(DataSubscription.OFF), utils._TIMEOUT)
858
- except Exception as e:
859
- exceptions.append(e)
867
+ # try:
868
+ # await asyncio.wait_for(self.set_subscription(DataSubscription.OFF), utils._TIMEOUT)
869
+ # except Exception as e:
870
+ # exceptions.append(e)
860
871
  try:
861
872
  await asyncio.wait_for(self.client.stop_notify(self.data_char), utils._TIMEOUT)
862
873
  except Exception as e:
sensor/sensor_data.py CHANGED
@@ -38,6 +38,7 @@ class Sample:
38
38
  class DataType(IntEnum):
39
39
  NTF_ACC = 0x1 # 加速度,用于标识加速度传感器采集的数据
40
40
  NTF_GYRO = 0x2 # 陀螺仪,用于标识陀螺仪传感器采集的数据
41
+ NTF_EMG = 0x8 # EMG,用于标识肌电传感器采集的数据
41
42
  NTF_EEG = 0x10 # EEG,用于标识脑电传感器采集的数据
42
43
  NTF_ECG = 0x11 # ECG,用于标识心电传感器采集的数据
43
44
  NTF_IMPEDANCE = (0x12,) # 阻抗数据
@@ -8,7 +8,7 @@ from typing import Deque, List
8
8
  from concurrent.futures import ThreadPoolExecutor
9
9
  import csv
10
10
  from sensor import utils
11
- from sensor.gforce import DataSubscription, GForce
11
+ from sensor.gforce import DataSubscription, GForce, SamplingRate
12
12
  from sensor.sensor_data import DataType, Sample, SensorData
13
13
 
14
14
  from enum import Enum, IntEnum
@@ -22,7 +22,8 @@ class SensorDataType(IntEnum):
22
22
  DATA_TYPE_ACC = 2
23
23
  DATA_TYPE_GYRO = 3
24
24
  DATA_TYPE_BRTH = 4
25
- DATA_TYPE_COUNT = 5
25
+ DATA_TYPE_EMG = 5
26
+ DATA_TYPE_COUNT = 6
26
27
 
27
28
 
28
29
  # 枚举 FeatureMaps 的 Python 实现
@@ -114,6 +115,31 @@ class SensorProfileDataCtx:
114
115
  def hasConcatBLE(self):
115
116
  return (self.featureMap & FeatureMaps.GFD_FEAT_CONCAT_BLE.value) != 0
116
117
 
118
+ async def initEMG(self, packageCount: int) -> int:
119
+ config = await self.gForce.get_emg_raw_data_config()
120
+ data = SensorData()
121
+ data.deviceMac = self.deviceMac
122
+ data.dataType = DataType.NTF_EMG
123
+ data.sampleRate = 500
124
+ data.resolutionBits = 0
125
+ data.channelCount = 8
126
+ data.channelMask = config.channel_mask
127
+ data.minPackageSampleCount = packageCount
128
+ data.packageSampleCount = 8
129
+ data.K = 4000000.0 / 8388607.0
130
+ data.clear()
131
+ self.sensorDatas[SensorDataType.DATA_TYPE_EMG] = data
132
+ self.notifyDataFlag |= DataSubscription.EMG_RAW
133
+
134
+ config.fs = SamplingRate.HZ_500
135
+ config.channel_mask = 255
136
+ config.resolution = 8
137
+ config.batch_len = 128
138
+ await self.gForce.set_emg_raw_data_config(config)
139
+
140
+ await self.gForce.set_package_id(True)
141
+ return data.channelCount
142
+
117
143
  async def initEEG(self, packageCount: int) -> int:
118
144
  config = await self.gForce.get_eeg_raw_data_config()
119
145
  cap = await self.gForce.get_eeg_raw_data_cap()
@@ -234,23 +260,23 @@ class SensorProfileDataCtx:
234
260
  if self.hasImpedance():
235
261
  self.notifyDataFlag |= DataSubscription.DNF_IMPEDANCE
236
262
 
237
- if self.hasEEG() & (self.init_map["NTF_EEG"] == "ON"):
238
- # print("initEEG")
263
+ if self.hasEMG() and (self.init_map["NTF_EMG"] == "ON"):
264
+ info.EmgChannelCount = await self.initEMG(packageCount)
265
+ info.EmgSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_EMG].sampleRate
266
+
267
+ if self.hasEEG() and (self.init_map["NTF_EEG"] == "ON"):
239
268
  info.EegChannelCount = await self.initEEG(packageCount)
240
269
  info.EegSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_EEG].sampleRate
241
270
 
242
- if self.hasECG() & (self.init_map["NTF_ECG"] == "ON"):
243
- # print("initECG")
271
+ if self.hasECG() and (self.init_map["NTF_ECG"] == "ON"):
244
272
  info.EcgChannelCount = await self.initECG(packageCount)
245
273
  info.EcgSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_ECG].sampleRate
246
274
 
247
- if self.hasBrth() & (self.init_map["NTF_BRTH"] == "ON"):
248
- # print("initBrth")
275
+ if self.hasBrth() and (self.init_map["NTF_BRTH"] == "ON"):
249
276
  info.BrthChannelCount = await self.initBrth(packageCount)
250
277
  info.BrthSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_BRTH].sampleRate
251
278
 
252
- if self.hasIMU() & (self.init_map["NTF_IMU"] == "ON"):
253
- # print("initIMU")
279
+ if self.hasIMU() and (self.init_map["NTF_IMU"] == "ON"):
254
280
  imuChannelCount = await self.initIMU(packageCount)
255
281
  info.AccChannelCount = imuChannelCount
256
282
  info.GyroChannelCount = imuChannelCount
@@ -274,12 +300,15 @@ class SensorProfileDataCtx:
274
300
  return True
275
301
  self._is_data_transfering = True
276
302
  self._rawDataBuffer.queue.clear()
303
+ self._concatDataBuffer.clear()
304
+ self.clear()
305
+
277
306
  if not self.isUniversalStream:
278
307
  await self.gForce.start_streaming(self._rawDataBuffer)
279
- return True
280
308
  else:
281
309
  await self.gForce.set_subscription(self.notifyDataFlag)
282
- return True
310
+
311
+ return True
283
312
 
284
313
  async def stop_streaming(self) -> bool:
285
314
  if not self._is_data_transfering:
@@ -287,12 +316,21 @@ class SensorProfileDataCtx:
287
316
 
288
317
  self._is_data_transfering = False
289
318
 
290
- if not self.isUniversalStream:
291
- await self.gForce.stop_streaming()
292
- return True
293
- else:
294
- await self.gForce.set_subscription(0)
295
- return True
319
+ try:
320
+
321
+ if not self.isUniversalStream:
322
+ await self.gForce.stop_streaming()
323
+ else:
324
+ await self.gForce.set_subscription(0)
325
+
326
+ while self._is_running and not self._rawDataBuffer.empty():
327
+ await asyncio.sleep(0.1)
328
+
329
+ except Exception as e:
330
+ print(e)
331
+ return False
332
+
333
+ return True
296
334
 
297
335
  async def setFilter(self, filter: str, value: str) -> str:
298
336
  self.filter_map[filter] = value
@@ -313,6 +351,7 @@ class SensorProfileDataCtx:
313
351
  switch |= 8
314
352
  try:
315
353
  await self.gForce.set_firmware_filter_switch(switch)
354
+ await asyncio.sleep(0.1)
316
355
  return "OK"
317
356
  except Exception as e:
318
357
  return "ERROR: " + str(e)
@@ -342,25 +381,26 @@ class SensorProfileDataCtx:
342
381
  except Exception as e:
343
382
  continue
344
383
 
345
- self._processDataPackage(data, buf, sensor)
384
+ if self.isDataTransfering:
385
+ self._processDataPackage(data, buf, sensor)
346
386
  self._rawDataBuffer.task_done()
347
387
 
348
- while self._is_running and self.isDataTransfering and not buf.empty():
349
- sensorData: SensorData = None
388
+ while self._is_running and self.isDataTransfering and not buf.empty():
389
+ sensorData: SensorData = None
390
+ try:
391
+ sensorData = buf.get_nowait()
392
+ except Exception as e:
393
+ break
394
+ if sensorData != None and callback != None:
350
395
  try:
351
- sensorData = buf.get_nowait()
396
+ asyncio.get_event_loop().run_in_executor(self.dataPool, callback, sensor, sensorData)
352
397
  except Exception as e:
353
- break
354
- if sensorData != None and callback != None:
355
- try:
356
- asyncio.get_event_loop().run_in_executor(self.dataPool, callback, sensor, sensorData)
357
- except Exception as e:
358
- print(e)
398
+ print(e)
359
399
 
360
- buf.task_done()
400
+ buf.task_done()
361
401
 
362
402
  def _processDataPackage(self, data: bytes, buf: Queue[SensorData], sensor):
363
- v = data[0]
403
+ v = data[0] & 0x7F
364
404
  if v == DataType.NTF_IMPEDANCE:
365
405
  offset = 1
366
406
  # packageIndex = ((data[offset + 1] & 0xff) << 8) | (data[offset] & 0xff)
@@ -383,7 +423,10 @@ class SensorProfileDataCtx:
383
423
 
384
424
  self.impedanceData = impedanceData
385
425
  self.saturationData = saturationData
386
-
426
+ elif v == DataType.NTF_EMG:
427
+ sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EMG]
428
+ if self.checkReadSamples(sensor, data, sensor_data, 3, 0):
429
+ self.sendSensorData(sensor_data, buf)
387
430
  elif v == DataType.NTF_EEG:
388
431
  sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EEG]
389
432
  if self.checkReadSamples(sensor, data, sensor_data, 3, 0):
@@ -407,7 +450,7 @@ class SensorProfileDataCtx:
407
450
 
408
451
  def checkReadSamples(self, sensor, data: bytes, sensorData: SensorData, dataOffset: int, dataGap: int):
409
452
  offset = 1
410
- v = data[0]
453
+
411
454
  if not self._is_data_transfering:
412
455
  return False
413
456
  try:
@@ -439,8 +482,9 @@ class SensorProfileDataCtx:
439
482
  asyncio.get_event_loop().run_in_executor(None, sensor._on_error_callback, sensor, lostLog)
440
483
  except Exception as e:
441
484
  pass
485
+ if lostSampleCount < 100:
486
+ self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
442
487
 
443
- self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
444
488
  if newPackageIndex == 0:
445
489
  sensorData.lastPackageIndex = 65535
446
490
  else:
@@ -455,6 +499,12 @@ class SensorProfileDataCtx:
455
499
  return False
456
500
  return True
457
501
 
502
+ def transTrainData(self, data: int):
503
+ xout = data >> 4
504
+ exp = data & 0x0000000F
505
+ xout = xout << exp
506
+ return xout
507
+
458
508
  def readSamples(
459
509
  self,
460
510
  data: bytes,
@@ -522,6 +572,10 @@ class SensorProfileDataCtx:
522
572
  rawData = (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]
523
573
  rawData -= 8388608
524
574
  offset += 3
575
+ elif sensorData.resolutionBits == 0:
576
+ rawData = struct.unpack_from("<h", data, offset)[0]
577
+ offset += 2
578
+ rawData = self.transTrainData(rawData)
525
579
 
526
580
  converted = rawData * K
527
581
  dataItem.rawData = rawData
sensor/sensor_profile.py CHANGED
@@ -61,6 +61,8 @@ class SensorProfile:
61
61
  self._gforce: GForce = None
62
62
  self._data_event_loop: asyncio.AbstractEventLoop = None
63
63
  self._event_loop: asyncio.AbstractEventLoop = None
64
+ self._is_starting = False
65
+ self._is_setting_param = False
64
66
 
65
67
  def __del__(self) -> None:
66
68
  """
@@ -239,6 +241,8 @@ class SensorProfile:
239
241
  self._data_ctx = SensorProfileDataCtx(self._gforce, self._device.Address, self._raw_data_buf)
240
242
  if self._data_ctx.isUniversalStream:
241
243
  async_exec(self._process_universal_data())
244
+ else:
245
+ async_exec(self._process_data())
242
246
 
243
247
  if self.deviceState == DeviceStateEx.Connected or self.deviceState == DeviceStateEx.Ready:
244
248
  return True
@@ -288,7 +292,7 @@ class SensorProfile:
288
292
  return await async_call(self._connect())
289
293
 
290
294
  async def _waitForDisconnect(self) -> bool:
291
- while self.deviceState != DeviceStateEx.Disconnected:
295
+ while not utils._terminated and self.deviceState != DeviceStateEx.Disconnected:
292
296
  await asyncio.sleep(1)
293
297
  return True
294
298
 
@@ -341,11 +345,13 @@ class SensorProfile:
341
345
  if self._data_event_loop == None:
342
346
  self._data_event_loop = asyncio.new_event_loop()
343
347
 
344
- result = await self._data_ctx.start_streaming()
348
+ self._raw_data_buf.queue.clear()
345
349
  self._data_buffer.queue.clear()
346
- self._data_ctx.clear()
347
- if not self._data_ctx.isUniversalStream:
348
- async_exec(self._process_data())
350
+
351
+ result = await self._data_ctx.start_streaming()
352
+ await asyncio.sleep(0.2)
353
+
354
+ self._is_starting = False
349
355
  return result
350
356
 
351
357
  def startDataNotification(self) -> bool:
@@ -355,6 +361,10 @@ class SensorProfile:
355
361
  :return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
356
362
 
357
363
  """
364
+ if self._is_starting:
365
+ return False
366
+
367
+ self._is_starting = True
358
368
  return sync_call(self._startDataNotification())
359
369
 
360
370
  async def asyncStartDataNotification(self) -> bool:
@@ -364,6 +374,10 @@ class SensorProfile:
364
374
  :return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
365
375
 
366
376
  """
377
+ if self._is_starting:
378
+ return False
379
+
380
+ self._is_starting = True
367
381
  return await async_call(self._startDataNotification())
368
382
 
369
383
  async def _stopDataNotification(self) -> bool:
@@ -377,7 +391,9 @@ class SensorProfile:
377
391
  if not self._data_ctx.isDataTransfering:
378
392
  return True
379
393
 
380
- return not await self._data_ctx.stop_streaming()
394
+ result = await self._data_ctx.stop_streaming()
395
+ self._is_starting = False
396
+ return not result
381
397
 
382
398
  def stopDataNotification(self) -> bool:
383
399
  """
@@ -386,6 +402,10 @@ class SensorProfile:
386
402
  :return: bool: 如果停止数据通知成功,返回 True;否则返回 False。
387
403
 
388
404
  """
405
+ if self._is_starting:
406
+ return False
407
+
408
+ self._is_starting = True
389
409
  return sync_call(self._stopDataNotification())
390
410
 
391
411
  async def asyncStopDataNotification(self) -> bool:
@@ -395,6 +415,10 @@ class SensorProfile:
395
415
  :return: bool: 如果停止数据通知成功,返回 True;否则返回 False。
396
416
 
397
417
  """
418
+ if self._is_starting:
419
+ return False
420
+
421
+ self._is_starting = True
398
422
  return await async_call(self._stopDataNotification())
399
423
 
400
424
  async def _refresh_power(self):
@@ -474,21 +498,35 @@ class SensorProfile:
474
498
  return None
475
499
 
476
500
  async def _setParam(self, key: str, value: str) -> str:
501
+ result = "Error: Not supported"
477
502
  if self.deviceState != DeviceStateEx.Ready:
478
- return "Please connect first"
503
+ result = "Error: Please connect first"
479
504
 
480
505
  if key in ["NTF_EMG", "NTF_EEG", "NTF_ECG", "NTF_IMU", "NTF_BRTH"]:
481
506
  if value in ["ON", "OFF"]:
482
507
  self._data_ctx.init_map[key] = value
483
- return "OK"
508
+ result = "OK"
484
509
 
485
510
  if key in ["FILTER_50Hz", "FILTER_60Hz", "FILTER_HPF", "FILTER_LPF"]:
486
511
  if value in ["ON", "OFF"]:
487
- return await self._data_ctx.setFilter(key, value)
512
+ needPauseTransfer = self.isDataTransfering
513
+ if needPauseTransfer:
514
+ if self._is_starting:
515
+ self._is_setting_param = False
516
+ return "Error: Please pause data transfer first"
517
+
518
+ self._is_starting = True
519
+ await self._stopDataNotification()
520
+ result = await self._data_ctx.setFilter(key, value)
521
+ if needPauseTransfer:
522
+ self._is_starting = True
523
+ await self._startDataNotification()
488
524
 
489
525
  if key == "DEBUG_BLE_DATA_PATH":
490
- return await self._data_ctx.setDebugCSV(value)
491
- return "Not supported"
526
+ result = await self._data_ctx.setDebugCSV(value)
527
+
528
+ self._is_setting_param = False
529
+ return result
492
530
 
493
531
  def setParam(self, key: str, value: str) -> str:
494
532
  """
@@ -500,6 +538,10 @@ class SensorProfile:
500
538
  :return: str: 设置参数的结果。
501
539
 
502
540
  """
541
+ if self._is_setting_param:
542
+ return "Error: Please wait for the previous operation to complete"
543
+
544
+ self._is_setting_param = True
503
545
  return sync_call(
504
546
  self._setParam(key, value),
505
547
  20,
@@ -515,6 +557,10 @@ class SensorProfile:
515
557
  :return: str: 设置参数的结果。
516
558
 
517
559
  """
560
+ if self._is_setting_param:
561
+ return "Error: Please wait for the previous operation to complete"
562
+
563
+ self._is_setting_param = True
518
564
  return await async_call(
519
565
  self._setParam(key, value),
520
566
  20,
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sensor-sdk
3
- Version: 0.0.13
3
+ Version: 0.0.16
4
4
  Summary: Python sdk for Synchroni
5
5
  Home-page: https://github.com/oymotion/SynchroniSDKPython
6
6
  Author: Martin Ye
7
7
  Author-email: yecq_82@hotmail.com
8
- Requires-Python: >=3.8.0
8
+ Requires-Python: >=3.9.0
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE.txt
11
11
  Requires-Dist: numpy
@@ -0,0 +1,14 @@
1
+ sensor/__init__.py,sha256=L1VyAP0EDEnJIMeMTzp4iXHSRUUHyHScF_GIl3iYKRI,123
2
+ sensor/gforce.py,sha256=gpetzJD0b4zezCMdhzodjjj55eb2C37ApeiNIYzRf1Q,26078
3
+ sensor/sensor_controller.py,sha256=qnM9mVwpJn9PCnxlvbgWFeaLktqVnmaquZgasYGEHqs,9451
4
+ sensor/sensor_data.py,sha256=vKreLHZs7WbQcnfqHiLLfHQ3cCmvD1K2xl2WUQg8vv0,4005
5
+ sensor/sensor_data_context.py,sha256=FpZlgEq98MPRR3SGOy4LimXaiU3SBVuxtDCbSMPro1M,30560
6
+ sensor/sensor_device.py,sha256=eO1vaqjxCc2UCPBoKXqlk6o498uRyWt6IYs7r7wXSD0,3042
7
+ sensor/sensor_profile.py,sha256=NJsPr9l4VB9zAjfkaD28chGnJiSCnYAGFdkkN9Wyky0,19669
8
+ sensor/utils.py,sha256=BudTD083ucGxcTXfmksF-2gLRi_i-k7tMytruLUUKJc,6503
9
+ sensor_sdk-0.0.16.dist-info/LICENSE.txt,sha256=8CSivOpub3IuXODTyqBRI91AxouJZk02YrcKuOAkWu8,1111
10
+ sensor_sdk-0.0.16.dist-info/METADATA,sha256=dxmSp_3HYa2gUxk9bpCeqsynksYfw1Km-SeIhsTtsFc,9825
11
+ sensor_sdk-0.0.16.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
12
+ sensor_sdk-0.0.16.dist-info/top_level.txt,sha256=Ftq49B6bH0Ffdc7c8LkcyakHo6lsg_snlBbpEUoILSk,7
13
+ sensor_sdk-0.0.16.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
14
+ sensor_sdk-0.0.16.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- sensor/__init__.py,sha256=L1VyAP0EDEnJIMeMTzp4iXHSRUUHyHScF_GIl3iYKRI,123
2
- sensor/gforce.py,sha256=QsbHhTieVKMJ_KHn7nEchh7sChZWL-iSP0LmfxhYYXw,25753
3
- sensor/sensor_controller.py,sha256=qnM9mVwpJn9PCnxlvbgWFeaLktqVnmaquZgasYGEHqs,9451
4
- sensor/sensor_data.py,sha256=Hu7Ql0LgQ7V24xYZhaLrKPwU4KWZeWE655v8Gy8xphY,3934
5
- sensor/sensor_data_context.py,sha256=a45WJPE-8-CzzMn9BlCjjC39fiIxkjV-UQw2Rok-OyM,28536
6
- sensor/sensor_device.py,sha256=eO1vaqjxCc2UCPBoKXqlk6o498uRyWt6IYs7r7wXSD0,3042
7
- sensor/sensor_profile.py,sha256=qxjTYt7X45rmVcapI93ynB4dTFLLt0M-WKCp-cpqKrM,18167
8
- sensor/utils.py,sha256=BudTD083ucGxcTXfmksF-2gLRi_i-k7tMytruLUUKJc,6503
9
- sensor_sdk-0.0.13.dist-info/LICENSE.txt,sha256=8CSivOpub3IuXODTyqBRI91AxouJZk02YrcKuOAkWu8,1111
10
- sensor_sdk-0.0.13.dist-info/METADATA,sha256=6hKH69-rbvoswQ9LGvPVcGiY0v8qKKN9rfiVXjX0hLs,9825
11
- sensor_sdk-0.0.13.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
12
- sensor_sdk-0.0.13.dist-info/top_level.txt,sha256=Ftq49B6bH0Ffdc7c8LkcyakHo6lsg_snlBbpEUoILSk,7
13
- sensor_sdk-0.0.13.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
14
- sensor_sdk-0.0.13.dist-info/RECORD,,