sensor-sdk 0.0.6__tar.gz → 0.0.33__tar.gz

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.
@@ -1,13 +1,25 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: sensor-sdk
3
- Version: 0.0.6
3
+ Version: 0.0.33
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
+ Requires-Dist: numpy
12
+ Requires-Dist: setuptools
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
11
23
 
12
24
  # sensor-sdk
13
25
 
@@ -92,10 +104,10 @@ isScanning = SensorControllerInstance.isScanning
92
104
 
93
105
  ### 5. Check if bluetooth is enabled
94
106
 
95
- Use `property isEnabled: bool` to check if bluetooth is enabled
107
+ Use `property isEnable: bool` to check if bluetooth is enable
96
108
 
97
109
  ```python
98
- isEnabled = SensorControllerInstance.isEnabled
110
+ isEnable = SensorControllerInstance.isEnable
99
111
  ```
100
112
 
101
113
  ### 6. Create SensorProfile
@@ -347,8 +359,47 @@ batteryPower = sensorProfile.getBatteryLevel()
347
359
 
348
360
  Please check console.py in examples directory
349
361
 
350
- ## Async methods
362
+ ### Async methods
351
363
 
352
364
  all methods start with async is async methods, they has same params and return result as sync methods.
353
365
 
354
366
  Please check async_console.py in examples directory
367
+
368
+ ### setParam method
369
+
370
+ Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
371
+
372
+ Below is available key and value:
373
+
374
+ ```python
375
+ result = sensorProfile.setParam("NTF_EMG", "ON")
376
+ # set EMG data to ON or OFF, result is "OK" if succeed
377
+
378
+ result = sensorProfile.setParam("NTF_EEG", "ON")
379
+ # set EEG data to ON or OFF, result is "OK" if succeed
380
+
381
+ result = sensorProfile.setParam("NTF_ECG", "ON")
382
+ # set ECG data to ON or OFF, result is "OK" if succeed
383
+
384
+ result = sensorProfile.setParam("NTF_IMU", "ON")
385
+ # set IMU data to ON or OFF, result is "OK" if succeed
386
+
387
+ result = sensorProfile.setParam("NTF_BRTH", "ON")
388
+ # set BRTH data to ON or OFF, result is "OK" if succeed
389
+
390
+ result = sensorProfile.setParam("FILTER_50HZ", "ON")
391
+ # set 50Hz notch filter to ON or OFF, result is "OK" if succeed
392
+
393
+ result = sensorProfile.setParam("FILTER_60HZ", "ON")
394
+ # set 60Hz notch filter to ON or OFF, result is "OK" if succeed
395
+
396
+ result = sensorProfile.setParam("FILTER_HPF", "ON")
397
+ # set 0.5Hz hpf filter to ON or OFF, result is "OK" if succeed
398
+
399
+ result = sensorProfile.setParam("FILTER_LPF", "ON")
400
+ # set 80Hz lpf filter to ON or OFF, result is "OK" if succeed
401
+
402
+ result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.csv")
403
+ # set debug ble data path, result is "OK" if succeed
404
+ # please give an absolute path and make sure it is valid and writeable by yourself
405
+ ```
@@ -1,14 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: sensor-sdk
3
- Version: 0.0.6
4
- Summary: Python sdk for Synchroni
5
- Home-page: https://github.com/oymotion/SynchroniSDKPython
6
- Author: Martin Ye
7
- Author-email: yecq_82@hotmail.com
8
- Requires-Python: >=3.8.0
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE.txt
11
-
12
1
  # sensor-sdk
13
2
 
14
3
  Synchroni sdk for Python
@@ -92,10 +81,10 @@ isScanning = SensorControllerInstance.isScanning
92
81
 
93
82
  ### 5. Check if bluetooth is enabled
94
83
 
95
- Use `property isEnabled: bool` to check if bluetooth is enabled
84
+ Use `property isEnable: bool` to check if bluetooth is enable
96
85
 
97
86
  ```python
98
- isEnabled = SensorControllerInstance.isEnabled
87
+ isEnable = SensorControllerInstance.isEnable
99
88
  ```
100
89
 
101
90
  ### 6. Create SensorProfile
@@ -347,8 +336,47 @@ batteryPower = sensorProfile.getBatteryLevel()
347
336
 
348
337
  Please check console.py in examples directory
349
338
 
350
- ## Async methods
339
+ ### Async methods
351
340
 
352
341
  all methods start with async is async methods, they has same params and return result as sync methods.
353
342
 
354
343
  Please check async_console.py in examples directory
344
+
345
+ ### setParam method
346
+
347
+ Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
348
+
349
+ Below is available key and value:
350
+
351
+ ```python
352
+ result = sensorProfile.setParam("NTF_EMG", "ON")
353
+ # set EMG data to ON or OFF, result is "OK" if succeed
354
+
355
+ result = sensorProfile.setParam("NTF_EEG", "ON")
356
+ # set EEG data to ON or OFF, result is "OK" if succeed
357
+
358
+ result = sensorProfile.setParam("NTF_ECG", "ON")
359
+ # set ECG data to ON or OFF, result is "OK" if succeed
360
+
361
+ result = sensorProfile.setParam("NTF_IMU", "ON")
362
+ # set IMU data to ON or OFF, result is "OK" if succeed
363
+
364
+ result = sensorProfile.setParam("NTF_BRTH", "ON")
365
+ # set BRTH data to ON or OFF, result is "OK" if succeed
366
+
367
+ result = sensorProfile.setParam("FILTER_50HZ", "ON")
368
+ # set 50Hz notch filter to ON or OFF, result is "OK" if succeed
369
+
370
+ result = sensorProfile.setParam("FILTER_60HZ", "ON")
371
+ # set 60Hz notch filter to ON or OFF, result is "OK" if succeed
372
+
373
+ result = sensorProfile.setParam("FILTER_HPF", "ON")
374
+ # set 0.5Hz hpf filter to ON or OFF, result is "OK" if succeed
375
+
376
+ result = sensorProfile.setParam("FILTER_LPF", "ON")
377
+ # set 80Hz lpf filter to ON or OFF, result is "OK" if succeed
378
+
379
+ result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.csv")
380
+ # set debug ble data path, result is "OK" if succeed
381
+ # please give an absolute path and make sure it is valid and writeable by yourself
382
+ ```
@@ -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
@@ -16,7 +17,7 @@ from bleak import (
16
17
  BleakGATTCharacteristic,
17
18
  )
18
19
 
19
- from sensor import utils
20
+ from sensor import sensor_utils
20
21
 
21
22
 
22
23
  @dataclass
@@ -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,)
@@ -103,6 +105,8 @@ class Command(IntEnum):
103
105
  CMD_GET_BLE_MTU_INFO = (0xAE,)
104
106
  CMD_GET_BRT_CONFIG = (0xB3,)
105
107
 
108
+ CMD_SET_FRIMWARE_FILTER_SWITCH = (0xAA,)
109
+ CMD_GET_FRIMWARE_FILTER_SWITCH = (0xA9,)
106
110
  # Partial command packet, format: [CMD_PARTIAL_DATA, packet number in reverse order, packet content]
107
111
  MD_PARTIAL_DATA = 0xFF
108
112
 
@@ -371,10 +375,18 @@ class Response:
371
375
 
372
376
  class GForce:
373
377
  def __init__(
374
- self, device: BLEDevice, cmd_char: str, data_char: str, isUniversalStream: bool
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,
375
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,25 +405,33 @@ class GForce:
393
405
  self._raw_data_buf = buf
394
406
 
395
407
  try:
396
- await asyncio.wait_for(client.connect(), 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
 
400
412
  if not client.is_connected:
401
413
  return
402
- if not self._is_universal_stream:
403
- await asyncio.wait_for(
404
- client.start_notify(self.cmd_char, self._on_cmd_response),
405
- utils._TIMEOUT,
406
- )
407
- else:
408
- await asyncio.wait_for(
409
- client.start_notify(self.data_char, self._on_universal_response),
410
- utils._TIMEOUT,
411
- )
412
414
 
413
- def _on_data_response(self, q: Queue[bytes], bs: bytearray):
415
+ try:
416
+ if not self._is_universal_stream:
417
+ await sensor_utils.async_call(
418
+ client.start_notify(self.cmd_char, self._on_cmd_response),
419
+ sensor_utils._TIMEOUT,
420
+ self.gforce_event_loop,
421
+ )
422
+
423
+ else:
424
+ await sensor_utils.async_call(
425
+ client.start_notify(self.data_char, self._on_universal_response),
426
+ sensor_utils._TIMEOUT,
427
+ self.gforce_event_loop,
428
+ )
429
+ except Exception as e:
430
+ return
431
+
432
+ def _on_data_response(self, q: queue.Queue[bytes], bs: bytearray):
414
433
  bs = bytes(bs)
434
+
415
435
  full_packet = []
416
436
 
417
437
  is_partial_data = bs[0] == ResponseCode.PARTIAL_PACKET
@@ -438,71 +458,12 @@ class GForce:
438
458
  return
439
459
 
440
460
  q.put_nowait(bytes(full_packet))
441
- # data = None
442
- # data_type = DataType(full_packet[0])
443
- # packet = full_packet[1:]
444
- # match data_type:
445
- # case DataType.EMG_ADC:
446
- # data = self._convert_emg_to_raw(packet)
447
-
448
- # case DataType.ACC:
449
- # data = self._convert_acceleration_to_g(packet)
450
-
451
- # case DataType.GYO:
452
- # data = self._convert_gyro_to_dps(packet)
453
-
454
- # case DataType.MAG:
455
- # data = self._convert_magnetometer_to_ut(packet)
456
-
457
- # case DataType.EULER:
458
- # data = self._convert_euler(packet)
459
-
460
- # case DataType.QUAT:
461
- # data = self._convert_quaternion(packet)
462
-
463
- # case DataType.ROTA:
464
- # data = self._convert_rotation_matrix(packet)
465
-
466
- # case DataType.EMG_GEST: # It is not supported by the device (?)
467
- # data = self._convert_emg_gesture(packet)
468
-
469
- # case DataType.HID_MOUSE: # It is not supported by the device
470
- # pass
471
-
472
- # case DataType.HID_JOYSTICK: # It is not supported by the device
473
- # pass
474
-
475
- # case DataType.PARTIAL:
476
- # pass
477
- # case _:
478
- # raise Exception(
479
- # f"Unknown data type {data_type}, full packet: {full_packet}"
480
- # )
481
-
482
- # q.put_nowait(data)
483
-
484
- # def _convert_emg_to_raw(self, data: bytes) -> np.ndarray[np.integer]:
485
- # match self.resolution:
486
- # case SampleResolution.BITS_8:
487
- # dtype = np.uint8
488
-
489
- # case SampleResolution.BITS_12:
490
- # dtype = np.uint16
491
-
492
- # case _:
493
- # raise Exception(f"Unsupported resolution {self.resolution}")
494
-
495
- # emg_data = np.frombuffer(data, dtype=dtype)
496
-
497
- # return emg_data.reshape(-1, self._num_channels)
498
461
 
499
462
  @staticmethod
500
463
  def _convert_acceleration_to_g(data: bytes) -> np.ndarray[np.float32]:
501
464
  normalizing_factor = 65536.0
502
465
 
503
- acceleration_data = (
504
- np.frombuffer(data, dtype=np.int32).astype(np.float32) / normalizing_factor
505
- )
466
+ acceleration_data = np.frombuffer(data, dtype=np.int32).astype(np.float32) / normalizing_factor
506
467
  num_channels = 3
507
468
 
508
469
  return acceleration_data.reshape(-1, num_channels)
@@ -511,9 +472,7 @@ class GForce:
511
472
  def _convert_gyro_to_dps(data: bytes) -> np.ndarray[np.float32]:
512
473
  normalizing_factor = 65536.0
513
474
 
514
- gyro_data = (
515
- np.frombuffer(data, dtype=np.int32).astype(np.float32) / normalizing_factor
516
- )
475
+ gyro_data = np.frombuffer(data, dtype=np.int32).astype(np.float32) / normalizing_factor
517
476
  num_channels = 3
518
477
 
519
478
  return gyro_data.reshape(-1, num_channels)
@@ -522,9 +481,7 @@ class GForce:
522
481
  def _convert_magnetometer_to_ut(data: bytes) -> np.ndarray[np.float32]:
523
482
  normalizing_factor = 65536.0
524
483
 
525
- magnetometer_data = (
526
- np.frombuffer(data, dtype=np.int32).astype(np.float32) / normalizing_factor
527
- )
484
+ magnetometer_data = np.frombuffer(data, dtype=np.int32).astype(np.float32) / normalizing_factor
528
485
  num_channels = 3
529
486
 
530
487
  return magnetometer_data.reshape(-1, num_channels)
@@ -565,12 +522,18 @@ class GForce:
565
522
  self._raw_data_buf.put_nowait(bytes(bs))
566
523
 
567
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):
568
528
  try:
529
+ # print(bytes(bs))
569
530
  response = self._parse_response(bytes(bs))
570
- if response.cmd in self.responses:
531
+ if self.responses.get(response.cmd) != None:
571
532
  self.responses[response.cmd].put_nowait(
572
533
  response.data,
573
534
  )
535
+ else:
536
+ print("invalid response:" + bytes(bs))
574
537
  except Exception as e:
575
538
  raise Exception("Failed to parse response: %s" % e)
576
539
 
@@ -729,6 +692,17 @@ class GForce:
729
692
  )
730
693
  )
731
694
 
695
+ async def set_package_id(self, switchStatus):
696
+ body = [switchStatus == True]
697
+ body = bytes(body)
698
+ ret = await self._send_request(
699
+ Request(
700
+ cmd=Command.PACKAGE_ID_CONTROL,
701
+ body=body,
702
+ has_res=True,
703
+ )
704
+ )
705
+
732
706
  async def set_log_level(self, logLevel):
733
707
  body = [0xFF & logLevel]
734
708
  body = bytes(body)
@@ -740,9 +714,32 @@ class GForce:
740
714
  )
741
715
  )
742
716
 
717
+ async def set_function_switch(self, funcSwitch)-> bool:
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
+ if (len(ret) > 0 and ret[0] == 0):
728
+ return True
729
+ return False
730
+
731
+ async def set_firmware_filter_switch(self, switchStatus: int):
732
+ body = [0xFF & switchStatus]
733
+ body = bytes(body)
734
+ await self._send_request(Request(cmd=Command.CMD_SET_FRIMWARE_FILTER_SWITCH, body=body, has_res=True))
735
+
736
+ async def get_firmware_filter_switch(self):
737
+ buf = await self._send_request(Request(cmd=Command.CMD_GET_FRIMWARE_FILTER_SWITCH, has_res=True))
738
+ return buf[0]
739
+
743
740
  async def set_emg_raw_data_config(self, cfg=EmgRawDataConfig()):
744
741
  body = cfg.to_bytes()
745
- await self._send_request(
742
+ ret = await self._send_request(
746
743
  Request(
747
744
  cmd=Command.SET_EMG_RAWDATA_CONFIG,
748
745
  body=body,
@@ -750,7 +747,7 @@ class GForce:
750
747
  )
751
748
  )
752
749
 
753
- # print('_send_request returned:', ret)
750
+ # print('set_emg_raw_data_config returned:', ret)
754
751
 
755
752
  self.resolution = cfg.resolution
756
753
 
@@ -835,26 +832,20 @@ class GForce:
835
832
  )
836
833
 
837
834
  async def start_streaming(self, q: queue.Queue):
838
- await asyncio.wait_for(
835
+ await sensor_utils.async_call(
839
836
  self.client.start_notify(
840
837
  self.data_char,
841
838
  lambda _, data: self._on_data_response(q, data),
842
839
  ),
843
- utils._TIMEOUT,
840
+ sensor_utils._TIMEOUT,
841
+ self.gforce_event_loop,
844
842
  )
845
843
 
846
844
  async def stop_streaming(self):
847
845
  exceptions = []
846
+
848
847
  try:
849
- await asyncio.wait_for(
850
- self.set_subscription(DataSubscription.OFF), utils._TIMEOUT
851
- )
852
- except Exception as e:
853
- exceptions.append(e)
854
- try:
855
- await asyncio.wait_for(
856
- self.client.stop_notify(self.data_char), utils._TIMEOUT
857
- )
848
+ await sensor_utils.async_call(self.client.stop_notify(self.data_char), sensor_utils._TIMEOUT, self.gforce_event_loop)
858
849
  except Exception as e:
859
850
  exceptions.append(e)
860
851
 
@@ -864,28 +855,62 @@ class GForce:
864
855
  async def disconnect(self):
865
856
  with suppress(asyncio.CancelledError):
866
857
  try:
867
- await asyncio.wait_for(self.client.disconnect(), utils._TIMEOUT)
858
+ await sensor_utils.async_call(self.client.disconnect(), sensor_utils._TIMEOUT, self.gforce_event_loop)
868
859
  except Exception as e:
869
860
  pass
870
861
 
871
862
  def _get_response_channel(self, cmd: Command) -> Queue:
872
- q = Queue()
873
- self.responses[cmd] = q
874
- return q
863
+ if self.responses.get(cmd) != None:
864
+ return self.responses[cmd]
865
+ else:
866
+ q = Queue()
867
+ self.responses[cmd] = q
868
+ return q
875
869
 
876
870
  async def _send_request(self, req: Request) -> Optional[bytes]:
871
+ return await sensor_utils.async_call(self._send_request_internal(req=req), runloop=self.event_loop)
872
+
873
+ async def _send_request_internal(self, req: Request) -> Optional[bytes]:
877
874
  q = None
878
875
  if req.has_res:
879
876
  q = self._get_response_channel(req.cmd)
880
877
 
878
+ timeStamp_old = -1
879
+ while not q.empty():
880
+ timeStamp_old = q.get_nowait()
881
+
882
+ now = datetime.now()
883
+ timestamp_now = now.timestamp()
884
+ if (timeStamp_old > -1) and ((timestamp_now - timeStamp_old) < 3):
885
+ print("send request too fast")
886
+ q.put_nowait(timeStamp_old)
887
+ return None
888
+
881
889
  bs = bytes([req.cmd])
882
890
  if req.body is not None:
883
891
  bs += req.body
884
- await asyncio.wait_for(
885
- self.client.write_gatt_char(self.cmd_char, bs), utils._TIMEOUT
886
- )
892
+
893
+ # print(str(req.cmd) + str(req.body))
894
+ try:
895
+ await sensor_utils.async_call(
896
+ self.client.write_gatt_char(self.cmd_char, bs),
897
+ 1,
898
+ runloop=self.gforce_event_loop,
899
+ )
900
+ except Exception as e:
901
+ self.responses[req.cmd] = None
902
+ return None
887
903
 
888
904
  if not req.has_res:
905
+ self.responses[req.cmd] = None
889
906
  return None
890
907
 
891
- return await asyncio.wait_for(q.get(), utils._TIMEOUT)
908
+ try:
909
+ ret = await asyncio.wait_for(q.get(), 2)
910
+ now = datetime.now()
911
+ timestamp_now = now.timestamp()
912
+ q.put_nowait(timestamp_now)
913
+ return ret
914
+ except Exception as e:
915
+ self.responses[req.cmd] = None
916
+ return None
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from concurrent.futures import ThreadPoolExecutor
2
3
  import threading
3
4
  from typing import Callable, Dict, List, Optional, Tuple
@@ -5,11 +6,10 @@ from typing import Callable, Dict, List, Optional, Tuple
5
6
  import bleak
6
7
 
7
8
  from sensor import sensor_profile
8
- from sensor import utils
9
+ from sensor import sensor_utils
9
10
  from sensor.sensor_profile import DeviceStateEx, SensorProfile
10
- import asyncio
11
11
 
12
- from sensor.utils import async_call, start_loop, sync_call, async_exec, timer
12
+ from sensor.sensor_utils import async_call, sync_call, async_exec
13
13
  from bleak import (
14
14
  BleakScanner,
15
15
  AdvertisementData,
@@ -27,6 +27,7 @@ class SensorController:
27
27
  with SensorController._instance_lock:
28
28
  if not hasattr(SensorController, "_instance"):
29
29
  SensorController._instance = object.__new__(cls)
30
+
30
31
  return SensorController._instance
31
32
 
32
33
  """
@@ -37,21 +38,8 @@ class SensorController:
37
38
  """
38
39
  初始化 SensorController 实例。
39
40
  """
40
- self._event_loop = asyncio.new_event_loop()
41
- self._event_thread = threading.Thread(target=start_loop, args=(self._event_loop,))
42
- self._event_thread.daemon = True
43
- self._event_thread.name = "SensorController event"
44
- self._event_thread.start()
45
- self._gforce_event_loop = asyncio.new_event_loop()
46
- self._gforce_event_thread = threading.Thread(target=start_loop, args=(self._gforce_event_loop,))
47
- self._gforce_event_thread.daemon = True
48
- self._gforce_event_thread.name = "BLE operation"
49
- self._gforce_event_thread.start()
50
- self._scanner = BleakScanner(
51
- detection_callback=self._match_device,
52
- service_uuids=[SERVICE_GUID, RFSTAR_SERVICE_GUID],
53
- )
54
41
  self._is_scanning = False
42
+ self._scanner: BleakScanner = None
55
43
  self._device_callback: Callable[[List[sensor_profile.BLEDevice]], None] = None
56
44
  self._device_callback_period = 0
57
45
  self._enable_callback: Callable[[bool], None] = None
@@ -62,15 +50,16 @@ class SensorController:
62
50
  反初始化 SensorController 类的实例。
63
51
 
64
52
  """
65
- self._event_loop.close()
66
- self._gforce_event_loop.close()
67
53
 
68
54
  def terminate(self):
69
- utils._terminated = True
55
+ sensor_utils._terminated = True
56
+
70
57
  for sensor in self._sensor_profiles.values():
71
58
  if sensor.deviceState == DeviceStateEx.Connected or sensor.deviceState == DeviceStateEx.Ready:
72
59
  sensor._destroy()
73
60
 
61
+ sensor_utils.Terminate()
62
+
74
63
  def _match_device(self, _device: bleak.BLEDevice, adv: AdvertisementData):
75
64
  if _device.name == None:
76
65
  return False
@@ -82,7 +71,7 @@ class SensorController:
82
71
  return False
83
72
 
84
73
  @property
85
- def isScaning(self) -> bool:
74
+ def isScanning(self) -> bool:
86
75
  """
87
76
  检查是否正在扫描。
88
77
 
@@ -150,15 +139,23 @@ class SensorController:
150
139
  if deviceMap.get(mac) != None:
151
140
  devices.append(self._sensor_profiles[mac].BLEDevice)
152
141
  else:
153
- newSensor = SensorProfile(device, adv, mac, self._gforce_event_loop)
142
+ newSensor = SensorProfile(device, adv, mac)
154
143
  deviceMap[mac] = newSensor
155
144
  devices.append(newSensor.BLEDevice)
156
145
 
157
146
  self._sensor_profiles = deviceMap
158
147
  return devices
159
148
 
149
+ def _init_scan(self):
150
+ if self._scanner == None:
151
+ self._scanner = BleakScanner(
152
+ detection_callback=self._match_device,
153
+ service_uuids=[SERVICE_GUID, RFSTAR_SERVICE_GUID],
154
+ )
155
+
160
156
  async def _async_scan(self, period):
161
157
  self._is_scanning = True
158
+ self._init_scan()
162
159
  found_devices = await self._scanner.discover(timeout=period / 1000, return_adv=True)
163
160
  self._is_scanning = False
164
161
  return self._process_ble_devices(found_devices)
@@ -171,7 +168,7 @@ class SensorController:
171
168
 
172
169
  :return: List[sensor_profile.BLEDevice]: BLEDevice列表
173
170
  """
174
- return sync_call(self._gforce_event_loop, self._async_scan(period))
171
+ return sync_call(self._async_scan(period))
175
172
 
176
173
  async def asyncScan(self, period) -> List[sensor_profile.BLEDevice]:
177
174
  """
@@ -181,22 +178,23 @@ class SensorController:
181
178
 
182
179
  :return: List[sensor_profile.BLEDevice]: BLEDevice列表
183
180
  """
184
- return await async_call(self._gforce_event_loop, self._async_scan(period))
181
+ return await async_call(self._async_scan(period))
185
182
 
186
183
  async def _device_scan_callback(self, devices: List[sensor_profile.BLEDevice]):
187
- if self._device_callback:
184
+ if not sensor_utils._terminated and self._device_callback:
188
185
  try:
189
- self._device_callback(devices)
186
+ asyncio.get_event_loop().run_in_executor(None, self._device_callback, devices)
190
187
  except Exception as e:
191
188
  print(e)
192
189
 
193
- if self._is_scanning:
194
- async_exec(self._gforce_event_loop, self._startScan())
190
+ if not sensor_utils._terminated and self._is_scanning:
191
+ async_exec(self._startScan())
195
192
 
196
193
  async def _startScan(self) -> bool:
194
+ self._init_scan()
197
195
  found_devices = await self._scanner.discover(timeout=self._device_callback_period / 1000, return_adv=True)
198
196
  devices = self._process_ble_devices(found_devices)
199
- async_exec(self._event_loop, self._device_scan_callback(devices))
197
+ async_exec(self._device_scan_callback(devices))
200
198
 
201
199
  def startScan(self, periodInMs: int) -> bool:
202
200
  """
@@ -212,7 +210,7 @@ class SensorController:
212
210
  self._is_scanning = True
213
211
  self._device_callback_period = periodInMs
214
212
 
215
- async_exec(self._gforce_event_loop, self._startScan())
213
+ async_exec(self._startScan())
216
214
  return True
217
215
 
218
216
  def stopScan(self) -> None: