sensor-sdk 0.0.4__tar.gz → 0.0.5__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.
Potentially problematic release.
This version of sensor-sdk might be problematic. Click here for more details.
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/PKG-INFO +14 -2
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/README.md +13 -1
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor/gforce.py +73 -46
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor/sensor_controller.py +68 -18
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor/sensor_data_context.py +65 -27
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor/sensor_profile.py +145 -66
- sensor-sdk-0.0.5/sensor/utils.py +72 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor_sdk.egg-info/PKG-INFO +14 -2
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/setup.py +1 -1
- sensor-sdk-0.0.4/sensor/utils.py +0 -30
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/LICENSE.txt +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor/__init__.py +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor/sensor_data.py +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor/sensor_device.py +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor_sdk.egg-info/SOURCES.txt +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor_sdk.egg-info/dependency_links.txt +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor_sdk.egg-info/requires.txt +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor_sdk.egg-info/top_level.txt +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/sensor_sdk.egg-info/zip-safe +0 -0
- {sensor-sdk-0.0.4 → sensor-sdk-0.0.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sensor-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Python sdk for Synchroni
|
|
5
5
|
Home-page: https://github.com/oymotion/SynchroniSDKPython
|
|
6
6
|
Author: Martin Ye
|
|
@@ -68,6 +68,12 @@ success = SensorControllerInstance.startScan(6000)
|
|
|
68
68
|
|
|
69
69
|
returns true if start scan success, periodInMS means onDeviceCallback will be called every periodInMS
|
|
70
70
|
|
|
71
|
+
Use `def scan(period_in_ms: int) -> list[BLEDevice]` to scan once time
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
bleDevices = SensorControllerInstance.scan(6000)
|
|
75
|
+
```
|
|
76
|
+
|
|
71
77
|
### 3. Stop scan
|
|
72
78
|
|
|
73
79
|
Use `def stopScan() -> None` to stop scan
|
|
@@ -122,7 +128,7 @@ sensorProfiles = SensorControllerInstance.getConnectedSensors()
|
|
|
122
128
|
|
|
123
129
|
### 9. Get Connected BLE Devices
|
|
124
130
|
|
|
125
|
-
Use `def getConnectedDevices() -> list[
|
|
131
|
+
Use `def getConnectedDevices() -> list[BLEDevice]` to get connected BLE Devices.
|
|
126
132
|
|
|
127
133
|
```python
|
|
128
134
|
bleDevices = SensorControllerInstance.getConnectedDevices()
|
|
@@ -340,3 +346,9 @@ batteryPower = sensorProfile.getBatteryLevel()
|
|
|
340
346
|
```
|
|
341
347
|
|
|
342
348
|
Please check console.py in examples directory
|
|
349
|
+
|
|
350
|
+
## Async methods
|
|
351
|
+
|
|
352
|
+
all methods start with async is async methods, they has same params and return result as sync methods.
|
|
353
|
+
|
|
354
|
+
Please check async_console.py in examples directory
|
|
@@ -57,6 +57,12 @@ success = SensorControllerInstance.startScan(6000)
|
|
|
57
57
|
|
|
58
58
|
returns true if start scan success, periodInMS means onDeviceCallback will be called every periodInMS
|
|
59
59
|
|
|
60
|
+
Use `def scan(period_in_ms: int) -> list[BLEDevice]` to scan once time
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
bleDevices = SensorControllerInstance.scan(6000)
|
|
64
|
+
```
|
|
65
|
+
|
|
60
66
|
### 3. Stop scan
|
|
61
67
|
|
|
62
68
|
Use `def stopScan() -> None` to stop scan
|
|
@@ -111,7 +117,7 @@ sensorProfiles = SensorControllerInstance.getConnectedSensors()
|
|
|
111
117
|
|
|
112
118
|
### 9. Get Connected BLE Devices
|
|
113
119
|
|
|
114
|
-
Use `def getConnectedDevices() -> list[
|
|
120
|
+
Use `def getConnectedDevices() -> list[BLEDevice]` to get connected BLE Devices.
|
|
115
121
|
|
|
116
122
|
```python
|
|
117
123
|
bleDevices = SensorControllerInstance.getConnectedDevices()
|
|
@@ -329,3 +335,9 @@ batteryPower = sensorProfile.getBatteryLevel()
|
|
|
329
335
|
```
|
|
330
336
|
|
|
331
337
|
Please check console.py in examples directory
|
|
338
|
+
|
|
339
|
+
## Async methods
|
|
340
|
+
|
|
341
|
+
all methods start with async is async methods, they has same params and return result as sync methods.
|
|
342
|
+
|
|
343
|
+
Please check async_console.py in examples directory
|
|
@@ -16,6 +16,8 @@ from bleak import (
|
|
|
16
16
|
BleakGATTCharacteristic,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
from sensor import utils
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
@dataclass
|
|
21
23
|
class Characteristic:
|
|
@@ -146,15 +148,15 @@ class DataSubscription(IntEnum):
|
|
|
146
148
|
LOG = (0x00000800,)
|
|
147
149
|
|
|
148
150
|
DNF_EEG = (0x00010000,)
|
|
149
|
-
|
|
151
|
+
|
|
150
152
|
DNF_ECG = (0x00020000,)
|
|
151
|
-
|
|
153
|
+
|
|
152
154
|
DNF_IMPEDANCE = (0x00040000,)
|
|
153
|
-
|
|
155
|
+
|
|
154
156
|
DNF_IMU = (0x00080000,)
|
|
155
|
-
|
|
157
|
+
|
|
156
158
|
DNF_ADS = (0x00100000,)
|
|
157
|
-
|
|
159
|
+
|
|
158
160
|
DNF_BRTH = (0x00200000,)
|
|
159
161
|
|
|
160
162
|
DNF_CONCAT_BLE = (0x80000000,)
|
|
@@ -181,8 +183,8 @@ class DataType(IntEnum):
|
|
|
181
183
|
|
|
182
184
|
class SampleResolution(IntEnum):
|
|
183
185
|
BITS_8 = (8,)
|
|
184
|
-
BITS_12 = 12,
|
|
185
|
-
BITS_16 = 16,
|
|
186
|
+
BITS_12 = (12,)
|
|
187
|
+
BITS_16 = (16,)
|
|
186
188
|
BITS_24 = 24
|
|
187
189
|
|
|
188
190
|
|
|
@@ -215,6 +217,7 @@ class EmgRawDataConfig:
|
|
|
215
217
|
)
|
|
216
218
|
return cls(fs, channel_mask, batch_len, resolution)
|
|
217
219
|
|
|
220
|
+
|
|
218
221
|
@dataclass
|
|
219
222
|
class EegRawDataConfig:
|
|
220
223
|
fs: SamplingRate = 0
|
|
@@ -240,6 +243,7 @@ class EegRawDataConfig:
|
|
|
240
243
|
)
|
|
241
244
|
return cls(fs, channel_mask, batch_len, resolution, K)
|
|
242
245
|
|
|
246
|
+
|
|
243
247
|
@dataclass
|
|
244
248
|
class EegRawDataCap:
|
|
245
249
|
fs: SamplingRate = 0
|
|
@@ -262,7 +266,8 @@ class EegRawDataCap:
|
|
|
262
266
|
data,
|
|
263
267
|
)
|
|
264
268
|
return cls(fs, channel_count, batch_len, resolution)
|
|
265
|
-
|
|
269
|
+
|
|
270
|
+
|
|
266
271
|
@dataclass
|
|
267
272
|
class EcgRawDataConfig:
|
|
268
273
|
fs: SamplingRate = SamplingRate.HZ_250
|
|
@@ -287,7 +292,8 @@ class EcgRawDataConfig:
|
|
|
287
292
|
data,
|
|
288
293
|
)
|
|
289
294
|
return cls(fs, channel_mask, batch_len, resolution, K)
|
|
290
|
-
|
|
295
|
+
|
|
296
|
+
|
|
291
297
|
@dataclass
|
|
292
298
|
class ImuRawDataConfig:
|
|
293
299
|
channel_count: int = 0
|
|
@@ -295,6 +301,7 @@ class ImuRawDataConfig:
|
|
|
295
301
|
batch_len: int = 0
|
|
296
302
|
accK: float = 0
|
|
297
303
|
gyroK: float = 0
|
|
304
|
+
|
|
298
305
|
def to_bytes(self) -> bytes:
|
|
299
306
|
body = b""
|
|
300
307
|
body += struct.pack("<i", self.channel_count)
|
|
@@ -311,7 +318,8 @@ class ImuRawDataConfig:
|
|
|
311
318
|
data,
|
|
312
319
|
)
|
|
313
320
|
return cls(channel_count, fs, batch_len, accK, gyroK)
|
|
314
|
-
|
|
321
|
+
|
|
322
|
+
|
|
315
323
|
@dataclass
|
|
316
324
|
class BrthRawDataConfig:
|
|
317
325
|
fs: SamplingRate = 0
|
|
@@ -336,7 +344,8 @@ class BrthRawDataConfig:
|
|
|
336
344
|
data,
|
|
337
345
|
)
|
|
338
346
|
return cls(fs, channel_mask, batch_len, resolution, K)
|
|
339
|
-
|
|
347
|
+
|
|
348
|
+
|
|
340
349
|
@dataclass
|
|
341
350
|
class Request:
|
|
342
351
|
cmd: Command
|
|
@@ -361,7 +370,9 @@ class Response:
|
|
|
361
370
|
|
|
362
371
|
|
|
363
372
|
class GForce:
|
|
364
|
-
def __init__(
|
|
373
|
+
def __init__(
|
|
374
|
+
self, device: BLEDevice, cmd_char: str, data_char: str, isUniversalStream: bool
|
|
375
|
+
):
|
|
365
376
|
self.device_name = ""
|
|
366
377
|
self.client = None
|
|
367
378
|
self.cmd_char = cmd_char
|
|
@@ -371,21 +382,33 @@ class GForce:
|
|
|
371
382
|
self._num_channels = 8
|
|
372
383
|
self._device = device
|
|
373
384
|
self._is_universal_stream = isUniversalStream
|
|
374
|
-
self._raw_data_buf:queue.Queue[bytes] = None
|
|
385
|
+
self._raw_data_buf: queue.Queue[bytes] = None
|
|
375
386
|
self.packet_id = 0
|
|
376
387
|
self.data_packet = []
|
|
377
388
|
|
|
378
389
|
async def connect(self, disconnect_cb, buf: queue.Queue[bytes]):
|
|
379
390
|
client = BleakClient(self._device, disconnected_callback=disconnect_cb)
|
|
380
|
-
await client.connect()
|
|
381
|
-
|
|
382
391
|
self.client = client
|
|
383
392
|
self.device_name = self._device.name
|
|
384
393
|
self._raw_data_buf = buf
|
|
385
|
-
|
|
386
|
-
|
|
394
|
+
|
|
395
|
+
try:
|
|
396
|
+
await asyncio.wait_for(client.connect(), utils._TIMEOUT)
|
|
397
|
+
except Exception as e:
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
if not client.is_connected:
|
|
401
|
+
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
|
+
)
|
|
387
407
|
else:
|
|
388
|
-
await
|
|
408
|
+
await asyncio.wait_for(
|
|
409
|
+
client.start_notify(self.data_char, self._on_universal_response),
|
|
410
|
+
utils._TIMEOUT,
|
|
411
|
+
)
|
|
389
412
|
|
|
390
413
|
def _on_data_response(self, q: Queue[bytes], bs: bytearray):
|
|
391
414
|
bs = bytes(bs)
|
|
@@ -537,6 +560,7 @@ class GForce:
|
|
|
537
560
|
num_channels = 6
|
|
538
561
|
|
|
539
562
|
return emg_gesture_data.reshape(-1, num_channels)
|
|
563
|
+
|
|
540
564
|
def _on_universal_response(self, _: BleakGATTCharacteristic, bs: bytearray):
|
|
541
565
|
self._raw_data_buf.put_nowait(bytes(bs))
|
|
542
566
|
|
|
@@ -675,7 +699,6 @@ class GForce:
|
|
|
675
699
|
)
|
|
676
700
|
)
|
|
677
701
|
|
|
678
|
-
|
|
679
702
|
async def system_reset(self):
|
|
680
703
|
await self._send_request(
|
|
681
704
|
Request(
|
|
@@ -684,11 +707,8 @@ class GForce:
|
|
|
684
707
|
)
|
|
685
708
|
)
|
|
686
709
|
|
|
687
|
-
|
|
688
710
|
async def set_motor(self, switchStatus):
|
|
689
|
-
body = [
|
|
690
|
-
switchStatus == True
|
|
691
|
-
]
|
|
711
|
+
body = [switchStatus == True]
|
|
692
712
|
body = bytes(body)
|
|
693
713
|
ret = await self._send_request(
|
|
694
714
|
Request(
|
|
@@ -699,9 +719,7 @@ class GForce:
|
|
|
699
719
|
)
|
|
700
720
|
|
|
701
721
|
async def set_led(self, switchStatus):
|
|
702
|
-
body = [
|
|
703
|
-
switchStatus == True
|
|
704
|
-
]
|
|
722
|
+
body = [switchStatus == True]
|
|
705
723
|
body = bytes(body)
|
|
706
724
|
ret = await self._send_request(
|
|
707
725
|
Request(
|
|
@@ -712,9 +730,7 @@ class GForce:
|
|
|
712
730
|
)
|
|
713
731
|
|
|
714
732
|
async def set_log_level(self, logLevel):
|
|
715
|
-
body = [
|
|
716
|
-
0xFF & logLevel
|
|
717
|
-
]
|
|
733
|
+
body = [0xFF & logLevel]
|
|
718
734
|
body = bytes(body)
|
|
719
735
|
ret = await self._send_request(
|
|
720
736
|
Request(
|
|
@@ -724,7 +740,6 @@ class GForce:
|
|
|
724
740
|
)
|
|
725
741
|
)
|
|
726
742
|
|
|
727
|
-
|
|
728
743
|
async def set_emg_raw_data_config(self, cfg=EmgRawDataConfig()):
|
|
729
744
|
body = cfg.to_bytes()
|
|
730
745
|
await self._send_request(
|
|
@@ -757,7 +772,7 @@ class GForce:
|
|
|
757
772
|
)
|
|
758
773
|
)
|
|
759
774
|
return EmgRawDataConfig.from_bytes(buf)
|
|
760
|
-
|
|
775
|
+
|
|
761
776
|
async def get_eeg_raw_data_config(self) -> EegRawDataConfig:
|
|
762
777
|
buf = await self._send_request(
|
|
763
778
|
Request(
|
|
@@ -766,7 +781,7 @@ class GForce:
|
|
|
766
781
|
)
|
|
767
782
|
)
|
|
768
783
|
return EegRawDataConfig.from_bytes(buf)
|
|
769
|
-
|
|
784
|
+
|
|
770
785
|
async def get_eeg_raw_data_cap(self) -> EegRawDataCap:
|
|
771
786
|
buf = await self._send_request(
|
|
772
787
|
Request(
|
|
@@ -784,7 +799,7 @@ class GForce:
|
|
|
784
799
|
)
|
|
785
800
|
)
|
|
786
801
|
return EcgRawDataConfig.from_bytes(buf)
|
|
787
|
-
|
|
802
|
+
|
|
788
803
|
async def get_imu_raw_data_config(self) -> ImuRawDataConfig:
|
|
789
804
|
buf = await self._send_request(
|
|
790
805
|
Request(
|
|
@@ -792,8 +807,8 @@ class GForce:
|
|
|
792
807
|
has_res=True,
|
|
793
808
|
)
|
|
794
809
|
)
|
|
795
|
-
return ImuRawDataConfig.from_bytes(buf)
|
|
796
|
-
|
|
810
|
+
return ImuRawDataConfig.from_bytes(buf)
|
|
811
|
+
|
|
797
812
|
async def get_brth_raw_data_config(self) -> BrthRawDataConfig:
|
|
798
813
|
buf = await self._send_request(
|
|
799
814
|
Request(
|
|
@@ -801,8 +816,8 @@ class GForce:
|
|
|
801
816
|
has_res=True,
|
|
802
817
|
)
|
|
803
818
|
)
|
|
804
|
-
return BrthRawDataConfig.from_bytes(buf)
|
|
805
|
-
|
|
819
|
+
return BrthRawDataConfig.from_bytes(buf)
|
|
820
|
+
|
|
806
821
|
async def set_subscription(self, subscription: DataSubscription):
|
|
807
822
|
body = [
|
|
808
823
|
0xFF & subscription,
|
|
@@ -819,20 +834,27 @@ class GForce:
|
|
|
819
834
|
)
|
|
820
835
|
)
|
|
821
836
|
|
|
822
|
-
async def start_streaming(self, q:queue.Queue):
|
|
823
|
-
await
|
|
824
|
-
self.
|
|
825
|
-
|
|
837
|
+
async def start_streaming(self, q: queue.Queue):
|
|
838
|
+
await asyncio.wait_for(
|
|
839
|
+
self.client.start_notify(
|
|
840
|
+
self.data_char,
|
|
841
|
+
lambda _, data: self._on_data_response(q, data),
|
|
842
|
+
),
|
|
843
|
+
utils._TIMEOUT,
|
|
826
844
|
)
|
|
827
845
|
|
|
828
846
|
async def stop_streaming(self):
|
|
829
847
|
exceptions = []
|
|
830
848
|
try:
|
|
831
|
-
await
|
|
849
|
+
await asyncio.wait_for(
|
|
850
|
+
self.set_subscription(DataSubscription.OFF), utils._TIMEOUT
|
|
851
|
+
)
|
|
832
852
|
except Exception as e:
|
|
833
853
|
exceptions.append(e)
|
|
834
854
|
try:
|
|
835
|
-
await
|
|
855
|
+
await asyncio.wait_for(
|
|
856
|
+
self.client.stop_notify(self.data_char), utils._TIMEOUT
|
|
857
|
+
)
|
|
836
858
|
except Exception as e:
|
|
837
859
|
exceptions.append(e)
|
|
838
860
|
|
|
@@ -841,7 +863,10 @@ class GForce:
|
|
|
841
863
|
|
|
842
864
|
async def disconnect(self):
|
|
843
865
|
with suppress(asyncio.CancelledError):
|
|
844
|
-
|
|
866
|
+
try:
|
|
867
|
+
await asyncio.wait_for(self.client.disconnect(), utils._TIMEOUT)
|
|
868
|
+
except Exception as e:
|
|
869
|
+
pass
|
|
845
870
|
|
|
846
871
|
def _get_response_channel(self, cmd: Command) -> Queue:
|
|
847
872
|
q = Queue()
|
|
@@ -856,9 +881,11 @@ class GForce:
|
|
|
856
881
|
bs = bytes([req.cmd])
|
|
857
882
|
if req.body is not None:
|
|
858
883
|
bs += req.body
|
|
859
|
-
await
|
|
884
|
+
await asyncio.wait_for(
|
|
885
|
+
self.client.write_gatt_char(self.cmd_char, bs), utils._TIMEOUT
|
|
886
|
+
)
|
|
860
887
|
|
|
861
888
|
if not req.has_res:
|
|
862
889
|
return None
|
|
863
890
|
|
|
864
|
-
return await asyncio.wait_for(q.get(),
|
|
891
|
+
return await asyncio.wait_for(q.get(), utils._TIMEOUT)
|
|
@@ -5,10 +5,11 @@ from typing import Callable, Dict, List, Optional, Tuple
|
|
|
5
5
|
import bleak
|
|
6
6
|
|
|
7
7
|
from sensor import sensor_profile
|
|
8
|
+
from sensor import utils
|
|
8
9
|
from sensor.sensor_profile import DeviceStateEx, SensorProfile
|
|
9
10
|
import asyncio
|
|
10
11
|
|
|
11
|
-
from sensor.utils import start_loop, sync_timer, timer
|
|
12
|
+
from sensor.utils import async_timer, start_loop, sync_timer, timer
|
|
12
13
|
from bleak import (
|
|
13
14
|
BleakScanner,
|
|
14
15
|
AdvertisementData,
|
|
@@ -69,6 +70,7 @@ class SensorController:
|
|
|
69
70
|
self._gforce_event_loop.close()
|
|
70
71
|
|
|
71
72
|
def terminate(self):
|
|
73
|
+
utils._terminated = True
|
|
72
74
|
for sensor in self._sensor_profiles.values():
|
|
73
75
|
if (
|
|
74
76
|
sensor.deviceState == DeviceStateEx.Connected
|
|
@@ -133,32 +135,80 @@ class SensorController:
|
|
|
133
135
|
"""
|
|
134
136
|
self._device_callback = callback
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
def _process_ble_devices(
|
|
137
139
|
self, found_devices: Dict[str, Tuple[bleak.BLEDevice, AdvertisementData]]
|
|
138
|
-
):
|
|
140
|
+
) -> List[sensor_profile.BLEDevice]:
|
|
141
|
+
devices: List[sensor_profile.BLEDevice] = list()
|
|
142
|
+
deviceMap: Dict[str, SensorProfile] = self._sensor_profiles.copy()
|
|
143
|
+
for uuid in found_devices:
|
|
144
|
+
device = found_devices[uuid][0]
|
|
145
|
+
if device.name == None:
|
|
146
|
+
continue
|
|
147
|
+
adv = found_devices[uuid][1]
|
|
148
|
+
if SERVICE_GUID in adv.service_uuids:
|
|
149
|
+
mac = None
|
|
150
|
+
if adv.service_data.get(SERVICE_GUID) != None:
|
|
151
|
+
bytes_val = adv.service_data[SERVICE_GUID]
|
|
152
|
+
mac = ":".join(f"{byte:02X}" for byte in bytes_val)
|
|
153
|
+
elif adv.service_data.get(RFSTAR_SERVICE_GUID) != None:
|
|
154
|
+
bytes_val = adv.service_data[RFSTAR_SERVICE_GUID]
|
|
155
|
+
mac = ":".join(f"{byte:02X}" for byte in reversed(bytes_val))
|
|
156
|
+
|
|
157
|
+
if mac == None:
|
|
158
|
+
continue
|
|
159
|
+
if deviceMap.get(mac) != None:
|
|
160
|
+
devices.append(self._sensor_profiles[mac].BLEDevice)
|
|
161
|
+
else:
|
|
162
|
+
newSensor = SensorProfile(device, adv, mac, self._gforce_event_loop)
|
|
163
|
+
deviceMap[mac] = newSensor
|
|
164
|
+
devices.append(newSensor.BLEDevice)
|
|
165
|
+
|
|
166
|
+
self._sensor_profiles = deviceMap
|
|
167
|
+
return devices
|
|
168
|
+
|
|
169
|
+
async def _async_scan(self, period):
|
|
170
|
+
self._is_scanning = True
|
|
171
|
+
found_devices = await self._scanner.discover(
|
|
172
|
+
timeout=period / 1000, return_adv=True
|
|
173
|
+
)
|
|
174
|
+
self._is_scanning = False
|
|
175
|
+
return self._process_ble_devices(found_devices)
|
|
176
|
+
|
|
177
|
+
def scan(self, period) -> List[sensor_profile.BLEDevice]:
|
|
178
|
+
"""
|
|
179
|
+
扫描一段时间后返回BLEDevice列表。
|
|
180
|
+
|
|
181
|
+
:param periodInMs (int): 扫描时长(毫秒)
|
|
182
|
+
|
|
183
|
+
:return: List[sensor_profile.BLEDevice]: BLEDevice列表
|
|
184
|
+
"""
|
|
185
|
+
return sync_timer(self._gforce_event_loop, 0, self._async_scan(period))
|
|
186
|
+
|
|
187
|
+
async def asyncScan(self, period) -> List[sensor_profile.BLEDevice]:
|
|
188
|
+
"""
|
|
189
|
+
扫描一段时间后返回BLEDevice列表。
|
|
190
|
+
|
|
191
|
+
:param periodInMs (int): 扫描时长(毫秒)
|
|
192
|
+
|
|
193
|
+
:return: List[sensor_profile.BLEDevice]: BLEDevice列表
|
|
194
|
+
"""
|
|
195
|
+
return await async_timer(self._gforce_event_loop, 0, self._async_scan(period))
|
|
196
|
+
|
|
197
|
+
async def _device_scan_callback(self, devices: List[sensor_profile.BLEDevice]):
|
|
139
198
|
if self._device_callback:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
adv = found_devices[mac][1]
|
|
145
|
-
if SERVICE_GUID in adv.service_uuids:
|
|
146
|
-
if deviceMap.get(mac) != None:
|
|
147
|
-
devices.append(self._sensor_profiles[mac].BLEDevice)
|
|
148
|
-
else:
|
|
149
|
-
newSensor = SensorProfile(device, adv, self._gforce_event_loop)
|
|
150
|
-
deviceMap[mac] = newSensor
|
|
151
|
-
devices.append(newSensor.BLEDevice)
|
|
152
|
-
self._sensor_profiles = deviceMap
|
|
153
|
-
self._device_callback(devices)
|
|
199
|
+
try:
|
|
200
|
+
self._device_callback(devices)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
print(e)
|
|
154
203
|
|
|
155
204
|
if self._is_scanning:
|
|
156
205
|
timer(self._gforce_event_loop, 0, self._startScan())
|
|
157
206
|
|
|
158
207
|
async def _startScan(self) -> bool:
|
|
159
|
-
|
|
208
|
+
found_devices = await self._scanner.discover(
|
|
160
209
|
timeout=self._device_callback_period / 1000, return_adv=True
|
|
161
210
|
)
|
|
211
|
+
devices = self._process_ble_devices(found_devices)
|
|
162
212
|
timer(self._event_loop, 0, self._device_scan_callback(devices))
|
|
163
213
|
|
|
164
214
|
def startScan(self, periodInMs: int) -> bool:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections import deque
|
|
3
|
+
import platform
|
|
3
4
|
from queue import Queue
|
|
5
|
+
import struct
|
|
4
6
|
from typing import Deque, List
|
|
5
7
|
|
|
6
8
|
from sensor.gforce import DataSubscription, GForce
|
|
@@ -42,6 +44,7 @@ class SensorProfileDataCtx:
|
|
|
42
44
|
self.deviceMac = deviceMac
|
|
43
45
|
self._device_info: DeviceInfo = None
|
|
44
46
|
|
|
47
|
+
self._is_initing = False
|
|
45
48
|
self._is_running = True
|
|
46
49
|
self._is_data_transfering = False
|
|
47
50
|
self.isUniversalStream: bool = gForce._is_universal_stream
|
|
@@ -79,7 +82,9 @@ class SensorProfileDataCtx:
|
|
|
79
82
|
return self._is_data_transfering
|
|
80
83
|
|
|
81
84
|
def hasInit(self):
|
|
82
|
-
return
|
|
85
|
+
return (
|
|
86
|
+
not self._is_initing and self.featureMap != 0 and self.notifyDataFlag != 0
|
|
87
|
+
)
|
|
83
88
|
|
|
84
89
|
def hasEMG(self):
|
|
85
90
|
return (self.featureMap & FeatureMaps.GFD_FEAT_EMG.value) != 0
|
|
@@ -198,32 +203,44 @@ class SensorProfileDataCtx:
|
|
|
198
203
|
|
|
199
204
|
async def fetchDeviceInfo(self) -> DeviceInfo:
|
|
200
205
|
info = DeviceInfo()
|
|
201
|
-
|
|
206
|
+
if platform.system() != "Linux":
|
|
207
|
+
info.MTUSize = self.gForce.client.mtu_size
|
|
208
|
+
else:
|
|
209
|
+
info.MTUSize = 0
|
|
210
|
+
print("get_device_name")
|
|
202
211
|
info.DeviceName = await self.gForce.get_device_name()
|
|
212
|
+
print("get_model_number")
|
|
203
213
|
info.ModelName = await self.gForce.get_model_number()
|
|
214
|
+
print("get_hardware_revision")
|
|
204
215
|
info.HardwareVersion = await self.gForce.get_hardware_revision()
|
|
216
|
+
print("get_firmware_revision")
|
|
205
217
|
info.FirmwareVersion = await self.gForce.get_firmware_revision()
|
|
206
218
|
return info
|
|
207
219
|
|
|
208
220
|
async def init(self, packageCount: int) -> bool:
|
|
221
|
+
if self._is_initing:
|
|
222
|
+
return False
|
|
209
223
|
try:
|
|
224
|
+
self._is_initing = True
|
|
210
225
|
info = await self.fetchDeviceInfo()
|
|
211
|
-
|
|
212
226
|
await self.initDataTransfer(True)
|
|
213
|
-
|
|
214
227
|
if self.hasImpedance():
|
|
215
228
|
self.notifyDataFlag |= DataSubscription.DNF_IMPEDANCE
|
|
216
229
|
|
|
217
230
|
if self.hasEEG():
|
|
231
|
+
print("initEEG")
|
|
218
232
|
info.EegChannelCount = await self.initEEG(packageCount)
|
|
219
233
|
|
|
220
234
|
if self.hasECG():
|
|
235
|
+
print("initECG")
|
|
221
236
|
info.EcgChannelCount = await self.initECG(packageCount)
|
|
222
237
|
|
|
223
238
|
if self.hasBrth():
|
|
239
|
+
print("initBrth")
|
|
224
240
|
info.BrthChannelCount = await self.initBrth(packageCount)
|
|
225
241
|
|
|
226
242
|
if self.hasIMU():
|
|
243
|
+
print("initIMU")
|
|
227
244
|
imuChannelCount = await self.initIMU(packageCount)
|
|
228
245
|
info.AccChannelCount = imuChannelCount
|
|
229
246
|
info.GyroChannelCount = imuChannelCount
|
|
@@ -233,9 +250,11 @@ class SensorProfileDataCtx:
|
|
|
233
250
|
if not self.isUniversalStream:
|
|
234
251
|
await self.initDataTransfer(False)
|
|
235
252
|
|
|
253
|
+
self._is_initing = False
|
|
236
254
|
return True
|
|
237
255
|
except Exception as e:
|
|
238
256
|
print(e)
|
|
257
|
+
self._is_initing = False
|
|
239
258
|
return False
|
|
240
259
|
|
|
241
260
|
async def start_streaming(self) -> bool:
|
|
@@ -263,16 +282,16 @@ class SensorProfileDataCtx:
|
|
|
263
282
|
await self.gForce.set_subscription(0)
|
|
264
283
|
return True
|
|
265
284
|
|
|
266
|
-
def process_data(self, buf: Queue[SensorData]):
|
|
285
|
+
def process_data(self, buf: Queue[SensorData], sensor):
|
|
267
286
|
try:
|
|
268
287
|
data: bytes = self._rawDataBuffer.get_nowait()
|
|
269
288
|
except Exception as e:
|
|
270
289
|
return
|
|
271
290
|
|
|
272
|
-
self._processDataPackage(data, buf)
|
|
291
|
+
self._processDataPackage(data, buf, sensor)
|
|
273
292
|
self._rawDataBuffer.task_done()
|
|
274
293
|
|
|
275
|
-
def _processDataPackage(self, data: bytes, buf: Queue[SensorData]):
|
|
294
|
+
def _processDataPackage(self, data: bytes, buf: Queue[SensorData], sensor):
|
|
276
295
|
v = data[0]
|
|
277
296
|
if v == DataType.NTF_IMPEDANCE:
|
|
278
297
|
offset = 1
|
|
@@ -283,15 +302,14 @@ class SensorProfileDataCtx:
|
|
|
283
302
|
saturationData = []
|
|
284
303
|
|
|
285
304
|
dataCount = (len(data) - 3) // 4 // 2
|
|
305
|
+
|
|
286
306
|
for index in range(dataCount):
|
|
287
|
-
|
|
288
|
-
impedance = int.from_bytes(impedance_bytes, byteorder="little")
|
|
307
|
+
impedance = struct.unpack_from("<f", data, offset)[0]
|
|
289
308
|
offset += 4
|
|
290
309
|
impedanceData.append(impedance)
|
|
291
310
|
|
|
292
311
|
for index in range(dataCount):
|
|
293
|
-
|
|
294
|
-
saturation = int.from_bytes(saturation_bytes, byteorder="little")
|
|
312
|
+
saturation = struct.unpack_from("<f", data, offset)[0]
|
|
295
313
|
offset += 4
|
|
296
314
|
saturationData.append(saturation / 10) # firmware value range 0 - 1000
|
|
297
315
|
|
|
@@ -300,27 +318,27 @@ class SensorProfileDataCtx:
|
|
|
300
318
|
|
|
301
319
|
elif v == DataType.NTF_EEG:
|
|
302
320
|
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EEG]
|
|
303
|
-
if self.checkReadSamples(data, sensor_data, 3, 0):
|
|
321
|
+
if self.checkReadSamples(sensor, data, sensor_data, 3, 0):
|
|
304
322
|
self.sendSensorData(sensor_data, buf)
|
|
305
323
|
elif v == DataType.NTF_ECG:
|
|
306
324
|
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_ECG]
|
|
307
|
-
if self.checkReadSamples(data, sensor_data, 3, 0):
|
|
325
|
+
if self.checkReadSamples(sensor, data, sensor_data, 3, 0):
|
|
308
326
|
self.sendSensorData(sensor_data, buf)
|
|
309
327
|
elif v == DataType.NTF_BRTH:
|
|
310
328
|
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_BRTH]
|
|
311
|
-
if self.checkReadSamples(data, sensor_data, 3, 0):
|
|
329
|
+
if self.checkReadSamples(sensor, data, sensor_data, 3, 0):
|
|
312
330
|
self.sendSensorData(sensor_data, buf)
|
|
313
331
|
elif v == DataType.NTF_IMU:
|
|
314
332
|
sensor_data_acc = self.sensorDatas[SensorDataType.DATA_TYPE_ACC]
|
|
315
|
-
if self.checkReadSamples(data, sensor_data_acc, 3, 6):
|
|
333
|
+
if self.checkReadSamples(sensor, data, sensor_data_acc, 3, 6):
|
|
316
334
|
self.sendSensorData(sensor_data_acc, buf)
|
|
317
335
|
|
|
318
336
|
sensor_data_gyro = self.sensorDatas[SensorDataType.DATA_TYPE_GYRO]
|
|
319
|
-
if self.checkReadSamples(data, sensor_data_gyro, 9, 6):
|
|
337
|
+
if self.checkReadSamples(sensor, data, sensor_data_gyro, 9, 6):
|
|
320
338
|
self.sendSensorData(sensor_data_gyro, buf)
|
|
321
339
|
|
|
322
340
|
def checkReadSamples(
|
|
323
|
-
self, data: bytes, sensorData: SensorData, dataOffset: int, dataGap: int
|
|
341
|
+
self, sensor, data: bytes, sensorData: SensorData, dataOffset: int, dataGap: int
|
|
324
342
|
):
|
|
325
343
|
offset = 1
|
|
326
344
|
v = data[0]
|
|
@@ -343,9 +361,23 @@ class SensorProfileDataCtx:
|
|
|
343
361
|
lostSampleCount = sensorData.packageSampleCount * (
|
|
344
362
|
deltaPackageIndex - 1
|
|
345
363
|
)
|
|
346
|
-
|
|
347
|
-
|
|
364
|
+
lostLog = (
|
|
365
|
+
"MSG|LOST SAMPLE|MAC|"
|
|
366
|
+
+ str(sensorData.deviceMac)
|
|
367
|
+
+ "|TYPE|"
|
|
368
|
+
+ str(sensorData.dataType)
|
|
369
|
+
+ "|COUNT|"
|
|
370
|
+
+ str(lostSampleCount)
|
|
348
371
|
)
|
|
372
|
+
# print(lostLog)
|
|
373
|
+
if sensor._event_loop != None and sensor._on_error_callback != None:
|
|
374
|
+
try:
|
|
375
|
+
sensor._event_loop.call_soon_threadsafe(
|
|
376
|
+
sensor._on_error_callback, sensor, lostLog
|
|
377
|
+
)
|
|
378
|
+
except Exception as e:
|
|
379
|
+
pass
|
|
380
|
+
|
|
349
381
|
self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
|
|
350
382
|
if newPackageIndex == 0:
|
|
351
383
|
sensorData.lastPackageIndex = 65535
|
|
@@ -356,7 +388,8 @@ class SensorProfileDataCtx:
|
|
|
356
388
|
self.readSamples(data, sensorData, dataOffset, dataGap, 0)
|
|
357
389
|
sensorData.lastPackageIndex = newPackageIndex
|
|
358
390
|
sensorData.lastPackageCounter += 1
|
|
359
|
-
except Exception:
|
|
391
|
+
except Exception as e:
|
|
392
|
+
print(e)
|
|
360
393
|
return False
|
|
361
394
|
return True
|
|
362
395
|
|
|
@@ -392,12 +425,16 @@ class SensorProfileDataCtx:
|
|
|
392
425
|
samples = channelSamples[channelIndex]
|
|
393
426
|
impedance = 0.0
|
|
394
427
|
saturation = 0.0
|
|
428
|
+
|
|
395
429
|
if sensorData.dataType == DataType.NTF_ECG:
|
|
396
430
|
impedanceChannelIndex = self.sensorDatas[
|
|
397
431
|
SensorDataType.DATA_TYPE_EEG
|
|
398
432
|
].channelCount
|
|
399
|
-
|
|
400
|
-
|
|
433
|
+
|
|
434
|
+
if impedanceChannelIndex < len(_impedanceData):
|
|
435
|
+
impedance = _impedanceData[impedanceChannelIndex]
|
|
436
|
+
saturation = _saturationData[impedanceChannelIndex]
|
|
437
|
+
|
|
401
438
|
impedanceChannelIndex += 1
|
|
402
439
|
|
|
403
440
|
dataItem = Sample()
|
|
@@ -805,14 +842,14 @@ class SensorProfileDataCtx:
|
|
|
805
842
|
data_package = bytes(
|
|
806
843
|
self._concatDataBuffer[index + 2 : index + 2 + n]
|
|
807
844
|
)
|
|
808
|
-
self._processDataPackage(data_package, buf)
|
|
845
|
+
self._processDataPackage(data_package, buf, sensor)
|
|
809
846
|
while self._is_running and self.isDataTransfering:
|
|
810
847
|
sensorData: SensorData = None
|
|
811
848
|
try:
|
|
812
849
|
sensorData = buf.get_nowait()
|
|
813
850
|
except Exception as e:
|
|
814
851
|
break
|
|
815
|
-
if sensorData != None and callback != None:
|
|
852
|
+
if loop != None and sensorData != None and callback != None:
|
|
816
853
|
try:
|
|
817
854
|
loop.call_soon_threadsafe(
|
|
818
855
|
callback, sensor, sensorData
|
|
@@ -841,9 +878,10 @@ class SensorProfileDataCtx:
|
|
|
841
878
|
data_package = bytes(
|
|
842
879
|
self._concatDataBuffer[index + 2 : index + 2 + n]
|
|
843
880
|
)
|
|
844
|
-
loop
|
|
845
|
-
|
|
846
|
-
|
|
881
|
+
if loop != None:
|
|
882
|
+
loop.call_soon_threadsafe(
|
|
883
|
+
self.gForce._on_cmd_response, None, data_package
|
|
884
|
+
)
|
|
847
885
|
last_cut = index = index + 2 + n
|
|
848
886
|
else:
|
|
849
887
|
index += 1
|
|
@@ -13,6 +13,7 @@ from bleak import (
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
import sensor
|
|
16
|
+
from sensor import utils
|
|
16
17
|
from sensor.gforce import GForce
|
|
17
18
|
from sensor.sensor_data import DataType, Sample, SensorData
|
|
18
19
|
import asyncio
|
|
@@ -20,7 +21,7 @@ import threading
|
|
|
20
21
|
|
|
21
22
|
from sensor.sensor_data_context import SensorProfileDataCtx
|
|
22
23
|
from sensor.sensor_device import BLEDevice, DeviceInfo, DeviceStateEx
|
|
23
|
-
from sensor.utils import start_loop, sync_timer, timer
|
|
24
|
+
from sensor.utils import async_timer, start_loop, sync_timer, timer
|
|
24
25
|
from contextlib import suppress
|
|
25
26
|
from dataclasses import dataclass
|
|
26
27
|
|
|
@@ -44,6 +45,7 @@ class SensorProfile:
|
|
|
44
45
|
self,
|
|
45
46
|
device: bleak.BLEDevice,
|
|
46
47
|
adv: bleak.AdvertisementData,
|
|
48
|
+
mac: str,
|
|
47
49
|
gforce_event_loop: asyncio.AbstractEventLoop,
|
|
48
50
|
):
|
|
49
51
|
"""
|
|
@@ -52,7 +54,7 @@ class SensorProfile:
|
|
|
52
54
|
:param device (BLEDevice): 蓝牙设备对象,包含设备的名称、地址和信号强度等信息。
|
|
53
55
|
"""
|
|
54
56
|
self._detail_device = device
|
|
55
|
-
self._device = BLEDevice(device.name,
|
|
57
|
+
self._device = BLEDevice(device.name, mac, adv.rssi)
|
|
56
58
|
self._device_state = DeviceStateEx.Disconnected
|
|
57
59
|
self._on_state_changed: Callable[["SensorProfile", DeviceStateEx], None] = None
|
|
58
60
|
self._on_error_callback: Callable[["SensorProfile", str], None] = None
|
|
@@ -85,6 +87,7 @@ class SensorProfile:
|
|
|
85
87
|
try:
|
|
86
88
|
self._data_event_loop.stop()
|
|
87
89
|
self._data_event_loop.close()
|
|
90
|
+
self._data_event_loop = None
|
|
88
91
|
self._data_event_thread.join()
|
|
89
92
|
except Exception as e:
|
|
90
93
|
pass
|
|
@@ -92,6 +95,7 @@ class SensorProfile:
|
|
|
92
95
|
try:
|
|
93
96
|
self._event_loop.stop()
|
|
94
97
|
self._event_loop.close()
|
|
98
|
+
self._event_loop = None
|
|
95
99
|
self._event_thread.join()
|
|
96
100
|
except Exception as e:
|
|
97
101
|
pass
|
|
@@ -108,7 +112,7 @@ class SensorProfile:
|
|
|
108
112
|
def _set_device_state(self, newState: DeviceStateEx):
|
|
109
113
|
if self._device_state != newState:
|
|
110
114
|
self._device_state = newState
|
|
111
|
-
if self._on_state_changed != None:
|
|
115
|
+
if self._event_loop != None and self._on_state_changed != None:
|
|
112
116
|
try:
|
|
113
117
|
self._event_loop.call_soon_threadsafe(
|
|
114
118
|
self._on_state_changed, self, newState
|
|
@@ -223,43 +227,6 @@ class SensorProfile:
|
|
|
223
227
|
self._on_power_changed = callback
|
|
224
228
|
|
|
225
229
|
async def _connect(self) -> bool:
|
|
226
|
-
if (
|
|
227
|
-
self.deviceState == DeviceStateEx.Connected
|
|
228
|
-
or self.deviceState == DeviceStateEx.Ready
|
|
229
|
-
):
|
|
230
|
-
return True
|
|
231
|
-
self._set_device_state(DeviceStateEx.Connecting)
|
|
232
|
-
|
|
233
|
-
def handle_disconnect(_: BleakClient):
|
|
234
|
-
self.stopDataNotification()
|
|
235
|
-
self._data_ctx.close()
|
|
236
|
-
time.sleep(1)
|
|
237
|
-
self._data_buffer.queue.clear()
|
|
238
|
-
self._data_ctx = None
|
|
239
|
-
self._gforce = None
|
|
240
|
-
self._set_device_state(DeviceStateEx.Disconnected)
|
|
241
|
-
pass
|
|
242
|
-
|
|
243
|
-
await self._gforce.connect(handle_disconnect, self._raw_data_buf)
|
|
244
|
-
|
|
245
|
-
if self._gforce.client.is_connected:
|
|
246
|
-
self._set_device_state(DeviceStateEx.Connected)
|
|
247
|
-
if self._gforce.client.mtu_size >= 80:
|
|
248
|
-
self._set_device_state(DeviceStateEx.Ready)
|
|
249
|
-
else:
|
|
250
|
-
self.disconnect()
|
|
251
|
-
else:
|
|
252
|
-
self._set_device_state(DeviceStateEx.Disconnected)
|
|
253
|
-
|
|
254
|
-
return True
|
|
255
|
-
|
|
256
|
-
def connect(self) -> bool:
|
|
257
|
-
"""
|
|
258
|
-
连接传感器。
|
|
259
|
-
|
|
260
|
-
:return: bool: 如果连接成功,返回 True;否则返回 False。
|
|
261
|
-
|
|
262
|
-
"""
|
|
263
230
|
if self._event_thread == None:
|
|
264
231
|
self._event_loop = asyncio.new_event_loop()
|
|
265
232
|
self._event_thread = threading.Thread(
|
|
@@ -304,12 +271,58 @@ class SensorProfile:
|
|
|
304
271
|
self._data_ctx = SensorProfileDataCtx(
|
|
305
272
|
self._gforce, self._device.Address, self._raw_data_buf
|
|
306
273
|
)
|
|
307
|
-
|
|
308
|
-
|
|
274
|
+
if self._data_ctx.isUniversalStream:
|
|
275
|
+
timer(self._data_event_loop, 0, self._process_universal_data())
|
|
276
|
+
|
|
277
|
+
if (
|
|
278
|
+
self.deviceState == DeviceStateEx.Connected
|
|
279
|
+
or self.deviceState == DeviceStateEx.Ready
|
|
280
|
+
):
|
|
281
|
+
return True
|
|
282
|
+
self._set_device_state(DeviceStateEx.Connecting)
|
|
309
283
|
|
|
284
|
+
def handle_disconnect(_: BleakClient):
|
|
285
|
+
self._data_ctx.close()
|
|
286
|
+
time.sleep(0.1)
|
|
287
|
+
self._data_buffer.queue.clear()
|
|
288
|
+
self._data_ctx = None
|
|
289
|
+
self._gforce = None
|
|
290
|
+
self._set_device_state(DeviceStateEx.Disconnected)
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
await self._gforce.connect(handle_disconnect, self._raw_data_buf)
|
|
294
|
+
|
|
295
|
+
if self._gforce.client.is_connected:
|
|
296
|
+
self._set_device_state(DeviceStateEx.Connected)
|
|
297
|
+
self._set_device_state(DeviceStateEx.Ready)
|
|
298
|
+
# if self._gforce.client.mtu_size >= 80:
|
|
299
|
+
# self._set_device_state(DeviceStateEx.Ready)
|
|
300
|
+
# else:
|
|
301
|
+
# self.disconnect()
|
|
302
|
+
else:
|
|
303
|
+
self._set_device_state(DeviceStateEx.Disconnected)
|
|
304
|
+
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
def connect(self) -> bool:
|
|
308
|
+
"""
|
|
309
|
+
连接传感器。
|
|
310
|
+
|
|
311
|
+
:return: bool: 如果连接成功,返回 True;否则返回 False。
|
|
312
|
+
|
|
313
|
+
"""
|
|
310
314
|
result = sync_timer(self._gforce_event_loop, 0, self._connect())
|
|
311
315
|
return result
|
|
312
316
|
|
|
317
|
+
async def asyncConnect(self) -> bool:
|
|
318
|
+
"""
|
|
319
|
+
连接传感器。
|
|
320
|
+
|
|
321
|
+
:return: bool: 如果连接成功,返回 True;否则返回 False。
|
|
322
|
+
|
|
323
|
+
"""
|
|
324
|
+
return await async_timer(self._gforce_event_loop, 0, self._connect())
|
|
325
|
+
|
|
313
326
|
async def _waitForDisconnect(self) -> bool:
|
|
314
327
|
while self.deviceState != DeviceStateEx.Disconnected:
|
|
315
328
|
asyncio.sleep(1)
|
|
@@ -325,7 +338,7 @@ class SensorProfile:
|
|
|
325
338
|
return False
|
|
326
339
|
self._set_device_state(DeviceStateEx.Disconnecting)
|
|
327
340
|
await self._gforce.disconnect()
|
|
328
|
-
await asyncio.wait_for(self._waitForDisconnect(),
|
|
341
|
+
await asyncio.wait_for(self._waitForDisconnect(), utils._TIMEOUT)
|
|
329
342
|
return True
|
|
330
343
|
|
|
331
344
|
def disconnect(self) -> bool:
|
|
@@ -337,16 +350,29 @@ class SensorProfile:
|
|
|
337
350
|
"""
|
|
338
351
|
return sync_timer(self._gforce_event_loop, 0, self._disconnect())
|
|
339
352
|
|
|
353
|
+
async def asyncDisconnect(self) -> bool:
|
|
354
|
+
"""
|
|
355
|
+
断开传感器连接。
|
|
356
|
+
|
|
357
|
+
:return: bool: 如果断开连接成功,返回 True;否则返回 False。
|
|
358
|
+
|
|
359
|
+
"""
|
|
360
|
+
return await async_timer(self._gforce_event_loop, 0, self._disconnect())
|
|
361
|
+
|
|
340
362
|
async def _process_data(self):
|
|
341
363
|
while self._data_ctx._is_running and self._data_ctx.isDataTransfering:
|
|
342
|
-
self._data_ctx.process_data(self._data_buffer)
|
|
364
|
+
self._data_ctx.process_data(self._data_buffer, self)
|
|
343
365
|
while self._data_ctx._is_running and self._data_ctx.isDataTransfering:
|
|
344
366
|
sensorData: SensorData = None
|
|
345
367
|
try:
|
|
346
368
|
sensorData = self._data_buffer.get_nowait()
|
|
347
369
|
except Exception as e:
|
|
348
370
|
break
|
|
349
|
-
if
|
|
371
|
+
if (
|
|
372
|
+
self._event_loop != None
|
|
373
|
+
and sensorData != None
|
|
374
|
+
and self._on_data_callback != None
|
|
375
|
+
):
|
|
350
376
|
try:
|
|
351
377
|
self._event_loop.call_soon_threadsafe(
|
|
352
378
|
self._on_data_callback, self, sensorData
|
|
@@ -361,20 +387,6 @@ class SensorProfile:
|
|
|
361
387
|
)
|
|
362
388
|
|
|
363
389
|
async def _startDataNotification(self) -> bool:
|
|
364
|
-
result = await self._data_ctx.start_streaming()
|
|
365
|
-
self._data_buffer.queue.clear()
|
|
366
|
-
self._data_ctx.clear()
|
|
367
|
-
if not self._data_ctx.isUniversalStream:
|
|
368
|
-
timer(self._data_event_loop, 0, self._process_data())
|
|
369
|
-
return result
|
|
370
|
-
|
|
371
|
-
def startDataNotification(self) -> bool:
|
|
372
|
-
"""
|
|
373
|
-
开始数据通知。
|
|
374
|
-
|
|
375
|
-
:return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
|
|
376
|
-
|
|
377
|
-
"""
|
|
378
390
|
if self.deviceState != DeviceStateEx.Ready:
|
|
379
391
|
return False
|
|
380
392
|
if self._data_ctx == None:
|
|
@@ -394,19 +406,34 @@ class SensorProfile:
|
|
|
394
406
|
self._data_event_thread.name = self.BLEDevice.Name + " data"
|
|
395
407
|
self._data_event_thread.start()
|
|
396
408
|
|
|
397
|
-
|
|
409
|
+
result = await self._data_ctx.start_streaming()
|
|
410
|
+
self._data_buffer.queue.clear()
|
|
411
|
+
self._data_ctx.clear()
|
|
412
|
+
if not self._data_ctx.isUniversalStream:
|
|
413
|
+
timer(self._data_event_loop, 0, self._process_data())
|
|
414
|
+
return result
|
|
398
415
|
|
|
399
|
-
|
|
416
|
+
def startDataNotification(self) -> bool:
|
|
417
|
+
"""
|
|
418
|
+
开始数据通知。
|
|
400
419
|
|
|
401
|
-
return
|
|
420
|
+
:return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
|
|
402
421
|
|
|
403
|
-
def stopDataNotification(self) -> bool:
|
|
404
422
|
"""
|
|
405
|
-
|
|
423
|
+
return sync_timer(self._gforce_event_loop, 0, self._startDataNotification())
|
|
406
424
|
|
|
407
|
-
|
|
425
|
+
async def asyncStartDataNotification(self) -> bool:
|
|
426
|
+
"""
|
|
427
|
+
开始数据通知。
|
|
428
|
+
|
|
429
|
+
:return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
|
|
408
430
|
|
|
409
431
|
"""
|
|
432
|
+
return await async_timer(
|
|
433
|
+
self._gforce_event_loop, 0, self._startDataNotification()
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
async def _stopDataNotification(self) -> bool:
|
|
410
437
|
if self.deviceState != DeviceStateEx.Ready:
|
|
411
438
|
return False
|
|
412
439
|
if self._data_ctx == None:
|
|
@@ -417,12 +444,32 @@ class SensorProfile:
|
|
|
417
444
|
if not self._data_ctx.isDataTransfering:
|
|
418
445
|
return True
|
|
419
446
|
|
|
447
|
+
return not await self._data_ctx.stop_streaming()
|
|
448
|
+
|
|
449
|
+
def stopDataNotification(self) -> bool:
|
|
450
|
+
"""
|
|
451
|
+
停止数据通知。
|
|
452
|
+
|
|
453
|
+
:return: bool: 如果停止数据通知成功,返回 True;否则返回 False。
|
|
454
|
+
|
|
455
|
+
"""
|
|
420
456
|
return sync_timer(self._gforce_event_loop, 0, self._stopDataNotification())
|
|
421
457
|
|
|
458
|
+
async def asyncStopDataNotification(self) -> bool:
|
|
459
|
+
"""
|
|
460
|
+
停止数据通知。
|
|
461
|
+
|
|
462
|
+
:return: bool: 如果停止数据通知成功,返回 True;否则返回 False。
|
|
463
|
+
|
|
464
|
+
"""
|
|
465
|
+
return await async_timer(
|
|
466
|
+
self._gforce_event_loop, 0, self._stopDataNotification()
|
|
467
|
+
)
|
|
468
|
+
|
|
422
469
|
async def _refresh_power(self):
|
|
423
470
|
self._power = await self._gforce.get_battery_level()
|
|
424
471
|
|
|
425
|
-
if self._on_power_changed != None:
|
|
472
|
+
if self._event_loop != None and self._on_power_changed != None:
|
|
426
473
|
try:
|
|
427
474
|
self._event_loop.call_soon_threadsafe(
|
|
428
475
|
self._on_power_changed, self, self._power
|
|
@@ -469,6 +516,26 @@ class SensorProfile:
|
|
|
469
516
|
self._gforce_event_loop,
|
|
470
517
|
0,
|
|
471
518
|
self._init(packageSampleCount, powerRefreshInterval),
|
|
519
|
+
120,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
async def asyncInit(
|
|
523
|
+
self, packageSampleCount: int, powerRefreshInterval: int
|
|
524
|
+
) -> bool:
|
|
525
|
+
"""
|
|
526
|
+
初始化数据采集。
|
|
527
|
+
|
|
528
|
+
:param packageSampleCount (int): 数据包中的样本数量。
|
|
529
|
+
:param powerRefreshInterval (int): 电量刷新间隔。
|
|
530
|
+
|
|
531
|
+
:return: bool: 初始化结果。True 表示成功,False 表示失败。
|
|
532
|
+
|
|
533
|
+
"""
|
|
534
|
+
return await async_timer(
|
|
535
|
+
self._gforce_event_loop,
|
|
536
|
+
0,
|
|
537
|
+
self._init(packageSampleCount, powerRefreshInterval),
|
|
538
|
+
120,
|
|
472
539
|
)
|
|
473
540
|
|
|
474
541
|
def getBatteryLevel(self) -> int:
|
|
@@ -502,3 +569,15 @@ class SensorProfile:
|
|
|
502
569
|
|
|
503
570
|
"""
|
|
504
571
|
return ""
|
|
572
|
+
|
|
573
|
+
async def AsyncSetParam(self, key: str, value: str) -> str:
|
|
574
|
+
"""
|
|
575
|
+
设置传感器的参数。
|
|
576
|
+
|
|
577
|
+
:param key (str): 参数的键。
|
|
578
|
+
:param value (str): 参数的值。
|
|
579
|
+
|
|
580
|
+
:return: str: 设置参数的结果。
|
|
581
|
+
|
|
582
|
+
"""
|
|
583
|
+
return ""
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import platform
|
|
3
|
+
import queue
|
|
4
|
+
import signal
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
_terminated = False
|
|
8
|
+
_TIMEOUT = 10
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def delay(_time: float, function) -> any:
|
|
12
|
+
if _time > 0:
|
|
13
|
+
await asyncio.sleep(_time)
|
|
14
|
+
return await function
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def timer(_loop: asyncio.AbstractEventLoop, _delay: float, function):
|
|
18
|
+
if _loop == None:
|
|
19
|
+
return
|
|
20
|
+
try:
|
|
21
|
+
asyncio.run_coroutine_threadsafe(delay(_delay, function), _loop)
|
|
22
|
+
except Exception as e:
|
|
23
|
+
print(e)
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def sync_timer(
|
|
28
|
+
_loop: asyncio.AbstractEventLoop, _delay: float, function, _timeout=_TIMEOUT
|
|
29
|
+
) -> any:
|
|
30
|
+
if _loop == None:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
f = asyncio.run_coroutine_threadsafe(
|
|
35
|
+
asyncio.wait_for(delay(_delay, function), _delay + _timeout), _loop
|
|
36
|
+
)
|
|
37
|
+
return f.result(timeout=_timeout)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print(e)
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def async_timer(
|
|
44
|
+
_loop: asyncio.AbstractEventLoop, _delay: float, function, _timeout=_TIMEOUT
|
|
45
|
+
) -> any:
|
|
46
|
+
if _loop == None:
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
f = asyncio.run_coroutine_threadsafe(
|
|
51
|
+
asyncio.wait_for(delay(_delay, function), _delay + _timeout), _loop
|
|
52
|
+
)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(e)
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
while not _terminated and not f.done():
|
|
58
|
+
await asyncio.sleep(0.1)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
if not f.cancelled():
|
|
62
|
+
return f.result()
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(e)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def start_loop(loop: asyncio.BaseEventLoop):
|
|
69
|
+
if platform.system() == "Darwin":
|
|
70
|
+
asyncio.get_running_loop = asyncio.get_event_loop
|
|
71
|
+
asyncio.set_event_loop(loop)
|
|
72
|
+
loop.run_forever()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sensor-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Python sdk for Synchroni
|
|
5
5
|
Home-page: https://github.com/oymotion/SynchroniSDKPython
|
|
6
6
|
Author: Martin Ye
|
|
@@ -68,6 +68,12 @@ success = SensorControllerInstance.startScan(6000)
|
|
|
68
68
|
|
|
69
69
|
returns true if start scan success, periodInMS means onDeviceCallback will be called every periodInMS
|
|
70
70
|
|
|
71
|
+
Use `def scan(period_in_ms: int) -> list[BLEDevice]` to scan once time
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
bleDevices = SensorControllerInstance.scan(6000)
|
|
75
|
+
```
|
|
76
|
+
|
|
71
77
|
### 3. Stop scan
|
|
72
78
|
|
|
73
79
|
Use `def stopScan() -> None` to stop scan
|
|
@@ -122,7 +128,7 @@ sensorProfiles = SensorControllerInstance.getConnectedSensors()
|
|
|
122
128
|
|
|
123
129
|
### 9. Get Connected BLE Devices
|
|
124
130
|
|
|
125
|
-
Use `def getConnectedDevices() -> list[
|
|
131
|
+
Use `def getConnectedDevices() -> list[BLEDevice]` to get connected BLE Devices.
|
|
126
132
|
|
|
127
133
|
```python
|
|
128
134
|
bleDevices = SensorControllerInstance.getConnectedDevices()
|
|
@@ -340,3 +346,9 @@ batteryPower = sensorProfile.getBatteryLevel()
|
|
|
340
346
|
```
|
|
341
347
|
|
|
342
348
|
Please check console.py in examples directory
|
|
349
|
+
|
|
350
|
+
## Async methods
|
|
351
|
+
|
|
352
|
+
all methods start with async is async methods, they has same params and return result as sync methods.
|
|
353
|
+
|
|
354
|
+
Please check async_console.py in examples directory
|
|
@@ -8,7 +8,7 @@ with open(os.path.join(this_directory, "README.md"), "r", encoding="utf-8") as f
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="sensor-sdk",
|
|
11
|
-
version="0.0.
|
|
11
|
+
version="0.0.5",
|
|
12
12
|
description="Python sdk for Synchroni",
|
|
13
13
|
long_description=long_description,
|
|
14
14
|
long_description_content_type="text/markdown",
|
sensor-sdk-0.0.4/sensor/utils.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import signal
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
async def delay(time: float, function) -> any:
|
|
6
|
-
if time > 0:
|
|
7
|
-
await asyncio.sleep(time)
|
|
8
|
-
return await function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def timer(_loop: asyncio.AbstractEventLoop, time: float, function):
|
|
12
|
-
try:
|
|
13
|
-
asyncio.run_coroutine_threadsafe(delay(time, function), _loop)
|
|
14
|
-
except Exception as e:
|
|
15
|
-
print(e)
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def sync_timer(_loop: asyncio.AbstractEventLoop, time: float, function) -> any:
|
|
20
|
-
try:
|
|
21
|
-
f = asyncio.run_coroutine_threadsafe(delay(time, function), _loop)
|
|
22
|
-
return f.result()
|
|
23
|
-
except Exception as e:
|
|
24
|
-
print(e)
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def start_loop(loop: asyncio.BaseEventLoop):
|
|
29
|
-
asyncio.set_event_loop(loop)
|
|
30
|
-
loop.run_forever()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|