sensor-sdk 0.0.13__tar.gz → 0.0.16__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.13 → sensor-sdk-0.0.16}/PKG-INFO +2 -2
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/README.md +382 -382
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/gforce.py +15 -4
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/sensor_data.py +1 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/sensor_data_context.py +87 -33
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/sensor_profile.py +57 -11
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor_sdk.egg-info/PKG-INFO +2 -2
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/setup.py +2 -2
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/LICENSE.txt +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/__init__.py +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/sensor_controller.py +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/sensor_device.py +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor/utils.py +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor_sdk.egg-info/SOURCES.txt +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor_sdk.egg-info/dependency_links.txt +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor_sdk.egg-info/requires.txt +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor_sdk.egg-info/top_level.txt +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/sensor_sdk.egg-info/zip-safe +0 -0
- {sensor-sdk-0.0.13 → sensor-sdk-0.0.16}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sensor-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.16
|
|
4
4
|
Summary: Python sdk for Synchroni
|
|
5
5
|
Home-page: https://github.com/oymotion/SynchroniSDKPython
|
|
6
6
|
Author: Martin Ye
|
|
7
7
|
Author-email: yecq_82@hotmail.com
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9.0
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.txt
|
|
11
11
|
|
|
@@ -1,382 +1,382 @@
|
|
|
1
|
-
# sensor-sdk
|
|
2
|
-
|
|
3
|
-
Synchroni sdk for Python
|
|
4
|
-
|
|
5
|
-
## Brief
|
|
6
|
-
|
|
7
|
-
Synchroni SDK is the software development kit for developers to access Synchroni products.
|
|
8
|
-
|
|
9
|
-
## Contributing
|
|
10
|
-
|
|
11
|
-
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
12
|
-
|
|
13
|
-
## License
|
|
14
|
-
|
|
15
|
-
MIT
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Installation
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
pip install sensor-sdk
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## 1. Permission
|
|
26
|
-
|
|
27
|
-
Application will obtain bluetooth permission by itself.
|
|
28
|
-
|
|
29
|
-
## 2. Import SDK
|
|
30
|
-
|
|
31
|
-
```python
|
|
32
|
-
from sensor import *
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## SensorController methods
|
|
36
|
-
|
|
37
|
-
### 1. Initalize
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
SensorControllerInstance = SensorController()
|
|
41
|
-
|
|
42
|
-
# register scan listener
|
|
43
|
-
if not SensorControllerInstance.hasDeviceFoundCallback:
|
|
44
|
-
def on_device_callback(deviceList: List[BLEDevice]):
|
|
45
|
-
# return all devices doesn't connected
|
|
46
|
-
pass
|
|
47
|
-
SensorControllerInstance.onDeviceFoundCallback = on_device_callback
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### 2. Start scan
|
|
51
|
-
|
|
52
|
-
Use `def startScan(period_in_ms: int) -> bool` to start scan
|
|
53
|
-
|
|
54
|
-
```python
|
|
55
|
-
success = SensorControllerInstance.startScan(6000)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
returns true if start scan success, periodInMS means onDeviceCallback will be called every periodInMS
|
|
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
|
-
|
|
66
|
-
### 3. Stop scan
|
|
67
|
-
|
|
68
|
-
Use `def stopScan() -> None` to stop scan
|
|
69
|
-
|
|
70
|
-
```python
|
|
71
|
-
SensorControllerInstance.stopScan()
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 4. Check scaning
|
|
75
|
-
|
|
76
|
-
Use `property isScanning: bool` to check scanning status
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
isScanning = SensorControllerInstance.isScanning
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### 5. Check if bluetooth is enabled
|
|
83
|
-
|
|
84
|
-
Use `property isEnabled: bool` to check if bluetooth is enabled
|
|
85
|
-
|
|
86
|
-
```python
|
|
87
|
-
isEnabled = SensorControllerInstance.isEnabled
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### 6. Create SensorProfile
|
|
91
|
-
|
|
92
|
-
Use `def requireSensor(device: BLEDevice) -> SensorProfile | None` to create SensorProfile.
|
|
93
|
-
|
|
94
|
-
If bleDevice is invalid, result is None.
|
|
95
|
-
|
|
96
|
-
```python
|
|
97
|
-
sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### 7. Get SensorProfile
|
|
101
|
-
|
|
102
|
-
Use `def getSensor(device: BLEDevice) -> SensorProfile | None` to get SensorProfile.
|
|
103
|
-
|
|
104
|
-
If SensorProfile didn't created, result is None.
|
|
105
|
-
|
|
106
|
-
```python
|
|
107
|
-
sensorProfile = SensorControllerInstance.getSensor(bleDevice)
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### 8. Get Connected SensorProfiles
|
|
111
|
-
|
|
112
|
-
Use `def getConnectedSensors() -> list[SensorProfile]` to get connected SensorProfiles.
|
|
113
|
-
|
|
114
|
-
```python
|
|
115
|
-
sensorProfiles = SensorControllerInstance.getConnectedSensors()
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 9. Get Connected BLE Devices
|
|
119
|
-
|
|
120
|
-
Use `def getConnectedDevices() -> list[BLEDevice]` to get connected BLE Devices.
|
|
121
|
-
|
|
122
|
-
```python
|
|
123
|
-
bleDevices = SensorControllerInstance.getConnectedDevices()
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 10. Terminate
|
|
127
|
-
|
|
128
|
-
Use `def terminate()` to terminate sdk
|
|
129
|
-
|
|
130
|
-
```python
|
|
131
|
-
|
|
132
|
-
def terminate():
|
|
133
|
-
SensorControllerInstance.terminate()
|
|
134
|
-
exit()
|
|
135
|
-
|
|
136
|
-
def main():
|
|
137
|
-
signal.signal(signal.SIGINT, lambda signal, frame: terminate())
|
|
138
|
-
time.sleep(30)
|
|
139
|
-
SensorControllerInstance.terminate()
|
|
140
|
-
|
|
141
|
-
Please MAKE SURE to call terminate when exit main() or press Ctrl+C
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## SensorProfile methods
|
|
145
|
-
|
|
146
|
-
### 11. Initalize
|
|
147
|
-
|
|
148
|
-
Please register callbacks for SensorProfile
|
|
149
|
-
|
|
150
|
-
```python
|
|
151
|
-
sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
|
|
152
|
-
|
|
153
|
-
# register callbacks
|
|
154
|
-
def on_state_changed(sensor, newState):
|
|
155
|
-
# please do logic when device disconnected unexpected
|
|
156
|
-
pass
|
|
157
|
-
|
|
158
|
-
def on_error_callback(sensor, reason):
|
|
159
|
-
# called when error occurs
|
|
160
|
-
pass
|
|
161
|
-
|
|
162
|
-
def on_power_changed(sensor, power):
|
|
163
|
-
# callback for get battery level of device, power from 0 - 100, -1 is invalid
|
|
164
|
-
pass
|
|
165
|
-
|
|
166
|
-
def on_data_callback(sensor, data):
|
|
167
|
-
# called after start data transfer
|
|
168
|
-
pass
|
|
169
|
-
|
|
170
|
-
sensorProfile.onStateChanged = on_state_changed
|
|
171
|
-
sensorProfile.onErrorCallback = on_error_callback
|
|
172
|
-
sensorProfile.onPowerChanged = on_power_changed
|
|
173
|
-
sensorProfile.onDataCallback = on_data_callback
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### 12. Connect device
|
|
177
|
-
|
|
178
|
-
Use `def connect() -> bool` to connect.
|
|
179
|
-
|
|
180
|
-
```python
|
|
181
|
-
success = sensorProfile.connect()
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### 13. Disconnect
|
|
185
|
-
|
|
186
|
-
Use `def disconnect() -> bool` to disconnect.
|
|
187
|
-
|
|
188
|
-
```python
|
|
189
|
-
success = sensorProfile.disconnect()
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### 14. Get device status
|
|
193
|
-
|
|
194
|
-
Use `property deviceState: DeviceStateEx` to get device status.
|
|
195
|
-
|
|
196
|
-
Please send command in 'Ready' state, should be after connect() return True.
|
|
197
|
-
|
|
198
|
-
```python
|
|
199
|
-
deviceStateEx = sensorProfile.deviceState
|
|
200
|
-
|
|
201
|
-
# deviceStateEx has define:
|
|
202
|
-
# class DeviceStateEx(Enum):
|
|
203
|
-
# Disconnected = 0
|
|
204
|
-
# Connecting = 1
|
|
205
|
-
# Connected = 2
|
|
206
|
-
# Ready = 3
|
|
207
|
-
# Disconnecting = 4
|
|
208
|
-
# Invalid = 5
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### 15. Get BLE device of SensorProfile
|
|
212
|
-
|
|
213
|
-
Use `property BLEDevice: BLEDevice` to get BLE device of SensorProfile.
|
|
214
|
-
|
|
215
|
-
```python
|
|
216
|
-
bleDevice = sensorProfile.BLEDevice
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### 16. Get device info of SensorProfile
|
|
220
|
-
|
|
221
|
-
Use `def getDeviceInfo() -> dict | None` to get device info of SensorProfile.
|
|
222
|
-
|
|
223
|
-
Please call after device in 'Ready' state, return None if it's not connected.
|
|
224
|
-
|
|
225
|
-
```python
|
|
226
|
-
deviceInfo = sensorProfile.getDeviceInfo()
|
|
227
|
-
|
|
228
|
-
# deviceInfo has defines:
|
|
229
|
-
# deviceInfo = {
|
|
230
|
-
# "deviceName": str,
|
|
231
|
-
# "modelName": str,
|
|
232
|
-
# "hardwareVersion": str,
|
|
233
|
-
# "firmwareVersion": str,
|
|
234
|
-
# "emgChannelCount": int,
|
|
235
|
-
# "eegChannelCount": int,
|
|
236
|
-
# "ecgChannelCount": int,
|
|
237
|
-
# "accChannelCount": int,
|
|
238
|
-
# "gyroChannelCount": int,
|
|
239
|
-
# "brthChannelCount": int,
|
|
240
|
-
# "mtuSize": int
|
|
241
|
-
# }
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### 17. Init data transfer
|
|
245
|
-
|
|
246
|
-
Use `def init(packageSampleCount: int, powerRefreshInterval: int) -> bool`.
|
|
247
|
-
|
|
248
|
-
Please call after device in 'Ready' state, return True if init succeed.
|
|
249
|
-
|
|
250
|
-
```python
|
|
251
|
-
success = sensorProfile.init(5, 60*1000)
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
packageSampleCount: set sample counts of SensorData.channelSamples in onDataCallback()
|
|
255
|
-
powerRefreshInterval: callback period for onPowerChanged()
|
|
256
|
-
|
|
257
|
-
### 18. Check if init data transfer succeed
|
|
258
|
-
|
|
259
|
-
Use `property hasInited: bool` to check if init data transfer succeed.
|
|
260
|
-
|
|
261
|
-
```python
|
|
262
|
-
hasInited = sensorProfile.hasInited
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### 19. DataNotify
|
|
266
|
-
|
|
267
|
-
Use `def startDataNotification() -> bool` to start data notification.
|
|
268
|
-
|
|
269
|
-
Please call if hasInited return True
|
|
270
|
-
|
|
271
|
-
#### 19.1 Start data transfer
|
|
272
|
-
|
|
273
|
-
```python
|
|
274
|
-
success = sensorProfile.startDataNotification()
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
Data type list:
|
|
278
|
-
|
|
279
|
-
```python
|
|
280
|
-
class DataType(Enum):
|
|
281
|
-
NTF_ACC = 0x1 # unit is g
|
|
282
|
-
NTF_GYRO = 0x2 # unit is degree/s
|
|
283
|
-
NTF_EEG = 0x10 # unit is uV
|
|
284
|
-
NTF_ECG = 0x11 # unit is uV
|
|
285
|
-
NTF_BRTH = 0x15 # unit is uV
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
Process data in onDataCallback.
|
|
289
|
-
|
|
290
|
-
```python
|
|
291
|
-
def on_data_callback(sensor, data):
|
|
292
|
-
if data.dataType == DataType.NTF_EEG:
|
|
293
|
-
pass
|
|
294
|
-
elif data.dataType == DataType.NTF_ECG:
|
|
295
|
-
pass
|
|
296
|
-
|
|
297
|
-
# process data as you wish
|
|
298
|
-
for oneChannelSamples in data.channelSamples:
|
|
299
|
-
for sample in oneChannelSamples:
|
|
300
|
-
if sample.isLost:
|
|
301
|
-
# do some logic
|
|
302
|
-
pass
|
|
303
|
-
else:
|
|
304
|
-
# draw with sample.data & sample.channelIndex
|
|
305
|
-
# print(f"{sample.channelIndex} | {sample.sampleIndex} | {sample.data} | {sample.impedance}")
|
|
306
|
-
pass
|
|
307
|
-
|
|
308
|
-
sensorProfile.onDataCallback = on_data_callback
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
#### 19.2 Stop data transfer
|
|
312
|
-
|
|
313
|
-
Use `def stopDataNotification() -> bool` to stop data transfer.
|
|
314
|
-
|
|
315
|
-
```python
|
|
316
|
-
success = sensorProfile.stopDataNotification()
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
#### 19.3 Check if it's data transfering
|
|
320
|
-
|
|
321
|
-
Use `property isDataTransfering: bool` to check if it's data transfering.
|
|
322
|
-
|
|
323
|
-
```python
|
|
324
|
-
isDataTransfering = sensorProfile.isDataTransfering
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### 20. Get battery level
|
|
328
|
-
|
|
329
|
-
Use `def getBatteryLevel() -> int` to get battery level. Please call after device in 'Ready' state.
|
|
330
|
-
|
|
331
|
-
```python
|
|
332
|
-
batteryPower = sensorProfile.getBatteryLevel()
|
|
333
|
-
|
|
334
|
-
# batteryPower is battery level returned, value ranges from 0 to 100, 0 means out of battery, while 100 means full.
|
|
335
|
-
```
|
|
336
|
-
|
|
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
|
|
344
|
-
|
|
345
|
-
### setParam method
|
|
346
|
-
|
|
347
|
-
Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
|
|
348
|
-
|
|
349
|
-
Below is available key and value:
|
|
350
|
-
|
|
351
|
-
```python
|
|
352
|
-
result = sensorProfile.setParam("NTF_EMG", "ON")
|
|
353
|
-
# set EMG data to ON or OFF, result is "OK" if succeed
|
|
354
|
-
|
|
355
|
-
result = sensorProfile.setParam("NTF_EEG", "ON")
|
|
356
|
-
# set EEG data to ON or OFF, result is "OK" if succeed
|
|
357
|
-
|
|
358
|
-
result = sensorProfile.setParam("NTF_ECG", "ON")
|
|
359
|
-
# set ECG data to ON or OFF, result is "OK" if succeed
|
|
360
|
-
|
|
361
|
-
result = sensorProfile.setParam("NTF_IMU", "ON")
|
|
362
|
-
# set IMU data to ON or OFF, result is "OK" if succeed
|
|
363
|
-
|
|
364
|
-
result = sensorProfile.setParam("NTF_BRTH", "ON")
|
|
365
|
-
# set BRTH data to ON or OFF, result is "OK" if succeed
|
|
366
|
-
|
|
367
|
-
result = sensorProfile.setParam("FILTER_50Hz", "ON")
|
|
368
|
-
# set 50Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
369
|
-
|
|
370
|
-
result = sensorProfile.setParam("FILTER_60Hz", "ON")
|
|
371
|
-
# set 60Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
372
|
-
|
|
373
|
-
result = sensorProfile.setParam("FILTER_HPF", "ON")
|
|
374
|
-
# set 0.5Hz hpf filter to ON or OFF, result is "OK" if succeed
|
|
375
|
-
|
|
376
|
-
result = sensorProfile.setParam("FILTER_LPF", "ON")
|
|
377
|
-
# set 80Hz lpf filter to ON or OFF, result is "OK" if succeed
|
|
378
|
-
|
|
379
|
-
result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.csv")
|
|
380
|
-
# set debug ble data path, result is "OK" if succeed
|
|
381
|
-
# please give an absolute path and make sure it is valid and writeable by yourself
|
|
382
|
-
```
|
|
1
|
+
# sensor-sdk
|
|
2
|
+
|
|
3
|
+
Synchroni sdk for Python
|
|
4
|
+
|
|
5
|
+
## Brief
|
|
6
|
+
|
|
7
|
+
Synchroni SDK is the software development kit for developers to access Synchroni products.
|
|
8
|
+
|
|
9
|
+
## Contributing
|
|
10
|
+
|
|
11
|
+
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
12
|
+
|
|
13
|
+
## License
|
|
14
|
+
|
|
15
|
+
MIT
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pip install sensor-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 1. Permission
|
|
26
|
+
|
|
27
|
+
Application will obtain bluetooth permission by itself.
|
|
28
|
+
|
|
29
|
+
## 2. Import SDK
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from sensor import *
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## SensorController methods
|
|
36
|
+
|
|
37
|
+
### 1. Initalize
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
SensorControllerInstance = SensorController()
|
|
41
|
+
|
|
42
|
+
# register scan listener
|
|
43
|
+
if not SensorControllerInstance.hasDeviceFoundCallback:
|
|
44
|
+
def on_device_callback(deviceList: List[BLEDevice]):
|
|
45
|
+
# return all devices doesn't connected
|
|
46
|
+
pass
|
|
47
|
+
SensorControllerInstance.onDeviceFoundCallback = on_device_callback
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Start scan
|
|
51
|
+
|
|
52
|
+
Use `def startScan(period_in_ms: int) -> bool` to start scan
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
success = SensorControllerInstance.startScan(6000)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
returns true if start scan success, periodInMS means onDeviceCallback will be called every periodInMS
|
|
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
|
+
|
|
66
|
+
### 3. Stop scan
|
|
67
|
+
|
|
68
|
+
Use `def stopScan() -> None` to stop scan
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
SensorControllerInstance.stopScan()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 4. Check scaning
|
|
75
|
+
|
|
76
|
+
Use `property isScanning: bool` to check scanning status
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
isScanning = SensorControllerInstance.isScanning
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 5. Check if bluetooth is enabled
|
|
83
|
+
|
|
84
|
+
Use `property isEnabled: bool` to check if bluetooth is enabled
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
isEnabled = SensorControllerInstance.isEnabled
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 6. Create SensorProfile
|
|
91
|
+
|
|
92
|
+
Use `def requireSensor(device: BLEDevice) -> SensorProfile | None` to create SensorProfile.
|
|
93
|
+
|
|
94
|
+
If bleDevice is invalid, result is None.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 7. Get SensorProfile
|
|
101
|
+
|
|
102
|
+
Use `def getSensor(device: BLEDevice) -> SensorProfile | None` to get SensorProfile.
|
|
103
|
+
|
|
104
|
+
If SensorProfile didn't created, result is None.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
sensorProfile = SensorControllerInstance.getSensor(bleDevice)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 8. Get Connected SensorProfiles
|
|
111
|
+
|
|
112
|
+
Use `def getConnectedSensors() -> list[SensorProfile]` to get connected SensorProfiles.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
sensorProfiles = SensorControllerInstance.getConnectedSensors()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 9. Get Connected BLE Devices
|
|
119
|
+
|
|
120
|
+
Use `def getConnectedDevices() -> list[BLEDevice]` to get connected BLE Devices.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
bleDevices = SensorControllerInstance.getConnectedDevices()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 10. Terminate
|
|
127
|
+
|
|
128
|
+
Use `def terminate()` to terminate sdk
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
|
|
132
|
+
def terminate():
|
|
133
|
+
SensorControllerInstance.terminate()
|
|
134
|
+
exit()
|
|
135
|
+
|
|
136
|
+
def main():
|
|
137
|
+
signal.signal(signal.SIGINT, lambda signal, frame: terminate())
|
|
138
|
+
time.sleep(30)
|
|
139
|
+
SensorControllerInstance.terminate()
|
|
140
|
+
|
|
141
|
+
Please MAKE SURE to call terminate when exit main() or press Ctrl+C
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## SensorProfile methods
|
|
145
|
+
|
|
146
|
+
### 11. Initalize
|
|
147
|
+
|
|
148
|
+
Please register callbacks for SensorProfile
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
|
|
152
|
+
|
|
153
|
+
# register callbacks
|
|
154
|
+
def on_state_changed(sensor, newState):
|
|
155
|
+
# please do logic when device disconnected unexpected
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
def on_error_callback(sensor, reason):
|
|
159
|
+
# called when error occurs
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
def on_power_changed(sensor, power):
|
|
163
|
+
# callback for get battery level of device, power from 0 - 100, -1 is invalid
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def on_data_callback(sensor, data):
|
|
167
|
+
# called after start data transfer
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
sensorProfile.onStateChanged = on_state_changed
|
|
171
|
+
sensorProfile.onErrorCallback = on_error_callback
|
|
172
|
+
sensorProfile.onPowerChanged = on_power_changed
|
|
173
|
+
sensorProfile.onDataCallback = on_data_callback
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 12. Connect device
|
|
177
|
+
|
|
178
|
+
Use `def connect() -> bool` to connect.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
success = sensorProfile.connect()
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 13. Disconnect
|
|
185
|
+
|
|
186
|
+
Use `def disconnect() -> bool` to disconnect.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
success = sensorProfile.disconnect()
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 14. Get device status
|
|
193
|
+
|
|
194
|
+
Use `property deviceState: DeviceStateEx` to get device status.
|
|
195
|
+
|
|
196
|
+
Please send command in 'Ready' state, should be after connect() return True.
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
deviceStateEx = sensorProfile.deviceState
|
|
200
|
+
|
|
201
|
+
# deviceStateEx has define:
|
|
202
|
+
# class DeviceStateEx(Enum):
|
|
203
|
+
# Disconnected = 0
|
|
204
|
+
# Connecting = 1
|
|
205
|
+
# Connected = 2
|
|
206
|
+
# Ready = 3
|
|
207
|
+
# Disconnecting = 4
|
|
208
|
+
# Invalid = 5
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 15. Get BLE device of SensorProfile
|
|
212
|
+
|
|
213
|
+
Use `property BLEDevice: BLEDevice` to get BLE device of SensorProfile.
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
bleDevice = sensorProfile.BLEDevice
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 16. Get device info of SensorProfile
|
|
220
|
+
|
|
221
|
+
Use `def getDeviceInfo() -> dict | None` to get device info of SensorProfile.
|
|
222
|
+
|
|
223
|
+
Please call after device in 'Ready' state, return None if it's not connected.
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
deviceInfo = sensorProfile.getDeviceInfo()
|
|
227
|
+
|
|
228
|
+
# deviceInfo has defines:
|
|
229
|
+
# deviceInfo = {
|
|
230
|
+
# "deviceName": str,
|
|
231
|
+
# "modelName": str,
|
|
232
|
+
# "hardwareVersion": str,
|
|
233
|
+
# "firmwareVersion": str,
|
|
234
|
+
# "emgChannelCount": int,
|
|
235
|
+
# "eegChannelCount": int,
|
|
236
|
+
# "ecgChannelCount": int,
|
|
237
|
+
# "accChannelCount": int,
|
|
238
|
+
# "gyroChannelCount": int,
|
|
239
|
+
# "brthChannelCount": int,
|
|
240
|
+
# "mtuSize": int
|
|
241
|
+
# }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 17. Init data transfer
|
|
245
|
+
|
|
246
|
+
Use `def init(packageSampleCount: int, powerRefreshInterval: int) -> bool`.
|
|
247
|
+
|
|
248
|
+
Please call after device in 'Ready' state, return True if init succeed.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
success = sensorProfile.init(5, 60*1000)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
packageSampleCount: set sample counts of SensorData.channelSamples in onDataCallback()
|
|
255
|
+
powerRefreshInterval: callback period for onPowerChanged()
|
|
256
|
+
|
|
257
|
+
### 18. Check if init data transfer succeed
|
|
258
|
+
|
|
259
|
+
Use `property hasInited: bool` to check if init data transfer succeed.
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
hasInited = sensorProfile.hasInited
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 19. DataNotify
|
|
266
|
+
|
|
267
|
+
Use `def startDataNotification() -> bool` to start data notification.
|
|
268
|
+
|
|
269
|
+
Please call if hasInited return True
|
|
270
|
+
|
|
271
|
+
#### 19.1 Start data transfer
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
success = sensorProfile.startDataNotification()
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Data type list:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
class DataType(Enum):
|
|
281
|
+
NTF_ACC = 0x1 # unit is g
|
|
282
|
+
NTF_GYRO = 0x2 # unit is degree/s
|
|
283
|
+
NTF_EEG = 0x10 # unit is uV
|
|
284
|
+
NTF_ECG = 0x11 # unit is uV
|
|
285
|
+
NTF_BRTH = 0x15 # unit is uV
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Process data in onDataCallback.
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
def on_data_callback(sensor, data):
|
|
292
|
+
if data.dataType == DataType.NTF_EEG:
|
|
293
|
+
pass
|
|
294
|
+
elif data.dataType == DataType.NTF_ECG:
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
# process data as you wish
|
|
298
|
+
for oneChannelSamples in data.channelSamples:
|
|
299
|
+
for sample in oneChannelSamples:
|
|
300
|
+
if sample.isLost:
|
|
301
|
+
# do some logic
|
|
302
|
+
pass
|
|
303
|
+
else:
|
|
304
|
+
# draw with sample.data & sample.channelIndex
|
|
305
|
+
# print(f"{sample.channelIndex} | {sample.sampleIndex} | {sample.data} | {sample.impedance}")
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
sensorProfile.onDataCallback = on_data_callback
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### 19.2 Stop data transfer
|
|
312
|
+
|
|
313
|
+
Use `def stopDataNotification() -> bool` to stop data transfer.
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
success = sensorProfile.stopDataNotification()
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### 19.3 Check if it's data transfering
|
|
320
|
+
|
|
321
|
+
Use `property isDataTransfering: bool` to check if it's data transfering.
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
isDataTransfering = sensorProfile.isDataTransfering
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 20. Get battery level
|
|
328
|
+
|
|
329
|
+
Use `def getBatteryLevel() -> int` to get battery level. Please call after device in 'Ready' state.
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
batteryPower = sensorProfile.getBatteryLevel()
|
|
333
|
+
|
|
334
|
+
# batteryPower is battery level returned, value ranges from 0 to 100, 0 means out of battery, while 100 means full.
|
|
335
|
+
```
|
|
336
|
+
|
|
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
|
|
344
|
+
|
|
345
|
+
### setParam method
|
|
346
|
+
|
|
347
|
+
Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
|
|
348
|
+
|
|
349
|
+
Below is available key and value:
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
result = sensorProfile.setParam("NTF_EMG", "ON")
|
|
353
|
+
# set EMG data to ON or OFF, result is "OK" if succeed
|
|
354
|
+
|
|
355
|
+
result = sensorProfile.setParam("NTF_EEG", "ON")
|
|
356
|
+
# set EEG data to ON or OFF, result is "OK" if succeed
|
|
357
|
+
|
|
358
|
+
result = sensorProfile.setParam("NTF_ECG", "ON")
|
|
359
|
+
# set ECG data to ON or OFF, result is "OK" if succeed
|
|
360
|
+
|
|
361
|
+
result = sensorProfile.setParam("NTF_IMU", "ON")
|
|
362
|
+
# set IMU data to ON or OFF, result is "OK" if succeed
|
|
363
|
+
|
|
364
|
+
result = sensorProfile.setParam("NTF_BRTH", "ON")
|
|
365
|
+
# set BRTH data to ON or OFF, result is "OK" if succeed
|
|
366
|
+
|
|
367
|
+
result = sensorProfile.setParam("FILTER_50Hz", "ON")
|
|
368
|
+
# set 50Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
369
|
+
|
|
370
|
+
result = sensorProfile.setParam("FILTER_60Hz", "ON")
|
|
371
|
+
# set 60Hz notch filter to ON or OFF, result is "OK" if succeed
|
|
372
|
+
|
|
373
|
+
result = sensorProfile.setParam("FILTER_HPF", "ON")
|
|
374
|
+
# set 0.5Hz hpf filter to ON or OFF, result is "OK" if succeed
|
|
375
|
+
|
|
376
|
+
result = sensorProfile.setParam("FILTER_LPF", "ON")
|
|
377
|
+
# set 80Hz lpf filter to ON or OFF, result is "OK" if succeed
|
|
378
|
+
|
|
379
|
+
result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.csv")
|
|
380
|
+
# set debug ble data path, result is "OK" if succeed
|
|
381
|
+
# please give an absolute path and make sure it is valid and writeable by yourself
|
|
382
|
+
```
|
|
@@ -728,6 +728,17 @@ class GForce:
|
|
|
728
728
|
)
|
|
729
729
|
)
|
|
730
730
|
|
|
731
|
+
async def set_package_id(self, switchStatus):
|
|
732
|
+
body = [switchStatus == True]
|
|
733
|
+
body = bytes(body)
|
|
734
|
+
ret = await self._send_request(
|
|
735
|
+
Request(
|
|
736
|
+
cmd=Command.PACKAGE_ID_CONTROL,
|
|
737
|
+
body=body,
|
|
738
|
+
has_res=True,
|
|
739
|
+
)
|
|
740
|
+
)
|
|
741
|
+
|
|
731
742
|
async def set_log_level(self, logLevel):
|
|
732
743
|
body = [0xFF & logLevel]
|
|
733
744
|
body = bytes(body)
|
|
@@ -853,10 +864,10 @@ class GForce:
|
|
|
853
864
|
|
|
854
865
|
async def stop_streaming(self):
|
|
855
866
|
exceptions = []
|
|
856
|
-
try:
|
|
857
|
-
|
|
858
|
-
except Exception as e:
|
|
859
|
-
|
|
867
|
+
# try:
|
|
868
|
+
# await asyncio.wait_for(self.set_subscription(DataSubscription.OFF), utils._TIMEOUT)
|
|
869
|
+
# except Exception as e:
|
|
870
|
+
# exceptions.append(e)
|
|
860
871
|
try:
|
|
861
872
|
await asyncio.wait_for(self.client.stop_notify(self.data_char), utils._TIMEOUT)
|
|
862
873
|
except Exception as e:
|
|
@@ -38,6 +38,7 @@ class Sample:
|
|
|
38
38
|
class DataType(IntEnum):
|
|
39
39
|
NTF_ACC = 0x1 # 加速度,用于标识加速度传感器采集的数据
|
|
40
40
|
NTF_GYRO = 0x2 # 陀螺仪,用于标识陀螺仪传感器采集的数据
|
|
41
|
+
NTF_EMG = 0x8 # EMG,用于标识肌电传感器采集的数据
|
|
41
42
|
NTF_EEG = 0x10 # EEG,用于标识脑电传感器采集的数据
|
|
42
43
|
NTF_ECG = 0x11 # ECG,用于标识心电传感器采集的数据
|
|
43
44
|
NTF_IMPEDANCE = (0x12,) # 阻抗数据
|
|
@@ -8,7 +8,7 @@ from typing import Deque, List
|
|
|
8
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
9
9
|
import csv
|
|
10
10
|
from sensor import utils
|
|
11
|
-
from sensor.gforce import DataSubscription, GForce
|
|
11
|
+
from sensor.gforce import DataSubscription, GForce, SamplingRate
|
|
12
12
|
from sensor.sensor_data import DataType, Sample, SensorData
|
|
13
13
|
|
|
14
14
|
from enum import Enum, IntEnum
|
|
@@ -22,7 +22,8 @@ class SensorDataType(IntEnum):
|
|
|
22
22
|
DATA_TYPE_ACC = 2
|
|
23
23
|
DATA_TYPE_GYRO = 3
|
|
24
24
|
DATA_TYPE_BRTH = 4
|
|
25
|
-
|
|
25
|
+
DATA_TYPE_EMG = 5
|
|
26
|
+
DATA_TYPE_COUNT = 6
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
# 枚举 FeatureMaps 的 Python 实现
|
|
@@ -114,6 +115,31 @@ class SensorProfileDataCtx:
|
|
|
114
115
|
def hasConcatBLE(self):
|
|
115
116
|
return (self.featureMap & FeatureMaps.GFD_FEAT_CONCAT_BLE.value) != 0
|
|
116
117
|
|
|
118
|
+
async def initEMG(self, packageCount: int) -> int:
|
|
119
|
+
config = await self.gForce.get_emg_raw_data_config()
|
|
120
|
+
data = SensorData()
|
|
121
|
+
data.deviceMac = self.deviceMac
|
|
122
|
+
data.dataType = DataType.NTF_EMG
|
|
123
|
+
data.sampleRate = 500
|
|
124
|
+
data.resolutionBits = 0
|
|
125
|
+
data.channelCount = 8
|
|
126
|
+
data.channelMask = config.channel_mask
|
|
127
|
+
data.minPackageSampleCount = packageCount
|
|
128
|
+
data.packageSampleCount = 8
|
|
129
|
+
data.K = 4000000.0 / 8388607.0
|
|
130
|
+
data.clear()
|
|
131
|
+
self.sensorDatas[SensorDataType.DATA_TYPE_EMG] = data
|
|
132
|
+
self.notifyDataFlag |= DataSubscription.EMG_RAW
|
|
133
|
+
|
|
134
|
+
config.fs = SamplingRate.HZ_500
|
|
135
|
+
config.channel_mask = 255
|
|
136
|
+
config.resolution = 8
|
|
137
|
+
config.batch_len = 128
|
|
138
|
+
await self.gForce.set_emg_raw_data_config(config)
|
|
139
|
+
|
|
140
|
+
await self.gForce.set_package_id(True)
|
|
141
|
+
return data.channelCount
|
|
142
|
+
|
|
117
143
|
async def initEEG(self, packageCount: int) -> int:
|
|
118
144
|
config = await self.gForce.get_eeg_raw_data_config()
|
|
119
145
|
cap = await self.gForce.get_eeg_raw_data_cap()
|
|
@@ -234,23 +260,23 @@ class SensorProfileDataCtx:
|
|
|
234
260
|
if self.hasImpedance():
|
|
235
261
|
self.notifyDataFlag |= DataSubscription.DNF_IMPEDANCE
|
|
236
262
|
|
|
237
|
-
if self.
|
|
238
|
-
|
|
263
|
+
if self.hasEMG() and (self.init_map["NTF_EMG"] == "ON"):
|
|
264
|
+
info.EmgChannelCount = await self.initEMG(packageCount)
|
|
265
|
+
info.EmgSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_EMG].sampleRate
|
|
266
|
+
|
|
267
|
+
if self.hasEEG() and (self.init_map["NTF_EEG"] == "ON"):
|
|
239
268
|
info.EegChannelCount = await self.initEEG(packageCount)
|
|
240
269
|
info.EegSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_EEG].sampleRate
|
|
241
270
|
|
|
242
|
-
if self.hasECG()
|
|
243
|
-
# print("initECG")
|
|
271
|
+
if self.hasECG() and (self.init_map["NTF_ECG"] == "ON"):
|
|
244
272
|
info.EcgChannelCount = await self.initECG(packageCount)
|
|
245
273
|
info.EcgSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_ECG].sampleRate
|
|
246
274
|
|
|
247
|
-
if self.hasBrth()
|
|
248
|
-
# print("initBrth")
|
|
275
|
+
if self.hasBrth() and (self.init_map["NTF_BRTH"] == "ON"):
|
|
249
276
|
info.BrthChannelCount = await self.initBrth(packageCount)
|
|
250
277
|
info.BrthSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_BRTH].sampleRate
|
|
251
278
|
|
|
252
|
-
if self.hasIMU()
|
|
253
|
-
# print("initIMU")
|
|
279
|
+
if self.hasIMU() and (self.init_map["NTF_IMU"] == "ON"):
|
|
254
280
|
imuChannelCount = await self.initIMU(packageCount)
|
|
255
281
|
info.AccChannelCount = imuChannelCount
|
|
256
282
|
info.GyroChannelCount = imuChannelCount
|
|
@@ -274,12 +300,15 @@ class SensorProfileDataCtx:
|
|
|
274
300
|
return True
|
|
275
301
|
self._is_data_transfering = True
|
|
276
302
|
self._rawDataBuffer.queue.clear()
|
|
303
|
+
self._concatDataBuffer.clear()
|
|
304
|
+
self.clear()
|
|
305
|
+
|
|
277
306
|
if not self.isUniversalStream:
|
|
278
307
|
await self.gForce.start_streaming(self._rawDataBuffer)
|
|
279
|
-
return True
|
|
280
308
|
else:
|
|
281
309
|
await self.gForce.set_subscription(self.notifyDataFlag)
|
|
282
|
-
|
|
310
|
+
|
|
311
|
+
return True
|
|
283
312
|
|
|
284
313
|
async def stop_streaming(self) -> bool:
|
|
285
314
|
if not self._is_data_transfering:
|
|
@@ -287,12 +316,21 @@ class SensorProfileDataCtx:
|
|
|
287
316
|
|
|
288
317
|
self._is_data_transfering = False
|
|
289
318
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
319
|
+
try:
|
|
320
|
+
|
|
321
|
+
if not self.isUniversalStream:
|
|
322
|
+
await self.gForce.stop_streaming()
|
|
323
|
+
else:
|
|
324
|
+
await self.gForce.set_subscription(0)
|
|
325
|
+
|
|
326
|
+
while self._is_running and not self._rawDataBuffer.empty():
|
|
327
|
+
await asyncio.sleep(0.1)
|
|
328
|
+
|
|
329
|
+
except Exception as e:
|
|
330
|
+
print(e)
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
return True
|
|
296
334
|
|
|
297
335
|
async def setFilter(self, filter: str, value: str) -> str:
|
|
298
336
|
self.filter_map[filter] = value
|
|
@@ -313,6 +351,7 @@ class SensorProfileDataCtx:
|
|
|
313
351
|
switch |= 8
|
|
314
352
|
try:
|
|
315
353
|
await self.gForce.set_firmware_filter_switch(switch)
|
|
354
|
+
await asyncio.sleep(0.1)
|
|
316
355
|
return "OK"
|
|
317
356
|
except Exception as e:
|
|
318
357
|
return "ERROR: " + str(e)
|
|
@@ -342,25 +381,26 @@ class SensorProfileDataCtx:
|
|
|
342
381
|
except Exception as e:
|
|
343
382
|
continue
|
|
344
383
|
|
|
345
|
-
self.
|
|
384
|
+
if self.isDataTransfering:
|
|
385
|
+
self._processDataPackage(data, buf, sensor)
|
|
346
386
|
self._rawDataBuffer.task_done()
|
|
347
387
|
|
|
348
|
-
|
|
349
|
-
|
|
388
|
+
while self._is_running and self.isDataTransfering and not buf.empty():
|
|
389
|
+
sensorData: SensorData = None
|
|
390
|
+
try:
|
|
391
|
+
sensorData = buf.get_nowait()
|
|
392
|
+
except Exception as e:
|
|
393
|
+
break
|
|
394
|
+
if sensorData != None and callback != None:
|
|
350
395
|
try:
|
|
351
|
-
|
|
396
|
+
asyncio.get_event_loop().run_in_executor(self.dataPool, callback, sensor, sensorData)
|
|
352
397
|
except Exception as e:
|
|
353
|
-
|
|
354
|
-
if sensorData != None and callback != None:
|
|
355
|
-
try:
|
|
356
|
-
asyncio.get_event_loop().run_in_executor(self.dataPool, callback, sensor, sensorData)
|
|
357
|
-
except Exception as e:
|
|
358
|
-
print(e)
|
|
398
|
+
print(e)
|
|
359
399
|
|
|
360
|
-
|
|
400
|
+
buf.task_done()
|
|
361
401
|
|
|
362
402
|
def _processDataPackage(self, data: bytes, buf: Queue[SensorData], sensor):
|
|
363
|
-
v = data[0]
|
|
403
|
+
v = data[0] & 0x7F
|
|
364
404
|
if v == DataType.NTF_IMPEDANCE:
|
|
365
405
|
offset = 1
|
|
366
406
|
# packageIndex = ((data[offset + 1] & 0xff) << 8) | (data[offset] & 0xff)
|
|
@@ -383,7 +423,10 @@ class SensorProfileDataCtx:
|
|
|
383
423
|
|
|
384
424
|
self.impedanceData = impedanceData
|
|
385
425
|
self.saturationData = saturationData
|
|
386
|
-
|
|
426
|
+
elif v == DataType.NTF_EMG:
|
|
427
|
+
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EMG]
|
|
428
|
+
if self.checkReadSamples(sensor, data, sensor_data, 3, 0):
|
|
429
|
+
self.sendSensorData(sensor_data, buf)
|
|
387
430
|
elif v == DataType.NTF_EEG:
|
|
388
431
|
sensor_data = self.sensorDatas[SensorDataType.DATA_TYPE_EEG]
|
|
389
432
|
if self.checkReadSamples(sensor, data, sensor_data, 3, 0):
|
|
@@ -407,7 +450,7 @@ class SensorProfileDataCtx:
|
|
|
407
450
|
|
|
408
451
|
def checkReadSamples(self, sensor, data: bytes, sensorData: SensorData, dataOffset: int, dataGap: int):
|
|
409
452
|
offset = 1
|
|
410
|
-
|
|
453
|
+
|
|
411
454
|
if not self._is_data_transfering:
|
|
412
455
|
return False
|
|
413
456
|
try:
|
|
@@ -439,8 +482,9 @@ class SensorProfileDataCtx:
|
|
|
439
482
|
asyncio.get_event_loop().run_in_executor(None, sensor._on_error_callback, sensor, lostLog)
|
|
440
483
|
except Exception as e:
|
|
441
484
|
pass
|
|
485
|
+
if lostSampleCount < 100:
|
|
486
|
+
self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
|
|
442
487
|
|
|
443
|
-
self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
|
|
444
488
|
if newPackageIndex == 0:
|
|
445
489
|
sensorData.lastPackageIndex = 65535
|
|
446
490
|
else:
|
|
@@ -455,6 +499,12 @@ class SensorProfileDataCtx:
|
|
|
455
499
|
return False
|
|
456
500
|
return True
|
|
457
501
|
|
|
502
|
+
def transTrainData(self, data: int):
|
|
503
|
+
xout = data >> 4
|
|
504
|
+
exp = data & 0x0000000F
|
|
505
|
+
xout = xout << exp
|
|
506
|
+
return xout
|
|
507
|
+
|
|
458
508
|
def readSamples(
|
|
459
509
|
self,
|
|
460
510
|
data: bytes,
|
|
@@ -522,6 +572,10 @@ class SensorProfileDataCtx:
|
|
|
522
572
|
rawData = (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]
|
|
523
573
|
rawData -= 8388608
|
|
524
574
|
offset += 3
|
|
575
|
+
elif sensorData.resolutionBits == 0:
|
|
576
|
+
rawData = struct.unpack_from("<h", data, offset)[0]
|
|
577
|
+
offset += 2
|
|
578
|
+
rawData = self.transTrainData(rawData)
|
|
525
579
|
|
|
526
580
|
converted = rawData * K
|
|
527
581
|
dataItem.rawData = rawData
|
|
@@ -61,6 +61,8 @@ class SensorProfile:
|
|
|
61
61
|
self._gforce: GForce = None
|
|
62
62
|
self._data_event_loop: asyncio.AbstractEventLoop = None
|
|
63
63
|
self._event_loop: asyncio.AbstractEventLoop = None
|
|
64
|
+
self._is_starting = False
|
|
65
|
+
self._is_setting_param = False
|
|
64
66
|
|
|
65
67
|
def __del__(self) -> None:
|
|
66
68
|
"""
|
|
@@ -239,6 +241,8 @@ class SensorProfile:
|
|
|
239
241
|
self._data_ctx = SensorProfileDataCtx(self._gforce, self._device.Address, self._raw_data_buf)
|
|
240
242
|
if self._data_ctx.isUniversalStream:
|
|
241
243
|
async_exec(self._process_universal_data())
|
|
244
|
+
else:
|
|
245
|
+
async_exec(self._process_data())
|
|
242
246
|
|
|
243
247
|
if self.deviceState == DeviceStateEx.Connected or self.deviceState == DeviceStateEx.Ready:
|
|
244
248
|
return True
|
|
@@ -288,7 +292,7 @@ class SensorProfile:
|
|
|
288
292
|
return await async_call(self._connect())
|
|
289
293
|
|
|
290
294
|
async def _waitForDisconnect(self) -> bool:
|
|
291
|
-
while self.deviceState != DeviceStateEx.Disconnected:
|
|
295
|
+
while not utils._terminated and self.deviceState != DeviceStateEx.Disconnected:
|
|
292
296
|
await asyncio.sleep(1)
|
|
293
297
|
return True
|
|
294
298
|
|
|
@@ -341,11 +345,13 @@ class SensorProfile:
|
|
|
341
345
|
if self._data_event_loop == None:
|
|
342
346
|
self._data_event_loop = asyncio.new_event_loop()
|
|
343
347
|
|
|
344
|
-
|
|
348
|
+
self._raw_data_buf.queue.clear()
|
|
345
349
|
self._data_buffer.queue.clear()
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
350
|
+
|
|
351
|
+
result = await self._data_ctx.start_streaming()
|
|
352
|
+
await asyncio.sleep(0.2)
|
|
353
|
+
|
|
354
|
+
self._is_starting = False
|
|
349
355
|
return result
|
|
350
356
|
|
|
351
357
|
def startDataNotification(self) -> bool:
|
|
@@ -355,6 +361,10 @@ class SensorProfile:
|
|
|
355
361
|
:return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
|
|
356
362
|
|
|
357
363
|
"""
|
|
364
|
+
if self._is_starting:
|
|
365
|
+
return False
|
|
366
|
+
|
|
367
|
+
self._is_starting = True
|
|
358
368
|
return sync_call(self._startDataNotification())
|
|
359
369
|
|
|
360
370
|
async def asyncStartDataNotification(self) -> bool:
|
|
@@ -364,6 +374,10 @@ class SensorProfile:
|
|
|
364
374
|
:return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
|
|
365
375
|
|
|
366
376
|
"""
|
|
377
|
+
if self._is_starting:
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
self._is_starting = True
|
|
367
381
|
return await async_call(self._startDataNotification())
|
|
368
382
|
|
|
369
383
|
async def _stopDataNotification(self) -> bool:
|
|
@@ -377,7 +391,9 @@ class SensorProfile:
|
|
|
377
391
|
if not self._data_ctx.isDataTransfering:
|
|
378
392
|
return True
|
|
379
393
|
|
|
380
|
-
|
|
394
|
+
result = await self._data_ctx.stop_streaming()
|
|
395
|
+
self._is_starting = False
|
|
396
|
+
return not result
|
|
381
397
|
|
|
382
398
|
def stopDataNotification(self) -> bool:
|
|
383
399
|
"""
|
|
@@ -386,6 +402,10 @@ class SensorProfile:
|
|
|
386
402
|
:return: bool: 如果停止数据通知成功,返回 True;否则返回 False。
|
|
387
403
|
|
|
388
404
|
"""
|
|
405
|
+
if self._is_starting:
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
self._is_starting = True
|
|
389
409
|
return sync_call(self._stopDataNotification())
|
|
390
410
|
|
|
391
411
|
async def asyncStopDataNotification(self) -> bool:
|
|
@@ -395,6 +415,10 @@ class SensorProfile:
|
|
|
395
415
|
:return: bool: 如果停止数据通知成功,返回 True;否则返回 False。
|
|
396
416
|
|
|
397
417
|
"""
|
|
418
|
+
if self._is_starting:
|
|
419
|
+
return False
|
|
420
|
+
|
|
421
|
+
self._is_starting = True
|
|
398
422
|
return await async_call(self._stopDataNotification())
|
|
399
423
|
|
|
400
424
|
async def _refresh_power(self):
|
|
@@ -474,21 +498,35 @@ class SensorProfile:
|
|
|
474
498
|
return None
|
|
475
499
|
|
|
476
500
|
async def _setParam(self, key: str, value: str) -> str:
|
|
501
|
+
result = "Error: Not supported"
|
|
477
502
|
if self.deviceState != DeviceStateEx.Ready:
|
|
478
|
-
|
|
503
|
+
result = "Error: Please connect first"
|
|
479
504
|
|
|
480
505
|
if key in ["NTF_EMG", "NTF_EEG", "NTF_ECG", "NTF_IMU", "NTF_BRTH"]:
|
|
481
506
|
if value in ["ON", "OFF"]:
|
|
482
507
|
self._data_ctx.init_map[key] = value
|
|
483
|
-
|
|
508
|
+
result = "OK"
|
|
484
509
|
|
|
485
510
|
if key in ["FILTER_50Hz", "FILTER_60Hz", "FILTER_HPF", "FILTER_LPF"]:
|
|
486
511
|
if value in ["ON", "OFF"]:
|
|
487
|
-
|
|
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
|
+
result = await self._data_ctx.setFilter(key, value)
|
|
521
|
+
if needPauseTransfer:
|
|
522
|
+
self._is_starting = True
|
|
523
|
+
await self._startDataNotification()
|
|
488
524
|
|
|
489
525
|
if key == "DEBUG_BLE_DATA_PATH":
|
|
490
|
-
|
|
491
|
-
|
|
526
|
+
result = await self._data_ctx.setDebugCSV(value)
|
|
527
|
+
|
|
528
|
+
self._is_setting_param = False
|
|
529
|
+
return result
|
|
492
530
|
|
|
493
531
|
def setParam(self, key: str, value: str) -> str:
|
|
494
532
|
"""
|
|
@@ -500,6 +538,10 @@ class SensorProfile:
|
|
|
500
538
|
:return: str: 设置参数的结果。
|
|
501
539
|
|
|
502
540
|
"""
|
|
541
|
+
if self._is_setting_param:
|
|
542
|
+
return "Error: Please wait for the previous operation to complete"
|
|
543
|
+
|
|
544
|
+
self._is_setting_param = True
|
|
503
545
|
return sync_call(
|
|
504
546
|
self._setParam(key, value),
|
|
505
547
|
20,
|
|
@@ -515,6 +557,10 @@ class SensorProfile:
|
|
|
515
557
|
:return: str: 设置参数的结果。
|
|
516
558
|
|
|
517
559
|
"""
|
|
560
|
+
if self._is_setting_param:
|
|
561
|
+
return "Error: Please wait for the previous operation to complete"
|
|
562
|
+
|
|
563
|
+
self._is_setting_param = True
|
|
518
564
|
return await async_call(
|
|
519
565
|
self._setParam(key, value),
|
|
520
566
|
20,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sensor-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.16
|
|
4
4
|
Summary: Python sdk for Synchroni
|
|
5
5
|
Home-page: https://github.com/oymotion/SynchroniSDKPython
|
|
6
6
|
Author: Martin Ye
|
|
7
7
|
Author-email: yecq_82@hotmail.com
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9.0
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.txt
|
|
11
11
|
|
|
@@ -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.16",
|
|
12
12
|
description="Python sdk for Synchroni",
|
|
13
13
|
long_description=long_description,
|
|
14
14
|
long_description_content_type="text/markdown",
|
|
@@ -19,5 +19,5 @@ setup(
|
|
|
19
19
|
install_requires=["numpy", "setuptools", "bleak"],
|
|
20
20
|
package_data={"sensor": []},
|
|
21
21
|
zip_safe=True,
|
|
22
|
-
python_requires=">=3.
|
|
22
|
+
python_requires=">=3.9.0",
|
|
23
23
|
)
|
|
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
|
|
File without changes
|