sensor-sdk 0.0.71__tar.gz → 0.1.1__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.
Files changed (22) hide show
  1. {sensor_sdk-0.0.71/sensor_sdk.egg-info → sensor_sdk-0.1.1}/PKG-INFO +41 -11
  2. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/README.md +40 -10
  3. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/bleak_host.py +30 -25
  4. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/bleak_process.py +1228 -1088
  5. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/gforce.py +1087 -1077
  6. sensor_sdk-0.1.1/sensor/sdk_log.py +259 -0
  7. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/sensor_controller.py +40 -14
  8. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/sensor_data.py +103 -103
  9. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/sensor_data_context.py +1634 -1488
  10. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/sensor_device.py +4 -0
  11. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/sensor_profile.py +630 -540
  12. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/sensor_utils.py +908 -906
  13. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1/sensor_sdk.egg-info}/PKG-INFO +41 -11
  14. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor_sdk.egg-info/SOURCES.txt +1 -0
  15. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/setup.py +1 -1
  16. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/LICENSE.txt +0 -0
  17. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor/__init__.py +0 -0
  18. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor_sdk.egg-info/dependency_links.txt +0 -0
  19. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor_sdk.egg-info/requires.txt +0 -0
  20. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor_sdk.egg-info/top_level.txt +0 -0
  21. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/sensor_sdk.egg-info/zip-safe +0 -0
  22. {sensor_sdk-0.0.71 → sensor_sdk-0.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sensor-sdk
3
- Version: 0.0.71
3
+ Version: 0.1.1
4
4
  Summary: Python sdk for Synchroni
5
5
  Home-page: https://github.com/oymotion/SynchroniSDKPython
6
6
  Author: Martin Ye
@@ -208,6 +208,8 @@ success = sensorProfile.connect()
208
208
 
209
209
  Use `def disconnect() -> bool` to disconnect.
210
210
 
211
+ If data notification is currently active, `disconnect()` will automatically stop it first before closing the BLE connection.
212
+
211
213
  ```python
212
214
  success = sensorProfile.disconnect()
213
215
  ```
@@ -369,24 +371,31 @@ Please check async_console.py in examples directory
369
371
 
370
372
  Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
371
373
 
374
+ The asynchronous variant is `asyncSetParam(self, key: str, value: str) -> str`.
375
+
376
+ If the device is already streaming when you change an `NTF_*` or `FILTER_*` key, the SDK will stop and restart the data notification so the new setting takes effect immediately.
377
+
372
378
  Below is available key and value:
373
379
 
374
380
  ```python
381
+ # Data stream toggles
382
+ result = sensorProfile.setParam("NTF_GEST", "ON")
375
383
  result = sensorProfile.setParam("NTF_EMG", "ON")
376
- # set EMG data to ON or OFF, result is "OK" if succeed
377
-
378
384
  result = sensorProfile.setParam("NTF_EEG", "ON")
379
- # set EEG data to ON or OFF, result is "OK" if succeed
380
-
381
385
  result = sensorProfile.setParam("NTF_ECG", "ON")
382
- # set ECG data to ON or OFF, result is "OK" if succeed
383
-
384
386
  result = sensorProfile.setParam("NTF_IMU", "ON")
385
- # set IMU data to ON or OFF, result is "OK" if succeed
386
-
387
387
  result = sensorProfile.setParam("NTF_BRTH", "ON")
388
- # set BRTH data to ON or OFF, result is "OK" if succeed
389
-
388
+ result = sensorProfile.setParam("NTF_IMPEDANCE", "ON")
389
+ result = sensorProfile.setParam("NTF_MAG_ANGLE", "ON")
390
+ result = sensorProfile.setParam("NTF_PPG_RAW", "ON")
391
+ result = sensorProfile.setParam("NTF_GFORCE_EULER", "ON")
392
+ result = sensorProfile.setParam("NTF_GFORCE_QUAT", "ON")
393
+ result = sensorProfile.setParam("NTF_GFORCE_ACC", "ON")
394
+ result = sensorProfile.setParam("NTF_GFORCE_GYRO", "ON")
395
+ # set data stream to ON or OFF, result is "OK" if succeed
396
+ # Note: on legacy (non-new) EMG devices, NTF_GEST and NTF_EMG are mutually exclusive.
397
+
398
+ # Firmware filter toggles
390
399
  result = sensorProfile.setParam("FILTER_50HZ", "ON")
391
400
  # set 50Hz notch filter to ON or OFF, result is "OK" if succeed
392
401
 
@@ -403,3 +412,24 @@ result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.csv")
403
412
  # set debug ble data path, result is "OK" if succeed
404
413
  # please give an absolute path and make sure it is valid and writeable by yourself
405
414
  ```
415
+
416
+
417
+ ### getParam method
418
+
419
+ Use `def getParam(self, key: str) -> str` to query the current parameter state of a sensor profile. Please call after the device reaches the 'Ready' state.
420
+
421
+ The asynchronous variant is `asyncGetParam(self, key: str) -> str`.
422
+
423
+ Supported aggregate query keys:
424
+
425
+ ```python
426
+ result = sensorProfile.getParam("FILTER")
427
+ # Returns a pipe-separated string of all filter states, e.g.:
428
+ # "FILTER_50HZ|ON|FILTER_60HZ|ON|FILTER_HPF|ON|FILTER_LPF|ON"
429
+
430
+ result = sensorProfile.getParam("NTF")
431
+ # Returns a pipe-separated string of all notification states, e.g.:
432
+ # "NTF_BRTH|ON|NTF_ECG|ON|NTF_EEG|ON|NTF_EMG|ON|..."
433
+ ```
434
+
435
+ If the key is not supported, the result starts with `"Error"`.
@@ -185,6 +185,8 @@ success = sensorProfile.connect()
185
185
 
186
186
  Use `def disconnect() -> bool` to disconnect.
187
187
 
188
+ If data notification is currently active, `disconnect()` will automatically stop it first before closing the BLE connection.
189
+
188
190
  ```python
189
191
  success = sensorProfile.disconnect()
190
192
  ```
@@ -346,24 +348,31 @@ Please check async_console.py in examples directory
346
348
 
347
349
  Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
348
350
 
351
+ The asynchronous variant is `asyncSetParam(self, key: str, value: str) -> str`.
352
+
353
+ If the device is already streaming when you change an `NTF_*` or `FILTER_*` key, the SDK will stop and restart the data notification so the new setting takes effect immediately.
354
+
349
355
  Below is available key and value:
350
356
 
351
357
  ```python
358
+ # Data stream toggles
359
+ result = sensorProfile.setParam("NTF_GEST", "ON")
352
360
  result = sensorProfile.setParam("NTF_EMG", "ON")
353
- # set EMG data to ON or OFF, result is "OK" if succeed
354
-
355
361
  result = sensorProfile.setParam("NTF_EEG", "ON")
356
- # set EEG data to ON or OFF, result is "OK" if succeed
357
-
358
362
  result = sensorProfile.setParam("NTF_ECG", "ON")
359
- # set ECG data to ON or OFF, result is "OK" if succeed
360
-
361
363
  result = sensorProfile.setParam("NTF_IMU", "ON")
362
- # set IMU data to ON or OFF, result is "OK" if succeed
363
-
364
364
  result = sensorProfile.setParam("NTF_BRTH", "ON")
365
- # set BRTH data to ON or OFF, result is "OK" if succeed
366
-
365
+ result = sensorProfile.setParam("NTF_IMPEDANCE", "ON")
366
+ result = sensorProfile.setParam("NTF_MAG_ANGLE", "ON")
367
+ result = sensorProfile.setParam("NTF_PPG_RAW", "ON")
368
+ result = sensorProfile.setParam("NTF_GFORCE_EULER", "ON")
369
+ result = sensorProfile.setParam("NTF_GFORCE_QUAT", "ON")
370
+ result = sensorProfile.setParam("NTF_GFORCE_ACC", "ON")
371
+ result = sensorProfile.setParam("NTF_GFORCE_GYRO", "ON")
372
+ # set data stream to ON or OFF, result is "OK" if succeed
373
+ # Note: on legacy (non-new) EMG devices, NTF_GEST and NTF_EMG are mutually exclusive.
374
+
375
+ # Firmware filter toggles
367
376
  result = sensorProfile.setParam("FILTER_50HZ", "ON")
368
377
  # set 50Hz notch filter to ON or OFF, result is "OK" if succeed
369
378
 
@@ -380,3 +389,24 @@ result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.csv")
380
389
  # set debug ble data path, result is "OK" if succeed
381
390
  # please give an absolute path and make sure it is valid and writeable by yourself
382
391
  ```
392
+
393
+
394
+ ### getParam method
395
+
396
+ Use `def getParam(self, key: str) -> str` to query the current parameter state of a sensor profile. Please call after the device reaches the 'Ready' state.
397
+
398
+ The asynchronous variant is `asyncGetParam(self, key: str) -> str`.
399
+
400
+ Supported aggregate query keys:
401
+
402
+ ```python
403
+ result = sensorProfile.getParam("FILTER")
404
+ # Returns a pipe-separated string of all filter states, e.g.:
405
+ # "FILTER_50HZ|ON|FILTER_60HZ|ON|FILTER_HPF|ON|FILTER_LPF|ON"
406
+
407
+ result = sensorProfile.getParam("NTF")
408
+ # Returns a pipe-separated string of all notification states, e.g.:
409
+ # "NTF_BRTH|ON|NTF_ECG|ON|NTF_EEG|ON|NTF_EMG|ON|..."
410
+ ```
411
+
412
+ If the key is not supported, the result starts with `"Error"`.
@@ -6,6 +6,9 @@ import uuid
6
6
 
7
7
  from sensor.bleak_process import BleakProcess
8
8
  from sensor import sensor_utils
9
+ from sensor.sdk_log import SdkLog
10
+
11
+ _TAG = "BleakHost"
9
12
 
10
13
 
11
14
  class BleakHost:
@@ -14,7 +17,7 @@ class BleakHost:
14
17
  def __init__(self):
15
18
  self._cmd_queue = multiprocessing.Queue(maxsize=50)
16
19
  self._result_queue = multiprocessing.Queue(maxsize=sensor_utils.BLEAK_RESULT_QUEUE_MAXSIZE)
17
- self._bleak_process = BleakProcess(self._cmd_queue, self._result_queue)
20
+ self._bleak_process = BleakProcess(self._cmd_queue, self._result_queue, SdkLog.get_log_path())
18
21
  self._started = False
19
22
 
20
23
 
@@ -51,8 +54,7 @@ class BleakHost:
51
54
  try:
52
55
  self._cmd_queue.put({"type": "terminate"})
53
56
  except Exception as e:
54
- import traceback
55
- traceback.print_exc()
57
+ SdkLog.exception(_TAG, "Unexpected error")
56
58
 
57
59
  if self._bleak_process.is_alive():
58
60
  self._bleak_process.join(timeout=5)
@@ -84,14 +86,12 @@ class BleakHost:
84
86
  try:
85
87
  loop.call_soon_threadsafe(loop.stop)
86
88
  except Exception as e:
87
- import traceback
88
- traceback.print_exc()
89
+ SdkLog.exception(_TAG, "Unexpected error")
89
90
  if thread is not None and thread.is_alive():
90
91
  try:
91
92
  thread.join(timeout=2)
92
93
  except Exception as e:
93
- import traceback
94
- traceback.print_exc()
94
+ SdkLog.exception(_TAG, "Unexpected error")
95
95
  self._result_loop = None
96
96
  self._result_thread = None
97
97
 
@@ -113,15 +113,13 @@ class BleakHost:
113
113
  try:
114
114
  self.on_scan_once_result(msg)
115
115
  except Exception as e:
116
- import traceback
117
- traceback.print_exc()
116
+ SdkLog.exception(_TAG, "Unexpected error")
118
117
  elif msg_type == "devices":
119
118
  if self.on_scan_result is not None:
120
119
  try:
121
120
  self.on_scan_result(msg)
122
121
  except Exception as e:
123
- import traceback
124
- traceback.print_exc()
122
+ SdkLog.exception(_TAG, "Unexpected error")
125
123
  elif msg_type == "command_result":
126
124
  self._handle_command_result(msg)
127
125
  elif msg_type in ("state_changed", "power_changed", "sensor_data", "error"):
@@ -130,8 +128,7 @@ class BleakHost:
130
128
  try:
131
129
  self.on_device_message(device_mac, msg)
132
130
  except Exception as e:
133
- import traceback
134
- traceback.print_exc()
131
+ SdkLog.exception(_TAG, "Unexpected error")
135
132
 
136
133
  def _handle_command_result(self, msg):
137
134
 
@@ -149,6 +146,15 @@ class BleakHost:
149
146
  if loop is not None and not loop.is_closed() and not waiter.done():
150
147
  loop.call_soon_threadsafe(waiter.set_result, msg)
151
148
 
149
+ # 将 connect 返回的 chip_type 转发给对应 SensorProfile
150
+ chip_type = msg.get("chip_type")
151
+ device_mac = msg.get("device_mac")
152
+ if chip_type is not None and self.on_device_message is not None and device_mac is not None:
153
+ try:
154
+ self.on_device_message(device_mac, {"type": "chip_type", "chip_type": chip_type})
155
+ except Exception as e:
156
+ SdkLog.exception(_TAG, "Error forwarding chip_type")
157
+
152
158
  # ------------------------------------------------------------------
153
159
  # Command loop
154
160
  # ------------------------------------------------------------------
@@ -169,14 +175,12 @@ class BleakHost:
169
175
  try:
170
176
  loop.call_soon_threadsafe(loop.stop)
171
177
  except Exception as e:
172
- import traceback
173
- traceback.print_exc()
178
+ SdkLog.exception(_TAG, "Unexpected error")
174
179
  if thread is not None and thread.is_alive():
175
180
  try:
176
181
  thread.join(timeout=2)
177
182
  except Exception as e:
178
- import traceback
179
- traceback.print_exc()
183
+ SdkLog.exception(_TAG, "Unexpected error")
180
184
  self._cmd_loop = None
181
185
  self._cmd_thread = None
182
186
 
@@ -192,32 +196,28 @@ class BleakHost:
192
196
  try:
193
197
  self._cmd_queue.put_nowait({"type": "scan_once", "period": period})
194
198
  except queue.Full as e:
195
- import traceback
196
- traceback.print_exc()
199
+ SdkLog.exception(_TAG, "Unexpected error")
197
200
 
198
201
  def start_scan(self, period):
199
202
 
200
203
  try:
201
204
  self._cmd_queue.put_nowait({"type": "start_scan", "period": period})
202
205
  except queue.Full as e:
203
- import traceback
204
- traceback.print_exc()
206
+ SdkLog.exception(_TAG, "Unexpected error")
205
207
 
206
208
  def stop_scan(self):
207
209
 
208
210
  try:
209
211
  self._cmd_queue.put_nowait({"type": "stop_scan"})
210
212
  except queue.Full as e:
211
- import traceback
212
- traceback.print_exc()
213
+ SdkLog.exception(_TAG, "Unexpected error")
213
214
 
214
215
  def send_command(self, cmd: dict):
215
216
 
216
217
  try:
217
218
  self._cmd_queue.put_nowait(cmd)
218
219
  except queue.Full as e:
219
- import traceback
220
- traceback.print_exc()
220
+ SdkLog.exception(_TAG, "Command queue full")
221
221
 
222
222
  def send_command_sync(self, cmd: dict, timeout: float = 10.0) -> dict:
223
223
 
@@ -235,12 +235,14 @@ class BleakHost:
235
235
  try:
236
236
  self._cmd_queue.put(cmd, timeout=1.0)
237
237
  except queue.Full:
238
+ SdkLog.e(_TAG, f"send_command_sync queue full: {cmd.get('type')}")
238
239
  with self._pending_lock:
239
240
  self._pending.pop(cmd_id, None)
240
241
  return {}
241
242
  try:
242
243
  await asyncio.wait_for(event.wait(), timeout=timeout)
243
244
  except asyncio.TimeoutError:
245
+ SdkLog.e(_TAG, f"send_command_sync timeout: {cmd.get('type')}")
244
246
  with self._pending_lock:
245
247
  self._pending.pop(cmd_id, None)
246
248
  return {}
@@ -252,6 +254,7 @@ class BleakHost:
252
254
  try:
253
255
  return future.result(timeout=timeout)
254
256
  except Exception:
257
+ SdkLog.exception(_TAG, "send_command_sync future failed")
255
258
  return {}
256
259
 
257
260
  async def send_command_async(self, cmd: dict, timeout: float = 10.0) -> dict:
@@ -269,12 +272,14 @@ class BleakHost:
269
272
  try:
270
273
  self._cmd_queue.put(cmd, timeout=1.0)
271
274
  except queue.Full:
275
+ SdkLog.e(_TAG, f"send_command_async queue full: {cmd.get('type')}")
272
276
  with self._pending_lock:
273
277
  self._pending.pop(cmd_id, None)
274
278
  return {}
275
279
  try:
276
280
  return await asyncio.wait_for(future, timeout=timeout)
277
281
  except asyncio.TimeoutError:
282
+ SdkLog.e(_TAG, f"send_command_async timeout: {cmd.get('type')}")
278
283
  with self._pending_lock:
279
284
  self._pending.pop(cmd_id, None)
280
285
  return {}