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 +81 -75
- sensor/sensor_data_context.py +28 -24
- sensor/sensor_profile.py +130 -50
- sensor/sensor_utils.py +13 -7
- {sensor_sdk-0.0.18.dist-info → sensor_sdk-0.0.31.dist-info}/METADATA +11 -2
- sensor_sdk-0.0.31.dist-info/RECORD +14 -0
- {sensor_sdk-0.0.18.dist-info → sensor_sdk-0.0.31.dist-info}/WHEEL +1 -1
- sensor_sdk-0.0.18.dist-info/RECORD +0 -14
- {sensor_sdk-0.0.18.dist-info → sensor_sdk-0.0.31.dist-info/licenses}/LICENSE.txt +0 -0
- {sensor_sdk-0.0.18.dist-info → sensor_sdk-0.0.31.dist-info}/top_level.txt +0 -0
- {sensor_sdk-0.0.18.dist-info → sensor_sdk-0.0.31.dist-info}/zip-safe +0 -0
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__(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
sensor/sensor_data_context.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
215
|
-
if sensor_utils._terminated:
|
|
216
|
-
return False
|
|
230
|
+
async def _initGforce(self):
|
|
217
231
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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(
|
|
235
|
-
|
|
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(
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
381
|
-
|
|
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
|
-
|
|
409
|
-
|
|
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
|
-
|
|
422
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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,
|
|
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),
|
|
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),
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: sensor-sdk
|
|
3
|
-
Version: 0.0.
|
|
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,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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|