sensor-sdk 0.0.18__py3-none-any.whl → 0.0.31__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
@@ -3,6 +3,7 @@ import queue
3
3
  import struct
4
4
  from asyncio import Queue
5
5
  from contextlib import suppress
6
+ from datetime import datetime
6
7
  from dataclasses import dataclass
7
8
  from enum import IntEnum
8
9
  from typing import Optional, Dict, List
@@ -88,6 +89,7 @@ class Command(IntEnum):
88
89
  GET_EMG_RAWDATA_CONFIG = (0x46,)
89
90
 
90
91
  SET_DATA_NOTIF_SWITCH = (0x4F,)
92
+ SET_FUNCTION_SWITCH = (0x85,)
91
93
 
92
94
  CMD_GET_EEG_CONFIG = (0xA0,)
93
95
  CMD_SET_EEG_CONFIG = (0xA1,)
@@ -372,9 +374,19 @@ class Response:
372
374
 
373
375
 
374
376
  class GForce:
375
- def __init__(self, device: BLEDevice, cmd_char: str, data_char: str, isUniversalStream: bool):
377
+ def __init__(
378
+ self,
379
+ device: BLEDevice,
380
+ cmd_char: str,
381
+ data_char: str,
382
+ isUniversalStream: bool,
383
+ event_loop: asyncio.AbstractEventLoop,
384
+ gforce_event_loop: asyncio.AbstractEventLoop,
385
+ ):
376
386
  self.device_name = ""
377
387
  self.client = None
388
+ self.event_loop = event_loop
389
+ self.gforce_event_loop = gforce_event_loop
378
390
  self.cmd_char = cmd_char
379
391
  self.data_char = data_char
380
392
  self.responses: Dict[Command, Queue] = {}
@@ -393,7 +405,7 @@ class GForce:
393
405
  self._raw_data_buf = buf
394
406
 
395
407
  try:
396
- await asyncio.wait_for(client.connect(), sensor_utils._TIMEOUT)
408
+ await sensor_utils.async_call(client.connect(), sensor_utils._TIMEOUT, self.gforce_event_loop)
397
409
  except Exception as e:
398
410
  return
399
411
 
@@ -402,19 +414,22 @@ class GForce:
402
414
 
403
415
  try:
404
416
  if not self._is_universal_stream:
405
- await asyncio.wait_for(
417
+ await sensor_utils.async_call(
406
418
  client.start_notify(self.cmd_char, self._on_cmd_response),
407
419
  sensor_utils._TIMEOUT,
420
+ self.gforce_event_loop,
408
421
  )
422
+
409
423
  else:
410
- await asyncio.wait_for(
424
+ await sensor_utils.async_call(
411
425
  client.start_notify(self.data_char, self._on_universal_response),
412
426
  sensor_utils._TIMEOUT,
427
+ self.gforce_event_loop,
413
428
  )
414
429
  except Exception as e:
415
430
  return
416
431
 
417
- def _on_data_response(self, q: Queue[bytes], bs: bytearray):
432
+ def _on_data_response(self, q: queue.Queue[bytes], bs: bytearray):
418
433
  bs = bytes(bs)
419
434
 
420
435
  full_packet = []
@@ -443,63 +458,6 @@ class GForce:
443
458
  return
444
459
 
445
460
  q.put_nowait(bytes(full_packet))
446
- # data = None
447
- # data_type = DataType(full_packet[0])
448
- # packet = full_packet[1:]
449
- # match data_type:
450
- # case DataType.EMG_ADC:
451
- # data = self._convert_emg_to_raw(packet)
452
-
453
- # case DataType.ACC:
454
- # data = self._convert_acceleration_to_g(packet)
455
-
456
- # case DataType.GYO:
457
- # data = self._convert_gyro_to_dps(packet)
458
-
459
- # case DataType.MAG:
460
- # data = self._convert_magnetometer_to_ut(packet)
461
-
462
- # case DataType.EULER:
463
- # data = self._convert_euler(packet)
464
-
465
- # case DataType.QUAT:
466
- # data = self._convert_quaternion(packet)
467
-
468
- # case DataType.ROTA:
469
- # data = self._convert_rotation_matrix(packet)
470
-
471
- # case DataType.EMG_GEST: # It is not supported by the device (?)
472
- # data = self._convert_emg_gesture(packet)
473
-
474
- # case DataType.HID_MOUSE: # It is not supported by the device
475
- # pass
476
-
477
- # case DataType.HID_JOYSTICK: # It is not supported by the device
478
- # pass
479
-
480
- # case DataType.PARTIAL:
481
- # pass
482
- # case _:
483
- # raise Exception(
484
- # f"Unknown data type {data_type}, full packet: {full_packet}"
485
- # )
486
-
487
- # q.put_nowait(data)
488
-
489
- # def _convert_emg_to_raw(self, data: bytes) -> np.ndarray[np.integer]:
490
- # match self.resolution:
491
- # case SampleResolution.BITS_8:
492
- # dtype = np.uint8
493
-
494
- # case SampleResolution.BITS_12:
495
- # dtype = np.uint16
496
-
497
- # case _:
498
- # raise Exception(f"Unsupported resolution {self.resolution}")
499
-
500
- # emg_data = np.frombuffer(data, dtype=dtype)
501
-
502
- # return emg_data.reshape(-1, self._num_channels)
503
461
 
504
462
  @staticmethod
505
463
  def _convert_acceleration_to_g(data: bytes) -> np.ndarray[np.float32]:
@@ -564,12 +522,18 @@ class GForce:
564
522
  self._raw_data_buf.put_nowait(bytes(bs))
565
523
 
566
524
  def _on_cmd_response(self, _: BleakGATTCharacteristic, bs: bytearray):
525
+ sensor_utils.async_exec(self.async_on_cmd_response(bs), self.event_loop)
526
+
527
+ async def async_on_cmd_response(self, bs: bytearray):
567
528
  try:
529
+ # print(bytes(bs))
568
530
  response = self._parse_response(bytes(bs))
569
- if response.cmd in self.responses:
531
+ if self.responses.get(response.cmd) != None:
570
532
  self.responses[response.cmd].put_nowait(
571
533
  response.data,
572
534
  )
535
+ else:
536
+ print("invalid response:" + bytes(bs))
573
537
  except Exception as e:
574
538
  raise Exception("Failed to parse response: %s" % e)
575
539
 
@@ -750,6 +714,17 @@ class GForce:
750
714
  )
751
715
  )
752
716
 
717
+ async def set_function_switch(self, funcSwitch):
718
+ body = [0xFF & funcSwitch]
719
+ body = bytes(body)
720
+ ret = await self._send_request(
721
+ Request(
722
+ cmd=Command.SET_FUNCTION_SWITCH,
723
+ body=body,
724
+ has_res=True,
725
+ )
726
+ )
727
+
753
728
  async def set_firmware_filter_switch(self, switchStatus: int):
754
729
  body = [0xFF & switchStatus]
755
730
  body = bytes(body)
@@ -854,22 +829,20 @@ class GForce:
854
829
  )
855
830
 
856
831
  async def start_streaming(self, q: queue.Queue):
857
- await asyncio.wait_for(
832
+ await sensor_utils.async_call(
858
833
  self.client.start_notify(
859
834
  self.data_char,
860
835
  lambda _, data: self._on_data_response(q, data),
861
836
  ),
862
837
  sensor_utils._TIMEOUT,
838
+ self.gforce_event_loop,
863
839
  )
864
840
 
865
841
  async def stop_streaming(self):
866
842
  exceptions = []
867
- # try:
868
- # await asyncio.wait_for(self.set_subscription(DataSubscription.OFF), sensor_utils._TIMEOUT)
869
- # except Exception as e:
870
- # exceptions.append(e)
843
+
871
844
  try:
872
- await asyncio.wait_for(self.client.stop_notify(self.data_char), sensor_utils._TIMEOUT)
845
+ await sensor_utils.async_call(self.client.stop_notify(self.data_char), sensor_utils._TIMEOUT, self.gforce_event_loop)
873
846
  except Exception as e:
874
847
  exceptions.append(e)
875
848
 
@@ -879,29 +852,62 @@ class GForce:
879
852
  async def disconnect(self):
880
853
  with suppress(asyncio.CancelledError):
881
854
  try:
882
- await asyncio.wait_for(self.client.disconnect(), sensor_utils._TIMEOUT)
855
+ await sensor_utils.async_call(self.client.disconnect(), sensor_utils._TIMEOUT, self.gforce_event_loop)
883
856
  except Exception as e:
884
857
  pass
885
858
 
886
859
  def _get_response_channel(self, cmd: Command) -> Queue:
887
- q = Queue()
888
- self.responses[cmd] = q
889
- return q
860
+ if self.responses.get(cmd) != None:
861
+ return self.responses[cmd]
862
+ else:
863
+ q = Queue()
864
+ self.responses[cmd] = q
865
+ return q
890
866
 
891
867
  async def _send_request(self, req: Request) -> Optional[bytes]:
868
+ return await sensor_utils.async_call(self._send_request_internal(req=req), runloop=self.event_loop)
869
+
870
+ async def _send_request_internal(self, req: Request) -> Optional[bytes]:
892
871
  q = None
893
872
  if req.has_res:
894
873
  q = self._get_response_channel(req.cmd)
895
874
 
875
+ timeStamp_old = -1
876
+ while not q.empty():
877
+ timeStamp_old = q.get_nowait()
878
+
879
+ now = datetime.now()
880
+ timestamp_now = now.timestamp()
881
+ if (timeStamp_old > -1) and ((timestamp_now - timeStamp_old) < 3):
882
+ print("send request too fast")
883
+ q.put_nowait(timeStamp_old)
884
+ return None
885
+
896
886
  bs = bytes([req.cmd])
897
887
  if req.body is not None:
898
888
  bs += req.body
899
- await asyncio.wait_for(self.client.write_gatt_char(self.cmd_char, bs), sensor_utils._TIMEOUT)
889
+
890
+ # print(str(req.cmd) + str(req.body))
891
+ try:
892
+ await sensor_utils.async_call(
893
+ self.client.write_gatt_char(self.cmd_char, bs),
894
+ 1,
895
+ runloop=self.gforce_event_loop,
896
+ )
897
+ except Exception as e:
898
+ self.responses[req.cmd] = None
899
+ return None
900
900
 
901
901
  if not req.has_res:
902
+ self.responses[req.cmd] = None
902
903
  return None
903
904
 
904
905
  try:
905
- return await asyncio.wait_for(q.get(), sensor_utils._TIMEOUT)
906
+ ret = await asyncio.wait_for(q.get(), 2)
907
+ now = datetime.now()
908
+ timestamp_now = now.timestamp()
909
+ q.put_nowait(timestamp_now)
910
+ return ret
906
911
  except Exception as e:
912
+ self.responses[req.cmd] = None
907
913
  return None
@@ -138,6 +138,9 @@ class SensorProfileDataCtx:
138
138
  await self.gForce.set_emg_raw_data_config(config)
139
139
 
140
140
  await self.gForce.set_package_id(True)
141
+
142
+ await self.gForce.set_function_switch(2)
143
+
141
144
  return data.channelCount
142
145
 
143
146
  async def initEEG(self, packageCount: int) -> int:
@@ -350,8 +353,9 @@ class SensorProfileDataCtx:
350
353
  if value == "ON":
351
354
  switch |= 8
352
355
  try:
353
- await self.gForce.set_firmware_filter_switch(switch)
354
- await asyncio.sleep(0.1)
356
+ ret = await self.gForce.set_firmware_filter_switch(switch)
357
+ if ret == None:
358
+ return "ERROR: not success"
355
359
  return "OK"
356
360
  except Exception as e:
357
361
  return "ERROR: " + str(e)
@@ -372,10 +376,8 @@ class SensorProfileDataCtx:
372
376
  ####################################################################################
373
377
 
374
378
  async def process_data(self, buf: Queue[SensorData], sensor, callback):
375
- while self._is_running and self._rawDataBuffer.empty():
376
- await asyncio.sleep(0.1)
377
-
378
- while self._is_running and not self._rawDataBuffer.empty():
379
+ while self._is_running:
380
+ while not self._rawDataBuffer.empty():
379
381
  try:
380
382
  data: bytes = self._rawDataBuffer.get_nowait()
381
383
  except Exception as e:
@@ -385,7 +387,7 @@ class SensorProfileDataCtx:
385
387
  self._processDataPackage(data, buf, sensor)
386
388
  self._rawDataBuffer.task_done()
387
389
 
388
- while self._is_running and self.isDataTransfering and not buf.empty():
390
+ if self.isDataTransfering and not buf.empty():
389
391
  sensorData: SensorData = None
390
392
  try:
391
393
  sensorData = buf.get_nowait()
@@ -398,6 +400,8 @@ class SensorProfileDataCtx:
398
400
  print(e)
399
401
 
400
402
  buf.task_done()
403
+ else:
404
+ await asyncio.sleep(0.01)
401
405
 
402
406
  def _processDataPackage(self, data: bytes, buf: Queue[SensorData], sensor):
403
407
  v = data[0] & 0x7F
@@ -673,7 +677,21 @@ class SensorProfileDataCtx:
673
677
 
674
678
  while self._is_running:
675
679
  while self._is_running and self._rawDataBuffer.empty():
676
- await asyncio.sleep(0.1)
680
+ if self._is_running and self.isDataTransfering and not buf.empty():
681
+ sensorData: SensorData = None
682
+ try:
683
+ sensorData = buf.get_nowait()
684
+ except Exception as e:
685
+ break
686
+ if not sensor_utils._terminated and sensorData != None and callback != None:
687
+ try:
688
+ asyncio.get_event_loop().run_in_executor(self.dataPool, callback, sensor, sensorData)
689
+ except Exception as e:
690
+ print(e)
691
+
692
+ buf.task_done()
693
+ else:
694
+ await asyncio.sleep(0.01)
677
695
  continue
678
696
 
679
697
  try:
@@ -708,21 +726,7 @@ class SensorProfileDataCtx:
708
726
  if self._is_data_transfering:
709
727
  data_package = bytes(self._concatDataBuffer[index + 2 : index + 2 + n])
710
728
  self._processDataPackage(data_package, buf, sensor)
711
- while self._is_running and self.isDataTransfering and not buf.empty():
712
- sensorData: SensorData = None
713
- try:
714
- sensorData = buf.get_nowait()
715
- except Exception as e:
716
- break
717
- if not sensor_utils._terminated and sensorData != None and callback != None:
718
- try:
719
- asyncio.get_event_loop().run_in_executor(self.dataPool, callback, sensor, sensorData)
720
- except Exception as e:
721
- print(e)
722
-
723
- buf.task_done()
724
729
  last_cut = index = index + 2 + n
725
-
726
730
  elif self._concatDataBuffer[index] == 0xAA:
727
731
  if (index + 1) >= data_size:
728
732
  index += 1
@@ -737,10 +741,10 @@ class SensorProfileDataCtx:
737
741
  index += 1
738
742
  continue
739
743
  data_package = bytes(self._concatDataBuffer[index + 2 : index + 2 + n])
744
+
740
745
  if not sensor_utils._terminated:
741
- asyncio.get_event_loop().run_in_executor(None, self.gForce._on_cmd_response, None, data_package)
746
+ await sensor_utils.async_call(self.gForce.async_on_cmd_response(data_package), runloop=sensor._event_loop)
742
747
  last_cut = index = index + 2 + n
743
-
744
748
  else:
745
749
  index += 1
746
750
 
sensor/sensor_profile.py CHANGED
@@ -2,6 +2,7 @@
2
2
  # 该枚举类定义了设备的各种状态,用于表示设备在不同操作阶段的状态信息
3
3
  from enum import Enum, IntEnum
4
4
  from queue import Queue
5
+ import threading
5
6
  import time
6
7
  from typing import Callable, Optional
7
8
 
@@ -60,7 +61,11 @@ class SensorProfile:
60
61
  self._data_ctx: SensorProfileDataCtx = None
61
62
  self._gforce: GForce = None
62
63
  self._data_event_loop: asyncio.AbstractEventLoop = None
64
+ self._data_event_thread: threading.Thread = None
65
+ self._gforce_event_loop: asyncio.AbstractEventLoop = None
66
+ self._gforce_event_thread: threading.Thread = None
63
67
  self._event_loop: asyncio.AbstractEventLoop = None
68
+ self._event_thread: threading.Thread = None
64
69
  self._is_starting = False
65
70
  self._is_setting_param = False
66
71
 
@@ -89,6 +94,17 @@ class SensorProfile:
89
94
  except Exception as e:
90
95
  pass
91
96
 
97
+ if self._gforce_event_loop != None:
98
+ try:
99
+ self._gforce_event_loop.stop()
100
+ self._gforce_event_loop.close()
101
+ self._gforce_event_loop = None
102
+ except Exception as e:
103
+ pass
104
+
105
+ self._is_starting = False
106
+ self._is_setting_param = False
107
+
92
108
  @property
93
109
  def deviceState(self) -> DeviceStateEx:
94
110
  """
@@ -211,14 +227,13 @@ class SensorProfile:
211
227
  """
212
228
  self._on_power_changed = callback
213
229
 
214
- async def _connect(self) -> bool:
215
- if sensor_utils._terminated:
216
- return False
230
+ async def _initGforce(self):
217
231
 
218
- if self._event_loop == None:
219
- self._event_loop = asyncio.new_event_loop()
220
- self._data_buffer: Queue[SensorData] = Queue()
221
- self._raw_data_buf: Queue[bytes] = Queue()
232
+ self._gforce_event_loop = asyncio.new_event_loop()
233
+ self._gforce_event_thread = threading.Thread(target=sensor_utils.start_loop, args=(self._gforce_event_loop,))
234
+ self._gforce_event_thread.daemon = True
235
+ self._gforce_event_thread.name = self._detail_device.name + "data event"
236
+ self._gforce_event_thread.start()
222
237
 
223
238
  if self._gforce == None:
224
239
  if self._adv.service_data.get(SERVICE_GUID) != None:
@@ -228,30 +243,57 @@ class SensorProfile:
228
243
  OYM_CMD_NOTIFY_CHAR_UUID,
229
244
  OYM_DATA_NOTIFY_CHAR_UUID,
230
245
  False,
246
+ self._event_loop,
247
+ self._gforce_event_loop,
231
248
  )
232
249
  elif self._adv.service_data.get(RFSTAR_SERVICE_GUID) != None:
233
250
  # print("RFSTAR_SERVICE:" + self._detail_device.name)
234
- self._gforce = GForce(self._detail_device, RFSTAR_CMD_UUID, RFSTAR_DATA_UUID, True)
235
- self._data_event_loop = asyncio.new_event_loop()
251
+ self._gforce = GForce(
252
+ self._detail_device, RFSTAR_CMD_UUID, RFSTAR_DATA_UUID, True, self._event_loop, self._gforce_event_loop
253
+ )
254
+
236
255
  else:
237
256
  print("Invalid device service uuid:" + self._detail_device.name + str(self._adv))
238
257
  return False
239
258
 
259
+ self._data_event_loop = asyncio.new_event_loop()
260
+ self._data_event_thread = threading.Thread(target=sensor_utils.start_loop, args=(self._data_event_loop,))
261
+ self._data_event_thread.daemon = True
262
+ self._data_event_thread.name = self._detail_device.name + "data event"
263
+ self._data_event_thread.start()
264
+
240
265
  if self._data_ctx == None and self._gforce != None:
241
266
  self._data_ctx = SensorProfileDataCtx(self._gforce, self._device.Address, self._raw_data_buf)
242
267
  if self._data_ctx.isUniversalStream:
243
- async_exec(self._process_universal_data())
268
+ async_exec(self._process_universal_data(), self._data_event_loop)
244
269
  else:
245
- async_exec(self._process_data())
270
+ async_exec(self._process_data(), self._data_event_loop)
271
+
272
+ async def _connect(self) -> bool:
273
+ if sensor_utils._terminated:
274
+ return False
275
+
276
+ if self._event_loop == None:
277
+ self._event_loop = asyncio.new_event_loop()
278
+ self._event_thread = threading.Thread(target=sensor_utils.start_loop, args=(self._event_loop,))
279
+ self._event_thread.daemon = True
280
+ self._event_thread.name = self._detail_device.name + "event"
281
+ self._event_thread.start()
282
+
283
+ self._data_buffer: Queue[SensorData] = Queue()
284
+ self._raw_data_buf: Queue[bytes] = Queue()
246
285
 
247
286
  if self.deviceState == DeviceStateEx.Connected or self.deviceState == DeviceStateEx.Ready:
248
287
  return True
288
+
249
289
  self._set_device_state(DeviceStateEx.Connecting)
250
290
 
291
+ await async_call(self._initGforce(), runloop=self._event_loop)
292
+
251
293
  def handle_disconnect(_: BleakClient):
252
294
  if self._data_ctx != None:
253
295
  self._data_ctx.close()
254
- time.sleep(1)
296
+ time.sleep(0.2)
255
297
  self._data_buffer.queue.clear()
256
298
  self._data_ctx = None
257
299
  self._gforce = None
@@ -293,7 +335,7 @@ class SensorProfile:
293
335
 
294
336
  async def _waitForDisconnect(self) -> bool:
295
337
  while not sensor_utils._terminated and self.deviceState != DeviceStateEx.Disconnected:
296
- await asyncio.sleep(1)
338
+ await asyncio.sleep(0.1)
297
339
  return True
298
340
 
299
341
  async def _disconnect(self) -> bool:
@@ -330,6 +372,7 @@ class SensorProfile:
330
372
 
331
373
  async def _process_universal_data(self):
332
374
  await self._data_ctx.processUniversalData(self._data_buffer, self, self._on_data_callback)
375
+ print("finished")
333
376
 
334
377
  async def _startDataNotification(self) -> bool:
335
378
  if self.deviceState != DeviceStateEx.Ready:
@@ -342,16 +385,12 @@ class SensorProfile:
342
385
  if self._data_ctx.isDataTransfering:
343
386
  return True
344
387
 
345
- if self._data_event_loop == None:
346
- self._data_event_loop = asyncio.new_event_loop()
347
-
348
388
  self._raw_data_buf.queue.clear()
349
389
  self._data_buffer.queue.clear()
350
390
 
351
- result = await self._data_ctx.start_streaming()
391
+ result = await async_call(self._data_ctx.start_streaming(), runloop=None)
352
392
  await asyncio.sleep(0.2)
353
393
 
354
- self._is_starting = False
355
394
  return result
356
395
 
357
396
  def startDataNotification(self) -> bool:
@@ -364,8 +403,14 @@ class SensorProfile:
364
403
  if self._is_starting:
365
404
  return False
366
405
 
367
- self._is_starting = True
368
- return sync_call(self._startDataNotification())
406
+ try:
407
+ self._is_starting = True
408
+ ret = sync_call(self._startDataNotification())
409
+ self._is_starting = False
410
+ return ret
411
+ except Exception as e:
412
+ self._is_starting = False
413
+ print(e)
369
414
 
370
415
  async def asyncStartDataNotification(self) -> bool:
371
416
  """
@@ -377,8 +422,14 @@ class SensorProfile:
377
422
  if self._is_starting:
378
423
  return False
379
424
 
380
- self._is_starting = True
381
- return await async_call(self._startDataNotification())
425
+ try:
426
+ self._is_starting = True
427
+ ret = await async_call(self._startDataNotification())
428
+ self._is_starting = False
429
+ return ret
430
+ except Exception as e:
431
+ self._is_starting = False
432
+ print(e)
382
433
 
383
434
  async def _stopDataNotification(self) -> bool:
384
435
  if self.deviceState != DeviceStateEx.Ready:
@@ -391,8 +442,7 @@ class SensorProfile:
391
442
  if not self._data_ctx.isDataTransfering:
392
443
  return True
393
444
 
394
- result = await self._data_ctx.stop_streaming()
395
- self._is_starting = False
445
+ result = await async_call(self._data_ctx.stop_streaming(), runloop=None)
396
446
  return result
397
447
 
398
448
  def stopDataNotification(self) -> bool:
@@ -405,8 +455,14 @@ class SensorProfile:
405
455
  if self._is_starting:
406
456
  return False
407
457
 
408
- self._is_starting = True
409
- return sync_call(self._stopDataNotification())
458
+ try:
459
+ self._is_starting = True
460
+ ret = sync_call(self._stopDataNotification())
461
+ self._is_starting = False
462
+ return ret
463
+ except Exception as e:
464
+ self._is_starting = False
465
+ print(e)
410
466
 
411
467
  async def asyncStopDataNotification(self) -> bool:
412
468
  """
@@ -418,8 +474,14 @@ class SensorProfile:
418
474
  if self._is_starting:
419
475
  return False
420
476
 
421
- self._is_starting = True
422
- return await async_call(self._stopDataNotification())
477
+ try:
478
+ self._is_starting = True
479
+ ret = await async_call(self._stopDataNotification())
480
+ self._is_starting = False
481
+ return ret
482
+ except Exception as e:
483
+ self._is_starting = False
484
+ print(e)
423
485
 
424
486
  async def _refresh_power(self):
425
487
  while not sensor_utils._terminated and self.deviceState == DeviceStateEx.Ready:
@@ -443,6 +505,7 @@ class SensorProfile:
443
505
 
444
506
  if await self._data_ctx.init(packageSampleCount):
445
507
  self._power_interval = powerRefreshInterval
508
+ self._power = await self._gforce.get_battery_level()
446
509
  sensor_utils.async_exec(self._refresh_power())
447
510
 
448
511
  return self._data_ctx.hasInit()
@@ -477,6 +540,23 @@ class SensorProfile:
477
540
  20,
478
541
  )
479
542
 
543
+ async def _asyncGetBatteryLevel(self) -> int:
544
+ if self.deviceState != DeviceStateEx.Ready:
545
+ return -1
546
+ if self._data_ctx == None:
547
+ return -1
548
+ self._power = await self._gforce.get_battery_level()
549
+ return self._power
550
+
551
+ async def asyncGetBatteryLevel(self) -> int:
552
+ """
553
+ 获取传感器的电池电量。
554
+
555
+ :return: int: 传感器的电池电量。 正常0-100,-1为未知。
556
+
557
+ """
558
+ return await async_call(self._asyncGetBatteryLevel())
559
+
480
560
  def getBatteryLevel(self) -> int:
481
561
  """
482
562
  获取传感器的电池电量。
@@ -509,23 +589,11 @@ class SensorProfile:
509
589
 
510
590
  if key in ["FILTER_50Hz", "FILTER_60Hz", "FILTER_HPF", "FILTER_LPF"]:
511
591
  if value in ["ON", "OFF"]:
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
592
  result = await self._data_ctx.setFilter(key, value)
521
- if needPauseTransfer:
522
- self._is_starting = True
523
- await self._startDataNotification()
524
593
 
525
594
  if key == "DEBUG_BLE_DATA_PATH":
526
595
  result = await self._data_ctx.setDebugCSV(value)
527
596
 
528
- self._is_setting_param = False
529
597
  return result
530
598
 
531
599
  def setParam(self, key: str, value: str) -> str:
@@ -541,11 +609,17 @@ class SensorProfile:
541
609
  if self._is_setting_param:
542
610
  return "Error: Please wait for the previous operation to complete"
543
611
 
544
- self._is_setting_param = True
545
- return sync_call(
546
- self._setParam(key, value),
547
- 20,
548
- )
612
+ try:
613
+ self._is_setting_param = True
614
+ ret = sync_call(
615
+ self._setParam(key, value),
616
+ 1,
617
+ )
618
+ self._is_setting_param = False
619
+ return ret
620
+ except Exception as e:
621
+ self._is_setting_param = False
622
+ print(e)
549
623
 
550
624
  async def asyncSetParam(self, key: str, value: str) -> str:
551
625
  """
@@ -560,8 +634,14 @@ class SensorProfile:
560
634
  if self._is_setting_param:
561
635
  return "Error: Please wait for the previous operation to complete"
562
636
 
563
- self._is_setting_param = True
564
- return await async_call(
565
- self._setParam(key, value),
566
- 20,
567
- )
637
+ try:
638
+ self._is_setting_param = True
639
+ ret = await async_call(
640
+ self._setParam(key, value),
641
+ 1,
642
+ )
643
+ self._is_setting_param = False
644
+ return ret
645
+ except Exception as e:
646
+ self._is_setting_param = False
647
+ print(e)
sensor/sensor_utils.py CHANGED
@@ -15,7 +15,7 @@ _needCloseRunloop = False
15
15
 
16
16
  def checkRunLoop():
17
17
  global _runloop, _needCloseRunloop, _event_thread
18
- if _runloop == None:
18
+ if _runloop == None or not _runloop.is_running():
19
19
  try:
20
20
  _runloop = asyncio.get_running_loop()
21
21
  except Exception as e:
@@ -45,11 +45,13 @@ def Terminate():
45
45
  pass
46
46
 
47
47
 
48
- def async_exec(function):
48
+ def async_exec(function, runloop=None):
49
49
  checkRunLoop()
50
+ if runloop == None:
51
+ runloop = _runloop
50
52
  task: asyncio.Future = None
51
53
  try:
52
- task = asyncio.run_coroutine_threadsafe(function, _runloop)
54
+ task = asyncio.run_coroutine_threadsafe(function, runloop)
53
55
  running_tasks.add(task)
54
56
  task.add_done_callback(lambda t: running_tasks.remove(t))
55
57
  except Exception as e:
@@ -57,11 +59,13 @@ def async_exec(function):
57
59
  pass
58
60
 
59
61
 
60
- def sync_call(function, _timeout=_TIMEOUT) -> any:
62
+ def sync_call(function, _timeout=_TIMEOUT, runloop=None) -> any:
61
63
  checkRunLoop()
62
64
  task: asyncio.Future = None
65
+ if runloop == None:
66
+ runloop = _runloop
63
67
  try:
64
- task = asyncio.run_coroutine_threadsafe(asyncio.wait_for(function, _timeout), _runloop)
68
+ task = asyncio.run_coroutine_threadsafe(asyncio.wait_for(function, _timeout), runloop)
65
69
  running_tasks.add(task)
66
70
  task.add_done_callback(lambda t: running_tasks.remove(t))
67
71
  return task.result(timeout=_timeout)
@@ -70,11 +74,13 @@ def sync_call(function, _timeout=_TIMEOUT) -> any:
70
74
  pass
71
75
 
72
76
 
73
- async def async_call(function, _timeout=_TIMEOUT) -> any:
77
+ async def async_call(function, _timeout=_TIMEOUT, runloop=None) -> any:
74
78
  checkRunLoop()
75
79
  task: asyncio.Future = None
80
+ if runloop == None:
81
+ runloop = _runloop
76
82
  try:
77
- task = asyncio.run_coroutine_threadsafe(asyncio.wait_for(function, _timeout), _runloop)
83
+ task = asyncio.run_coroutine_threadsafe(asyncio.wait_for(function, _timeout), runloop)
78
84
  running_tasks.add(task)
79
85
  task.add_done_callback(lambda t: running_tasks.remove(t))
80
86
  except Exception as e:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: sensor-sdk
3
- Version: 0.0.18
3
+ Version: 0.0.31
4
4
  Summary: Python sdk for Synchroni
5
5
  Home-page: https://github.com/oymotion/SynchroniSDKPython
6
6
  Author: Martin Ye
@@ -11,6 +11,15 @@ License-File: LICENSE.txt
11
11
  Requires-Dist: numpy
12
12
  Requires-Dist: setuptools
13
13
  Requires-Dist: bleak
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: license-file
20
+ Dynamic: requires-dist
21
+ Dynamic: requires-python
22
+ Dynamic: summary
14
23
 
15
24
  # sensor-sdk
16
25
 
@@ -0,0 +1,14 @@
1
+ sensor/__init__.py,sha256=L1VyAP0EDEnJIMeMTzp4iXHSRUUHyHScF_GIl3iYKRI,123
2
+ sensor/gforce.py,sha256=k6nHsNV_IM8ymGYrZXXD3oHC5Ed0qODlvk8W6dhtMMk,26291
3
+ sensor/sensor_controller.py,sha256=cjVOcGaU9_8_eWFamtOpXsqSZMzD7lGU24gC_rsm2FQ,9546
4
+ sensor/sensor_data.py,sha256=vKreLHZs7WbQcnfqHiLLfHQ3cCmvD1K2xl2WUQg8vv0,4005
5
+ sensor/sensor_data_context.py,sha256=YGDEbUYElHE8PZLQIcCR08N8Jk3j5UNH2odo43nmba8,30856
6
+ sensor/sensor_device.py,sha256=eO1vaqjxCc2UCPBoKXqlk6o498uRyWt6IYs7r7wXSD0,3042
7
+ sensor/sensor_profile.py,sha256=KDmOQjBM_diG5PDVth9s1ALN-Sd4Y_zpEVkDjW-khSk,22654
8
+ sensor/sensor_utils.py,sha256=7JIZF6uhOcHuAPXwwrQi5FwGU5BVLc6Aml-n9QXzupM,6998
9
+ sensor_sdk-0.0.31.dist-info/licenses/LICENSE.txt,sha256=8CSivOpub3IuXODTyqBRI91AxouJZk02YrcKuOAkWu8,1111
10
+ sensor_sdk-0.0.31.dist-info/METADATA,sha256=Y25zWQWh_rF42spGr1eZf9DDQtQ_fS33vxJNYfHReTQ,10029
11
+ sensor_sdk-0.0.31.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
12
+ sensor_sdk-0.0.31.dist-info/top_level.txt,sha256=Ftq49B6bH0Ffdc7c8LkcyakHo6lsg_snlBbpEUoILSk,7
13
+ sensor_sdk-0.0.31.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
14
+ sensor_sdk-0.0.31.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,14 +0,0 @@
1
- sensor/__init__.py,sha256=L1VyAP0EDEnJIMeMTzp4iXHSRUUHyHScF_GIl3iYKRI,123
2
- sensor/gforce.py,sha256=aWE4qFSnr5fLqsXE0uNg4t-sp0QVNreKQIEfjk2vVgs,26148
3
- sensor/sensor_controller.py,sha256=cjVOcGaU9_8_eWFamtOpXsqSZMzD7lGU24gC_rsm2FQ,9546
4
- sensor/sensor_data.py,sha256=vKreLHZs7WbQcnfqHiLLfHQ3cCmvD1K2xl2WUQg8vv0,4005
5
- sensor/sensor_data_context.py,sha256=5I8AZ6e-qlnCLRydbW9MXLJTpNpI0uzPLqflVFzpVeA,30878
6
- sensor/sensor_device.py,sha256=eO1vaqjxCc2UCPBoKXqlk6o498uRyWt6IYs7r7wXSD0,3042
7
- sensor/sensor_profile.py,sha256=xe9QT3_lbrwS8pcVUVvVQavXZrRFKLl4Dj9eUs5hsFk,19747
8
- sensor/sensor_utils.py,sha256=TkAY6tv3hWEsTbMipI7DCelVYZUpRuILLQ-b2IxFIC4,6771
9
- sensor_sdk-0.0.18.dist-info/LICENSE.txt,sha256=8CSivOpub3IuXODTyqBRI91AxouJZk02YrcKuOAkWu8,1111
10
- sensor_sdk-0.0.18.dist-info/METADATA,sha256=JYfyaSXzq8L1C9xbsZk66ny4sDFl2ut7Ed6gG8LaiI8,9821
11
- sensor_sdk-0.0.18.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
12
- sensor_sdk-0.0.18.dist-info/top_level.txt,sha256=Ftq49B6bH0Ffdc7c8LkcyakHo6lsg_snlBbpEUoILSk,7
13
- sensor_sdk-0.0.18.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
14
- sensor_sdk-0.0.18.dist-info/RECORD,,