sensor-sdk 0.0.27__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.
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/PKG-INFO +16 -4
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/README.md +2 -2
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor/gforce.py +75 -90
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor/sensor_data.py +6 -3
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor/sensor_data_context.py +116 -53
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor/sensor_profile.py +64 -24
- sensor_sdk-0.0.33/sensor/sensor_utils.py +906 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor_sdk.egg-info/PKG-INFO +16 -4
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/setup.py +23 -23
- sensor-sdk-0.0.27/sensor/sensor_utils.py +0 -373
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/LICENSE.txt +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor/__init__.py +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor/sensor_controller.py +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor/sensor_device.py +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor_sdk.egg-info/SOURCES.txt +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor_sdk.egg-info/dependency_links.txt +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor_sdk.egg-info/requires.txt +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor_sdk.egg-info/top_level.txt +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/sensor_sdk.egg-info/zip-safe +0 -0
- {sensor-sdk-0.0.27 → sensor_sdk-0.0.33}/setup.cfg +0 -0
|
@@ -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.33
|
|
4
4
|
Summary: Python sdk for Synchroni
|
|
5
5
|
Home-page: https://github.com/oymotion/SynchroniSDKPython
|
|
6
6
|
Author: Martin Ye
|
|
@@ -8,6 +8,18 @@ Author-email: yecq_82@hotmail.com
|
|
|
8
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
|
|
|
@@ -375,10 +387,10 @@ result = sensorProfile.setParam("NTF_IMU", "ON")
|
|
|
375
387
|
result = sensorProfile.setParam("NTF_BRTH", "ON")
|
|
376
388
|
# set BRTH data to ON or OFF, result is "OK" if succeed
|
|
377
389
|
|
|
378
|
-
result = sensorProfile.setParam("
|
|
390
|
+
result = sensorProfile.setParam("FILTER_50HZ", "ON")
|
|
379
391
|
# set 50Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
380
392
|
|
|
381
|
-
result = sensorProfile.setParam("
|
|
393
|
+
result = sensorProfile.setParam("FILTER_60HZ", "ON")
|
|
382
394
|
# set 60Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
383
395
|
|
|
384
396
|
result = sensorProfile.setParam("FILTER_HPF", "ON")
|
|
@@ -364,10 +364,10 @@ result = sensorProfile.setParam("NTF_IMU", "ON")
|
|
|
364
364
|
result = sensorProfile.setParam("NTF_BRTH", "ON")
|
|
365
365
|
# set BRTH data to ON or OFF, result is "OK" if succeed
|
|
366
366
|
|
|
367
|
-
result = sensorProfile.setParam("
|
|
367
|
+
result = sensorProfile.setParam("FILTER_50HZ", "ON")
|
|
368
368
|
# set 50Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
369
369
|
|
|
370
|
-
result = sensorProfile.setParam("
|
|
370
|
+
result = sensorProfile.setParam("FILTER_60HZ", "ON")
|
|
371
371
|
# set 60Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
372
372
|
|
|
373
373
|
result = sensorProfile.setParam("FILTER_HPF", "ON")
|
|
@@ -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,12 +374,21 @@ 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
|
-
self.current_request: Request = None
|
|
381
392
|
self.responses: Dict[Command, Queue] = {}
|
|
382
393
|
self.resolution = SampleResolution.BITS_8
|
|
383
394
|
self._num_channels = 8
|
|
@@ -394,7 +405,7 @@ class GForce:
|
|
|
394
405
|
self._raw_data_buf = buf
|
|
395
406
|
|
|
396
407
|
try:
|
|
397
|
-
await
|
|
408
|
+
await sensor_utils.async_call(client.connect(), sensor_utils._TIMEOUT, self.gforce_event_loop)
|
|
398
409
|
except Exception as e:
|
|
399
410
|
return
|
|
400
411
|
|
|
@@ -403,19 +414,22 @@ class GForce:
|
|
|
403
414
|
|
|
404
415
|
try:
|
|
405
416
|
if not self._is_universal_stream:
|
|
406
|
-
await
|
|
417
|
+
await sensor_utils.async_call(
|
|
407
418
|
client.start_notify(self.cmd_char, self._on_cmd_response),
|
|
408
419
|
sensor_utils._TIMEOUT,
|
|
420
|
+
self.gforce_event_loop,
|
|
409
421
|
)
|
|
422
|
+
|
|
410
423
|
else:
|
|
411
|
-
await
|
|
424
|
+
await sensor_utils.async_call(
|
|
412
425
|
client.start_notify(self.data_char, self._on_universal_response),
|
|
413
426
|
sensor_utils._TIMEOUT,
|
|
427
|
+
self.gforce_event_loop,
|
|
414
428
|
)
|
|
415
429
|
except Exception as e:
|
|
416
430
|
return
|
|
417
431
|
|
|
418
|
-
def _on_data_response(self, q: Queue[bytes], bs: bytearray):
|
|
432
|
+
def _on_data_response(self, q: queue.Queue[bytes], bs: bytearray):
|
|
419
433
|
bs = bytes(bs)
|
|
420
434
|
|
|
421
435
|
full_packet = []
|
|
@@ -444,63 +458,6 @@ class GForce:
|
|
|
444
458
|
return
|
|
445
459
|
|
|
446
460
|
q.put_nowait(bytes(full_packet))
|
|
447
|
-
# data = None
|
|
448
|
-
# data_type = DataType(full_packet[0])
|
|
449
|
-
# packet = full_packet[1:]
|
|
450
|
-
# match data_type:
|
|
451
|
-
# case DataType.EMG_ADC:
|
|
452
|
-
# data = self._convert_emg_to_raw(packet)
|
|
453
|
-
|
|
454
|
-
# case DataType.ACC:
|
|
455
|
-
# data = self._convert_acceleration_to_g(packet)
|
|
456
|
-
|
|
457
|
-
# case DataType.GYO:
|
|
458
|
-
# data = self._convert_gyro_to_dps(packet)
|
|
459
|
-
|
|
460
|
-
# case DataType.MAG:
|
|
461
|
-
# data = self._convert_magnetometer_to_ut(packet)
|
|
462
|
-
|
|
463
|
-
# case DataType.EULER:
|
|
464
|
-
# data = self._convert_euler(packet)
|
|
465
|
-
|
|
466
|
-
# case DataType.QUAT:
|
|
467
|
-
# data = self._convert_quaternion(packet)
|
|
468
|
-
|
|
469
|
-
# case DataType.ROTA:
|
|
470
|
-
# data = self._convert_rotation_matrix(packet)
|
|
471
|
-
|
|
472
|
-
# case DataType.EMG_GEST: # It is not supported by the device (?)
|
|
473
|
-
# data = self._convert_emg_gesture(packet)
|
|
474
|
-
|
|
475
|
-
# case DataType.HID_MOUSE: # It is not supported by the device
|
|
476
|
-
# pass
|
|
477
|
-
|
|
478
|
-
# case DataType.HID_JOYSTICK: # It is not supported by the device
|
|
479
|
-
# pass
|
|
480
|
-
|
|
481
|
-
# case DataType.PARTIAL:
|
|
482
|
-
# pass
|
|
483
|
-
# case _:
|
|
484
|
-
# raise Exception(
|
|
485
|
-
# f"Unknown data type {data_type}, full packet: {full_packet}"
|
|
486
|
-
# )
|
|
487
|
-
|
|
488
|
-
# q.put_nowait(data)
|
|
489
|
-
|
|
490
|
-
# def _convert_emg_to_raw(self, data: bytes) -> np.ndarray[np.integer]:
|
|
491
|
-
# match self.resolution:
|
|
492
|
-
# case SampleResolution.BITS_8:
|
|
493
|
-
# dtype = np.uint8
|
|
494
|
-
|
|
495
|
-
# case SampleResolution.BITS_12:
|
|
496
|
-
# dtype = np.uint16
|
|
497
|
-
|
|
498
|
-
# case _:
|
|
499
|
-
# raise Exception(f"Unsupported resolution {self.resolution}")
|
|
500
|
-
|
|
501
|
-
# emg_data = np.frombuffer(data, dtype=dtype)
|
|
502
|
-
|
|
503
|
-
# return emg_data.reshape(-1, self._num_channels)
|
|
504
461
|
|
|
505
462
|
@staticmethod
|
|
506
463
|
def _convert_acceleration_to_g(data: bytes) -> np.ndarray[np.float32]:
|
|
@@ -565,6 +522,9 @@ 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:
|
|
569
529
|
# print(bytes(bs))
|
|
570
530
|
response = self._parse_response(bytes(bs))
|
|
@@ -754,6 +714,20 @@ class GForce:
|
|
|
754
714
|
)
|
|
755
715
|
)
|
|
756
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
|
+
|
|
757
731
|
async def set_firmware_filter_switch(self, switchStatus: int):
|
|
758
732
|
body = [0xFF & switchStatus]
|
|
759
733
|
body = bytes(body)
|
|
@@ -765,7 +739,7 @@ class GForce:
|
|
|
765
739
|
|
|
766
740
|
async def set_emg_raw_data_config(self, cfg=EmgRawDataConfig()):
|
|
767
741
|
body = cfg.to_bytes()
|
|
768
|
-
await self._send_request(
|
|
742
|
+
ret = await self._send_request(
|
|
769
743
|
Request(
|
|
770
744
|
cmd=Command.SET_EMG_RAWDATA_CONFIG,
|
|
771
745
|
body=body,
|
|
@@ -773,7 +747,7 @@ class GForce:
|
|
|
773
747
|
)
|
|
774
748
|
)
|
|
775
749
|
|
|
776
|
-
# print('
|
|
750
|
+
# print('set_emg_raw_data_config returned:', ret)
|
|
777
751
|
|
|
778
752
|
self.resolution = cfg.resolution
|
|
779
753
|
|
|
@@ -858,22 +832,20 @@ class GForce:
|
|
|
858
832
|
)
|
|
859
833
|
|
|
860
834
|
async def start_streaming(self, q: queue.Queue):
|
|
861
|
-
await
|
|
835
|
+
await sensor_utils.async_call(
|
|
862
836
|
self.client.start_notify(
|
|
863
837
|
self.data_char,
|
|
864
838
|
lambda _, data: self._on_data_response(q, data),
|
|
865
839
|
),
|
|
866
840
|
sensor_utils._TIMEOUT,
|
|
841
|
+
self.gforce_event_loop,
|
|
867
842
|
)
|
|
868
843
|
|
|
869
844
|
async def stop_streaming(self):
|
|
870
845
|
exceptions = []
|
|
871
|
-
|
|
872
|
-
# await asyncio.wait_for(self.set_subscription(DataSubscription.OFF), sensor_utils._TIMEOUT)
|
|
873
|
-
# except Exception as e:
|
|
874
|
-
# exceptions.append(e)
|
|
846
|
+
|
|
875
847
|
try:
|
|
876
|
-
await
|
|
848
|
+
await sensor_utils.async_call(self.client.stop_notify(self.data_char), sensor_utils._TIMEOUT, self.gforce_event_loop)
|
|
877
849
|
except Exception as e:
|
|
878
850
|
exceptions.append(e)
|
|
879
851
|
|
|
@@ -883,49 +855,62 @@ class GForce:
|
|
|
883
855
|
async def disconnect(self):
|
|
884
856
|
with suppress(asyncio.CancelledError):
|
|
885
857
|
try:
|
|
886
|
-
await
|
|
858
|
+
await sensor_utils.async_call(self.client.disconnect(), sensor_utils._TIMEOUT, self.gforce_event_loop)
|
|
887
859
|
except Exception as e:
|
|
888
860
|
pass
|
|
889
861
|
|
|
890
862
|
def _get_response_channel(self, cmd: Command) -> Queue:
|
|
891
863
|
if self.responses.get(cmd) != None:
|
|
892
|
-
return
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
864
|
+
return self.responses[cmd]
|
|
865
|
+
else:
|
|
866
|
+
q = Queue()
|
|
867
|
+
self.responses[cmd] = q
|
|
868
|
+
return q
|
|
897
869
|
|
|
898
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)
|
|
899
872
|
|
|
873
|
+
async def _send_request_internal(self, req: Request) -> Optional[bytes]:
|
|
900
874
|
q = None
|
|
901
875
|
if req.has_res:
|
|
902
876
|
q = self._get_response_channel(req.cmd)
|
|
903
|
-
if q == None:
|
|
904
|
-
# print("duplicate")
|
|
905
|
-
return None
|
|
906
877
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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
|
|
910
888
|
|
|
911
|
-
self.current_request = req
|
|
912
889
|
bs = bytes([req.cmd])
|
|
913
890
|
if req.body is not None:
|
|
914
891
|
bs += req.body
|
|
915
892
|
|
|
916
893
|
# print(str(req.cmd) + str(req.body))
|
|
917
|
-
|
|
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
|
|
918
903
|
|
|
919
904
|
if not req.has_res:
|
|
920
|
-
self.
|
|
905
|
+
self.responses[req.cmd] = None
|
|
921
906
|
return None
|
|
922
907
|
|
|
923
908
|
try:
|
|
924
|
-
ret = await asyncio.wait_for(q.get(),
|
|
925
|
-
|
|
926
|
-
|
|
909
|
+
ret = await asyncio.wait_for(q.get(), 2)
|
|
910
|
+
now = datetime.now()
|
|
911
|
+
timestamp_now = now.timestamp()
|
|
912
|
+
q.put_nowait(timestamp_now)
|
|
927
913
|
return ret
|
|
928
914
|
except Exception as e:
|
|
929
|
-
self.current_request = None
|
|
930
915
|
self.responses[req.cmd] = None
|
|
931
916
|
return None
|
|
@@ -41,10 +41,12 @@ class DataType(IntEnum):
|
|
|
41
41
|
NTF_EMG = 0x8 # EMG,用于标识肌电传感器采集的数据
|
|
42
42
|
NTF_EEG = 0x10 # EEG,用于标识脑电传感器采集的数据
|
|
43
43
|
NTF_ECG = 0x11 # ECG,用于标识心电传感器采集的数据
|
|
44
|
-
NTF_IMPEDANCE =
|
|
45
|
-
NTF_IMU =
|
|
46
|
-
NTF_ADS =
|
|
44
|
+
NTF_IMPEDANCE = 0x12 # 阻抗数据
|
|
45
|
+
NTF_IMU = 0x13 # 包含ACC和GYRO数据
|
|
46
|
+
NTF_ADS = 0x14 # 无单位ads数据
|
|
47
47
|
NTF_BRTH = 0x15 # 呼吸,用于标识呼吸传感器采集的数据
|
|
48
|
+
NTF_IMPEDANCE_EXT = 0x16 # 阻抗数据扩展
|
|
49
|
+
NTF_DATA_TYPE_MAX = 0x17
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
# 一次采样的数据,包含多个通道的数据,channal_samples 为一个二维数组, 第一个维度为通道索引,第二个维度为采样索引
|
|
@@ -81,6 +83,7 @@ class SensorData:
|
|
|
81
83
|
self.sampleRate = 0
|
|
82
84
|
self.channelCount = 0
|
|
83
85
|
self.packageSampleCount = 0
|
|
86
|
+
self.packageIndexLength = 2
|
|
84
87
|
self.channelSamples: List[List[Sample]] = list()
|
|
85
88
|
self.lastPackageCounter = 0
|
|
86
89
|
self.lastPackageIndex = 0
|
|
@@ -61,7 +61,7 @@ class SensorProfileDataCtx:
|
|
|
61
61
|
self.saturationData: List[float] = list()
|
|
62
62
|
self.dataPool = ThreadPoolExecutor(1, "data")
|
|
63
63
|
self.init_map = {"NTF_EMG": "ON", "NTF_EEG": "ON", "NTF_ECG": "ON", "NTF_IMU": "ON", "NTF_BRTH": "ON"}
|
|
64
|
-
self.filter_map = {"
|
|
64
|
+
self.filter_map = {"FILTER_50HZ": "ON", "FILTER_60HZ": "ON", "FILTER_HPF": "ON", "FILTER_LPF": "ON"}
|
|
65
65
|
self.debugCSVWriter = None
|
|
66
66
|
self.debugCSVPath = None
|
|
67
67
|
|
|
@@ -126,18 +126,41 @@ class SensorProfileDataCtx:
|
|
|
126
126
|
data.channelMask = config.channel_mask
|
|
127
127
|
data.minPackageSampleCount = packageCount
|
|
128
128
|
data.packageSampleCount = 8
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
data.clear()
|
|
131
|
-
|
|
132
|
-
self.
|
|
131
|
+
|
|
132
|
+
await self.gForce.set_package_id(True)
|
|
133
|
+
|
|
134
|
+
isNewEMG = await self.gForce.set_function_switch(2)
|
|
135
|
+
|
|
136
|
+
if (isNewEMG):
|
|
137
|
+
#new emg
|
|
138
|
+
data.packageIndexLength = 2
|
|
139
|
+
data.packageSampleCount = 8
|
|
140
|
+
data.resolutionBits = 0
|
|
141
|
+
data.K = 4000000.0 / 8388607.0
|
|
142
|
+
config.resolution = 8
|
|
143
|
+
else:
|
|
144
|
+
#old emg
|
|
145
|
+
data.packageIndexLength = 1
|
|
146
|
+
data.packageSampleCount = 8
|
|
147
|
+
data.resolutionBits = 8
|
|
148
|
+
gain = 1200
|
|
149
|
+
min_voltage = -1.25 * 1000000
|
|
150
|
+
max_voltage = 1.25 * 100000
|
|
151
|
+
div = 127.0
|
|
152
|
+
conversion_factor = (max_voltage - min_voltage) / gain / div
|
|
153
|
+
data.K = conversion_factor
|
|
154
|
+
config.resolution = 8
|
|
133
155
|
|
|
134
156
|
config.fs = SamplingRate.HZ_500
|
|
135
157
|
config.channel_mask = 255
|
|
136
|
-
config.resolution = 8
|
|
137
158
|
config.batch_len = 128
|
|
138
159
|
await self.gForce.set_emg_raw_data_config(config)
|
|
139
160
|
|
|
140
|
-
|
|
161
|
+
self.sensorDatas[SensorDataType.DATA_TYPE_EMG] = data
|
|
162
|
+
self.notifyDataFlag |= DataSubscription.EMG_RAW
|
|
163
|
+
|
|
141
164
|
return data.channelCount
|
|
142
165
|
|
|
143
166
|
async def initEEG(self, packageCount: int) -> int:
|
|
@@ -337,10 +360,10 @@ class SensorProfileDataCtx:
|
|
|
337
360
|
switch = 0
|
|
338
361
|
for filter in self.filter_map.keys():
|
|
339
362
|
value = self.filter_map[filter]
|
|
340
|
-
if filter == "
|
|
363
|
+
if filter == "FILTER_50HZ":
|
|
341
364
|
if value == "ON":
|
|
342
365
|
switch |= 1
|
|
343
|
-
elif filter == "
|
|
366
|
+
elif filter == "FILTER_60HZ":
|
|
344
367
|
if value == "ON":
|
|
345
368
|
switch |= 2
|
|
346
369
|
elif filter == "FILTER_HPF":
|
|
@@ -350,8 +373,9 @@ class SensorProfileDataCtx:
|
|
|
350
373
|
if value == "ON":
|
|
351
374
|
switch |= 8
|
|
352
375
|
try:
|
|
353
|
-
await self.gForce.set_firmware_filter_switch(switch)
|
|
354
|
-
|
|
376
|
+
ret = await self.gForce.set_firmware_filter_switch(switch)
|
|
377
|
+
if ret == None:
|
|
378
|
+
return "ERROR: not success"
|
|
355
379
|
return "OK"
|
|
356
380
|
except Exception as e:
|
|
357
381
|
return "ERROR: " + str(e)
|
|
@@ -372,10 +396,8 @@ class SensorProfileDataCtx:
|
|
|
372
396
|
####################################################################################
|
|
373
397
|
|
|
374
398
|
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():
|
|
399
|
+
while self._is_running:
|
|
400
|
+
while not self._rawDataBuffer.empty():
|
|
379
401
|
try:
|
|
380
402
|
data: bytes = self._rawDataBuffer.get_nowait()
|
|
381
403
|
except Exception as e:
|
|
@@ -385,7 +407,7 @@ class SensorProfileDataCtx:
|
|
|
385
407
|
self._processDataPackage(data, buf, sensor)
|
|
386
408
|
self._rawDataBuffer.task_done()
|
|
387
409
|
|
|
388
|
-
|
|
410
|
+
if self.isDataTransfering and not buf.empty():
|
|
389
411
|
sensorData: SensorData = None
|
|
390
412
|
try:
|
|
391
413
|
sensorData = buf.get_nowait()
|
|
@@ -398,6 +420,8 @@ class SensorProfileDataCtx:
|
|
|
398
420
|
print(e)
|
|
399
421
|
|
|
400
422
|
buf.task_done()
|
|
423
|
+
else:
|
|
424
|
+
await asyncio.sleep(0.01)
|
|
401
425
|
|
|
402
426
|
def _processDataPackage(self, data: bytes, buf: Queue[SensorData], sensor):
|
|
403
427
|
v = data[0] & 0x7F
|
|
@@ -423,9 +447,32 @@ class SensorProfileDataCtx:
|
|
|
423
447
|
|
|
424
448
|
self.impedanceData = impedanceData
|
|
425
449
|
self.saturationData = saturationData
|
|
450
|
+
elif v == DataType.NTF_IMPEDANCE_EXT:
|
|
451
|
+
offset = 1
|
|
452
|
+
# packageIndex = ((data[offset + 1] & 0xff) << 8) | (data[offset] & 0xff)
|
|
453
|
+
offset += 2
|
|
454
|
+
|
|
455
|
+
impedanceData = []
|
|
456
|
+
saturationData = []
|
|
457
|
+
|
|
458
|
+
dataCount = self._device_info.EegChannelCount + self._device_info.EcgChannelCount
|
|
459
|
+
|
|
460
|
+
for index in range(dataCount):
|
|
461
|
+
impedance = struct.unpack_from("<f", data, offset)[0]
|
|
462
|
+
offset += 4
|
|
463
|
+
impedanceData.append(impedance)
|
|
464
|
+
|
|
465
|
+
for index in range(dataCount):
|
|
466
|
+
saturation = struct.unpack_from("<H", data, offset)[0]
|
|
467
|
+
offset += 2
|
|
468
|
+
saturationData.append(saturation / 10) # firmware value range 0 - 1000
|
|
469
|
+
|
|
470
|
+
self.impedanceData = impedanceData
|
|
471
|
+
self.saturationData = saturationData
|
|
472
|
+
|
|
426
473
|
elif v == DataType.NTF_EMG:
|
|
427
474
|
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EMG]
|
|
428
|
-
if self.checkReadSamples(sensor, data, sensor_data,
|
|
475
|
+
if self.checkReadSamples(sensor, data, sensor_data, sensor_data.packageIndexLength + 1, 0):
|
|
429
476
|
self.sendSensorData(sensor_data, buf)
|
|
430
477
|
elif v == DataType.NTF_EEG:
|
|
431
478
|
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EEG]
|
|
@@ -454,16 +501,23 @@ class SensorProfileDataCtx:
|
|
|
454
501
|
if not self._is_data_transfering:
|
|
455
502
|
return False
|
|
456
503
|
try:
|
|
504
|
+
packageIndex = 0
|
|
505
|
+
maxPackageIndex = 0
|
|
506
|
+
if (sensorData.packageIndexLength == 2):
|
|
507
|
+
packageIndex = ((data[offset + 1] & 0xFF) << 8) | (data[offset] & 0xFF)
|
|
508
|
+
maxPackageIndex = 65535
|
|
509
|
+
else:
|
|
510
|
+
packageIndex = (data[offset] & 0xFF)
|
|
511
|
+
maxPackageIndex = 255
|
|
457
512
|
|
|
458
|
-
|
|
459
|
-
offset += 2
|
|
513
|
+
offset += sensorData.packageIndexLength
|
|
460
514
|
newPackageIndex = packageIndex
|
|
461
515
|
lastPackageIndex = sensorData.lastPackageIndex
|
|
462
516
|
if sensorData.lastPackageCounter == 0 and sensorData.lastPackageIndex == 0 and packageIndex > 1:
|
|
463
517
|
return False
|
|
464
518
|
|
|
465
519
|
if packageIndex < lastPackageIndex:
|
|
466
|
-
packageIndex +=
|
|
520
|
+
packageIndex += (maxPackageIndex + 1)
|
|
467
521
|
elif packageIndex == lastPackageIndex:
|
|
468
522
|
return False
|
|
469
523
|
|
|
@@ -488,12 +542,14 @@ class SensorProfileDataCtx:
|
|
|
488
542
|
self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
|
|
489
543
|
|
|
490
544
|
if newPackageIndex == 0:
|
|
491
|
-
sensorData.lastPackageIndex =
|
|
545
|
+
sensorData.lastPackageIndex = maxPackageIndex
|
|
492
546
|
else:
|
|
493
547
|
sensorData.lastPackageIndex = newPackageIndex - 1
|
|
494
548
|
sensorData.lastPackageCounter += deltaPackageIndex - 1
|
|
495
549
|
|
|
496
|
-
|
|
550
|
+
if (dataGap >= 0):
|
|
551
|
+
self.readSamples(data, sensorData, dataOffset, dataGap, 0)
|
|
552
|
+
|
|
497
553
|
sensorData.lastPackageIndex = newPackageIndex
|
|
498
554
|
sensorData.lastPackageCounter += 1
|
|
499
555
|
except Exception as e:
|
|
@@ -561,8 +617,15 @@ class SensorProfileDataCtx:
|
|
|
561
617
|
rawData = 0
|
|
562
618
|
if sensorData.resolutionBits == 8:
|
|
563
619
|
rawData = data[offset]
|
|
564
|
-
rawData -=
|
|
620
|
+
rawData -= 119
|
|
565
621
|
offset += 1
|
|
622
|
+
elif sensorData.resolutionBits == 12:
|
|
623
|
+
rawData = int.from_bytes(
|
|
624
|
+
data[offset : offset + 2],
|
|
625
|
+
byteorder="little",
|
|
626
|
+
signed=True,
|
|
627
|
+
)
|
|
628
|
+
offset += 2
|
|
566
629
|
elif sensorData.resolutionBits == 16:
|
|
567
630
|
rawData = int.from_bytes(
|
|
568
631
|
data[offset : offset + 2],
|
|
@@ -673,7 +736,21 @@ class SensorProfileDataCtx:
|
|
|
673
736
|
|
|
674
737
|
while self._is_running:
|
|
675
738
|
while self._is_running and self._rawDataBuffer.empty():
|
|
676
|
-
|
|
739
|
+
if self._is_running and self.isDataTransfering and not buf.empty():
|
|
740
|
+
sensorData: SensorData = None
|
|
741
|
+
try:
|
|
742
|
+
sensorData = buf.get_nowait()
|
|
743
|
+
except Exception as e:
|
|
744
|
+
break
|
|
745
|
+
if not sensor_utils._terminated and sensorData != None and callback != None:
|
|
746
|
+
try:
|
|
747
|
+
asyncio.get_event_loop().run_in_executor(self.dataPool, callback, sensor, sensorData)
|
|
748
|
+
except Exception as e:
|
|
749
|
+
print(e)
|
|
750
|
+
|
|
751
|
+
buf.task_done()
|
|
752
|
+
else:
|
|
753
|
+
await asyncio.sleep(0.01)
|
|
677
754
|
continue
|
|
678
755
|
|
|
679
756
|
try:
|
|
@@ -694,53 +771,39 @@ class SensorProfileDataCtx:
|
|
|
694
771
|
|
|
695
772
|
if self._concatDataBuffer[index] == 0x55:
|
|
696
773
|
if (index + 1) >= data_size:
|
|
697
|
-
index
|
|
774
|
+
index = data_size
|
|
698
775
|
continue
|
|
699
776
|
n = self._concatDataBuffer[index + 1]
|
|
700
|
-
if (index + 1 + n +
|
|
701
|
-
index
|
|
777
|
+
if (index + 1 + n + 2) >= data_size:
|
|
778
|
+
index = data_size
|
|
702
779
|
continue
|
|
703
|
-
|
|
704
|
-
calc_crc = sensor_utils.
|
|
705
|
-
if
|
|
780
|
+
crc16 = (self._concatDataBuffer[index + 1 + n + 2] << 8) | self._concatDataBuffer[index + 1 + n + 1]
|
|
781
|
+
calc_crc = sensor_utils.crc16_cal(self._concatDataBuffer[index + 2 : index + 2 + n], n)
|
|
782
|
+
if crc16 != calc_crc:
|
|
706
783
|
index += 1
|
|
707
784
|
continue
|
|
708
785
|
if self._is_data_transfering:
|
|
709
786
|
data_package = bytes(self._concatDataBuffer[index + 2 : index + 2 + n])
|
|
710
787
|
self._processDataPackage(data_package, buf, sensor)
|
|
711
|
-
|
|
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
|
-
last_cut = index = index + 2 + n
|
|
725
|
-
|
|
788
|
+
last_cut = index = index + 2 + n + 1
|
|
726
789
|
elif self._concatDataBuffer[index] == 0xAA:
|
|
727
790
|
if (index + 1) >= data_size:
|
|
728
|
-
index
|
|
791
|
+
index = data_size
|
|
729
792
|
continue
|
|
730
793
|
n = self._concatDataBuffer[index + 1]
|
|
731
|
-
if (index + 1 + n +
|
|
732
|
-
index
|
|
794
|
+
if (index + 1 + n + 2) >= data_size:
|
|
795
|
+
index = data_size
|
|
733
796
|
continue
|
|
734
|
-
|
|
735
|
-
calc_crc = sensor_utils.
|
|
736
|
-
if
|
|
797
|
+
crc16 = (self._concatDataBuffer[index + 1 + n + 2] << 8) | self._concatDataBuffer[index + 1 + n + 1]
|
|
798
|
+
calc_crc = sensor_utils.crc16_cal(self._concatDataBuffer[index + 2 : index + 2 + n], n)
|
|
799
|
+
if crc16 != calc_crc:
|
|
737
800
|
index += 1
|
|
738
801
|
continue
|
|
739
802
|
data_package = bytes(self._concatDataBuffer[index + 2 : index + 2 + n])
|
|
740
|
-
if not sensor_utils._terminated:
|
|
741
|
-
self.gForce._on_cmd_response(None, data_package)
|
|
742
|
-
last_cut = index = index + 2 + n
|
|
743
803
|
|
|
804
|
+
if not sensor_utils._terminated:
|
|
805
|
+
await sensor_utils.async_call(self.gForce.async_on_cmd_response(data_package), runloop=sensor._event_loop)
|
|
806
|
+
last_cut = index = index + 2 + n + 1
|
|
744
807
|
else:
|
|
745
808
|
index += 1
|
|
746
809
|
|