sensor-sdk 0.2.0__tar.gz → 0.2.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 (29) hide show
  1. {sensor_sdk-0.2.0/sensor_sdk.egg-info → sensor_sdk-0.2.1}/PKG-INFO +1 -1
  2. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/bleak_no_ack_patch.py +6 -3
  3. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/bleak_process.py +114 -10
  4. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/gforce.py +5 -0
  5. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sensor_data_context.py +94 -4
  6. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1/sensor_sdk.egg-info}/PKG-INFO +1 -1
  7. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/setup.py +1 -1
  8. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/LICENSE.txt +0 -0
  9. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/README.md +0 -0
  10. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/__init__.py +0 -0
  11. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/bleak_host.py +0 -0
  12. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/fb/DataType.py +0 -0
  13. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/fb/Sample.py +0 -0
  14. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/fb/SensorData.py +0 -0
  15. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/fb/__init__.py +0 -0
  16. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sdk_log.py +0 -0
  17. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sensor_controller.py +0 -0
  18. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sensor_data.py +0 -0
  19. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sensor_data_pool.py +0 -0
  20. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sensor_device.py +0 -0
  21. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sensor_profile.py +0 -0
  22. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/sensor_utils.py +0 -0
  23. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor/winrt_high_throughput.py +0 -0
  24. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor_sdk.egg-info/SOURCES.txt +0 -0
  25. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor_sdk.egg-info/dependency_links.txt +0 -0
  26. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor_sdk.egg-info/requires.txt +0 -0
  27. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor_sdk.egg-info/top_level.txt +0 -0
  28. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/sensor_sdk.egg-info/zip-safe +0 -0
  29. {sensor_sdk-0.2.0 → sensor_sdk-0.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sensor-sdk
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Python sdk for Synchroni
5
5
  Home-page: https://github.com/oymotion/SynchroniSDKPython
6
6
  Author: Martin Ye
@@ -3,7 +3,7 @@
3
3
  某些 Windows 主机上 write-with-response 会因为系统 ACK 等待导致命令超时,
4
4
  把命令特征强制改成无响应写入可以显著提升命令通道的实时性。
5
5
 
6
- 默认只作用于 SDK 已知的两个 CMD 特征 UUID;传入空列表或 `None` 则全局生效。
6
+ 默认只作用于 SDK 已知的两个 CMD 特征 UUID;传 `None` 则全局生效,空列表表示不匹配任何特征。
7
7
  """
8
8
 
9
9
  import logging
@@ -53,6 +53,7 @@ def apply(cmd_char_uuids=DEFAULT_CMD_CHAR_UUIDS):
53
53
  return await orig_write(self, char_specifier, data, response=response)
54
54
 
55
55
  BleakClient.write_gatt_char = patched_write
56
+ BleakClient._orig_write_gatt_char = orig_write # type: ignore[attr-defined]
56
57
  BleakClient._no_ack_patched = True # type: ignore[attr-defined]
57
58
  logger.debug(
58
59
  "Applied bleak write-without-response patch (targets: %s)",
@@ -70,6 +71,8 @@ def reset():
70
71
  if not getattr(BleakClient, "_no_ack_patched", False):
71
72
  return
72
73
 
73
- # 找到原始的未绑定方法比较麻烦,这里简单把标记清掉,
74
- # 实际撤销需要保存原始方法;apply 没有保存,故 reset 仅作占位。
74
+ orig_write = getattr(BleakClient, "_orig_write_gatt_char", None)
75
+ if orig_write is not None:
76
+ BleakClient.write_gatt_char = orig_write
77
+ del BleakClient._orig_write_gatt_char # type: ignore[attr-defined]
75
78
  BleakClient._no_ack_patched = False # type: ignore[attr-defined]
@@ -25,6 +25,10 @@ OYM_DATA_NOTIFY_CHAR_UUID = "f000ffe2-0451-4000-b000-000000000000"
25
25
  RFSTAR_CMD_UUID = "00000002-0000-1000-8000-00805f9b34fb"
26
26
  RFSTAR_DATA_UUID = "00000003-0000-1000-8000-00805f9b34fb"
27
27
 
28
+ # 异常断开后自动重连参数
29
+ _MAX_RECONNECT_ATTEMPTS = 5
30
+ _RECONNECT_DELAY_SECONDS = 1.0
31
+
28
32
 
29
33
  def _extract_mac(_device: bleak.BLEDevice, adv: AdvertisementData) -> str:
30
34
 
@@ -93,6 +97,8 @@ class BleakProcess(multiprocessing.Process):
93
97
  self._power_intervals: dict = {} # mac -> int (ms)
94
98
  self._data_tasks: dict = {} # mac -> asyncio.Task
95
99
  self._battery_tasks: dict = {} # mac -> asyncio.Task
100
+ self._reconnect_info: dict = {} # mac -> {cmd, attempts, task, normal_disconnect}
101
+ self._main_event_loop = None # BleakProcess 主事件循环
96
102
 
97
103
  # Per-device event loops and threads
98
104
  self._event_loops: dict = {} # mac -> asyncio.AbstractEventLoop
@@ -344,6 +350,7 @@ class BleakProcess(multiprocessing.Process):
344
350
  self._msg_queue = queue.Queue()
345
351
 
346
352
  async def _run_main():
353
+ self._main_event_loop = asyncio.get_running_loop()
347
354
  publisher_task = asyncio.create_task(self._publisher_task())
348
355
  try:
349
356
  await self._main_loop()
@@ -581,6 +588,14 @@ class BleakProcess(multiprocessing.Process):
581
588
  name = cmd.get("name", "")
582
589
  service_data = cmd.get("service_data", {})
583
590
 
591
+ # 保存连接信息,用于异常断开后自动重连
592
+ self._reconnect_info[device_mac] = {
593
+ "cmd": cmd,
594
+ "attempts": 0,
595
+ "task": None,
596
+ "normal_disconnect": False,
597
+ }
598
+
584
599
  event_loop = self._event_loops.get(device_mac)
585
600
  gforce_event_loop = self._gforce_event_loop
586
601
  data_event_loop = self._data_event_loops.get(device_mac)
@@ -676,8 +691,18 @@ class BleakProcess(multiprocessing.Process):
676
691
  self._raw_bufs[device_mac] = raw_buf
677
692
  self._device_states[device_mac] = "Connected"
678
693
 
694
+ # 重连成功,重置重连计数
695
+ reconnect_info = self._reconnect_info.get(device_mac)
696
+ if reconnect_info is not None:
697
+ reconnect_info["attempts"] = 0
698
+
679
699
  # Create data context
680
- data_ctx = SensorProfileDataCtx(gforce, device_mac, raw_buf)
700
+ data_ctx = SensorProfileDataCtx(
701
+ gforce,
702
+ device_mac,
703
+ raw_buf,
704
+ on_reconnect_request=lambda: self._schedule_reconnect(device_mac),
705
+ )
681
706
  self._data_ctxs[device_mac] = data_ctx
682
707
 
683
708
  # Start data processing loop in data_event_loop
@@ -705,6 +730,19 @@ class BleakProcess(multiprocessing.Process):
705
730
  if device_mac not in self._gforces and not disconnect_client:
706
731
  return # Already cleaned
707
732
 
733
+ # 用户主动断开时标记为正常断开,并取消正在进行的自动重连
734
+ if disconnect_client:
735
+ info = self._reconnect_info.get(device_mac)
736
+ if info is not None:
737
+ info["normal_disconnect"] = True
738
+ task = info.get("task")
739
+ if task is not None:
740
+ try:
741
+ task.cancel()
742
+ except Exception:
743
+ pass
744
+ info["task"] = None
745
+
708
746
  # Cancel data task (running in data_event_loop)
709
747
  if device_mac in self._data_tasks:
710
748
  task = self._data_tasks.pop(device_mac, None)
@@ -750,8 +788,63 @@ class BleakProcess(multiprocessing.Process):
750
788
  self._raw_bufs.pop(device_mac, None)
751
789
  self._device_states[device_mac] = "Disconnected"
752
790
 
791
+ # 正常断开后清理重连信息;异常断开保留以继续自动重连
792
+ if disconnect_client:
793
+ self._reconnect_info.pop(device_mac, None)
794
+
753
795
  self._publish("state_changed", device_mac=device_mac, state="Disconnected")
754
796
 
797
+ def _schedule_reconnect(self, device_mac: str):
798
+ """从 watchdog 线程调用,调度一次自动重连。"""
799
+ info = self._reconnect_info.get(device_mac)
800
+ if info is None or info.get("normal_disconnect"):
801
+ return
802
+ if info.get("task") is not None:
803
+ return # 已有重连任务在进行
804
+ if info["attempts"] >= _MAX_RECONNECT_ATTEMPTS:
805
+ SdkLog.e(_TAG, f"Max reconnect attempts reached for {device_mac}")
806
+ return
807
+
808
+ info["attempts"] += 1
809
+ SdkLog.i(_TAG, f"Scheduling reconnect attempt {info['attempts']} for {device_mac}")
810
+
811
+ loop = self._main_event_loop
812
+ if loop is None or loop.is_closed():
813
+ SdkLog.e(_TAG, f"Main event loop not available, cannot reconnect {device_mac}")
814
+ return
815
+
816
+ async def _delayed_reconnect():
817
+ await asyncio.sleep(_RECONNECT_DELAY_SECONDS)
818
+ await self._do_reconnect(device_mac)
819
+
820
+ try:
821
+ info["task"] = asyncio.run_coroutine_threadsafe(_delayed_reconnect(), loop)
822
+ except Exception as e:
823
+ SdkLog.exception(_TAG, f"Failed to schedule reconnect for {device_mac}: {e}")
824
+ info["task"] = None
825
+
826
+ async def _do_reconnect(self, device_mac: str):
827
+ """执行一次自动重连。"""
828
+ info = self._reconnect_info.get(device_mac)
829
+ if info is None or info.get("normal_disconnect"):
830
+ return
831
+
832
+ # 已经连接则无需重连
833
+ if device_mac in self._gforces:
834
+ info["task"] = None
835
+ return
836
+
837
+ SdkLog.i(_TAG, f"Reconnecting {device_mac}, attempt {info['attempts']}")
838
+ try:
839
+ await self._do_connect(info["cmd"])
840
+ except Exception as e:
841
+ SdkLog.exception(_TAG, f"Reconnect attempt failed for {device_mac}: {e}")
842
+ # 失败后会再次由 _schedule_reconnect 决定是否继续
843
+ self._schedule_reconnect(device_mac)
844
+ finally:
845
+ if info is not None:
846
+ info["task"] = None
847
+
755
848
  async def _do_disconnect(self, cmd: dict):
756
849
 
757
850
  device_mac = cmd["device_mac"]
@@ -1160,6 +1253,8 @@ class BleakProcess(multiprocessing.Process):
1160
1253
 
1161
1254
  result = "Error: Not supported"
1162
1255
  needs_restart = False
1256
+ oym_needs_subscribe = False
1257
+ was_streaming = ctx.isDataTransfering
1163
1258
 
1164
1259
  ntf_keys = [
1165
1260
  "NTF_GEST", "NTF_EMG", "NTF_EEG", "NTF_ECG", "NTF_IMU", "NTF_BRTH", "NTF_IMPEDANCE",
@@ -1220,14 +1315,19 @@ class BleakProcess(multiprocessing.Process):
1220
1315
  except Exception as e:
1221
1316
  SdkLog.exception(_TAG, f"_do_set_param set_function_switch failed: {device_mac}")
1222
1317
  result = "ERROR: set_function_switch fail: " + str(e)
1223
- elif ctx.isDataTransfering:
1318
+ if ctx.getChipType() == BLEChipType.OYM:
1319
+ oym_needs_subscribe = True
1320
+ elif was_streaming:
1224
1321
  needs_restart = True
1225
1322
  if ctx.getChipType() == BLEChipType.OYM:
1226
- try:
1227
- await ctx.gForce.set_subscription(ctx.notifyDataFlag)
1228
- except Exception as e:
1229
- SdkLog.exception(_TAG, f"_do_set_param set_subscription failed: {device_mac}")
1230
- result = "ERROR: set_subscription fail: " + str(e)
1323
+ oym_needs_subscribe = True
1324
+ elif ctx.getChipType() == BLEChipType.OYM:
1325
+ # 未在传输时,直接更新 OYM 订阅掩码
1326
+ try:
1327
+ await ctx.gForce.set_subscription(ctx.notifyDataFlag)
1328
+ except Exception as e:
1329
+ SdkLog.exception(_TAG, f"_do_set_param set_subscription failed: {device_mac}")
1330
+ result = "ERROR: set_subscription fail: " + str(e)
1231
1331
 
1232
1332
  if key in ["FILTER_50HZ", "FILTER_60HZ", "FILTER_HPF", "FILTER_LPF"]:
1233
1333
  if value in ["ON", "OFF"]:
@@ -1239,10 +1339,14 @@ class BleakProcess(multiprocessing.Process):
1239
1339
  SdkLog.exception(_TAG, f"_do_set_param setFilter failed: {device_mac} {key}={value}")
1240
1340
  result = "ERROR: " + str(e)
1241
1341
 
1242
- if needs_restart:
1342
+ if needs_restart or oym_needs_subscribe:
1243
1343
  try:
1244
- await ctx.stop_streaming()
1245
- await ctx.start_streaming()
1344
+ if was_streaming:
1345
+ await ctx.stop_streaming()
1346
+ if oym_needs_subscribe and ctx.getChipType() == BLEChipType.OYM:
1347
+ await ctx.gForce.set_subscription(ctx.notifyDataFlag)
1348
+ if was_streaming:
1349
+ await ctx.start_streaming()
1246
1350
  except Exception as e:
1247
1351
  SdkLog.exception(_TAG, f"_do_set_param restart stream failed: {device_mac}")
1248
1352
  result = "ERROR: restart stream fail: " + str(e)
@@ -2,6 +2,7 @@ import asyncio
2
2
  import queue
3
3
  import struct
4
4
  import platform
5
+ import time
5
6
  from contextlib import suppress
6
7
  from datetime import datetime
7
8
  from dataclasses import dataclass
@@ -453,6 +454,8 @@ class GForce:
453
454
  self.cmd_char = cmd_char
454
455
  self.data_char = data_char
455
456
  self.responses: Dict[Command, queue.Queue] = {}
457
+ self.last_command_failure_time: Optional[float] = None
458
+ self.last_command_failure_cmd: Optional[int] = None
456
459
  self.resolution = SampleResolution.BITS_8
457
460
  self._num_channels = 8
458
461
  self._device = device
@@ -1091,6 +1094,8 @@ class GForce:
1091
1094
  timeout=2
1092
1095
  )
1093
1096
  except Exception as e:
1097
+ self.last_command_failure_time = time.time()
1098
+ self.last_command_failure_cmd = req.cmd
1094
1099
  SdkLog.exception(_TAG, f"_send_request write_gatt_char failed: {req.cmd}")
1095
1100
  if req.has_res:
1096
1101
  self.responses[req.cmd] = None
@@ -9,7 +9,7 @@ import struct
9
9
  import math
10
10
  import threading
11
11
  import time
12
- from typing import Deque, List
12
+ from typing import Deque, List, Optional
13
13
  from concurrent.futures import ThreadPoolExecutor
14
14
  import csv
15
15
  from sensor import sensor_utils
@@ -30,6 +30,8 @@ QUAT_SCALE = 1 / 1073741824.0 # 2^30
30
30
  _MAX_CONCAT_BUFFER_SIZE = 64 * 1024 # 64KB
31
31
  # data 日志队列最大长度,写盘跟不上时丢弃日志而不是阻塞解析线程
32
32
  _DATA_LOG_QUEUE_MAXSIZE = 1000
33
+ # 包序号最大允许跳变值,超过视为非法跳变/丢包
34
+ _MAX_ALLOWED_PACKAGE_INDEX_DELTA = 100
33
35
 
34
36
  class SensorDataType(IntEnum):
35
37
  DATA_TYPE_EEG = 0
@@ -83,7 +85,13 @@ class ReadSamplesResult(IntEnum):
83
85
 
84
86
 
85
87
  class SensorProfileDataCtx:
86
- def __init__(self, gForce: GForce, deviceMac: str, buf: Queue[bytes]):
88
+ def __init__(
89
+ self,
90
+ gForce: GForce,
91
+ deviceMac: str,
92
+ buf: Queue[bytes],
93
+ on_reconnect_request: Optional[callable] = None,
94
+ ):
87
95
  self.featureMap = 0
88
96
  self.notifyDataFlag: DataSubscription = 0
89
97
 
@@ -150,6 +158,15 @@ class SensorProfileDataCtx:
150
158
  self._watchdog_thread.daemon = True
151
159
  self._watchdog_thread.start()
152
160
 
161
+ # 非法跳变监控:持续超过阈值则重启数据通知
162
+ self._illegal_jump_start_time: Optional[float] = None
163
+ self._is_restarting_stream = False
164
+
165
+ # 命令失败导致异常断开后自动重连
166
+ self._on_reconnect_request = on_reconnect_request
167
+ self._disconnected_start_time: Optional[float] = None
168
+ self._reconnect_requested = False
169
+
153
170
  def close(self):
154
171
  self._is_running = False
155
172
  if self.debugCSVWriter != None:
@@ -188,10 +205,28 @@ class SensorProfileDataCtx:
188
205
  pass
189
206
  self._watchdog_thread = None
190
207
 
208
+ async def _restart_streaming(self):
209
+ """非法跳变持续时重启数据通知。"""
210
+ if self._is_restarting_stream:
211
+ return
212
+ self._is_restarting_stream = True
213
+ try:
214
+ SdkLog.w(_TAG, f"Illegal package index jump persisted > 5s, restarting data notification for {self.deviceMac}")
215
+ await self.stop_streaming()
216
+ await self.start_streaming(self._rawDataBuffer)
217
+ self._illegal_jump_start_time = None
218
+ except Exception as e:
219
+ SdkLog.exception(_TAG, f"Restart streaming failed for {self.deviceMac}: {e}")
220
+ finally:
221
+ self._is_restarting_stream = False
222
+
191
223
  def _watchdog_loop(self):
192
224
  """守护线程:监控解析线程进度,防止卡死后内存无限增长。"""
193
225
  WATCHDOG_INTERVAL = 1.0 # 检查间隔
194
226
  WATCHDOG_TIMEOUT = 5.0 # 无进度报警阈值
227
+ ILLEGAL_JUMP_TIMEOUT = 5.0 # 非法跳变持续阈值
228
+ RECONNECT_AFTER_DISCONNECT = 3.0 # 断开后等待多久尝试重连
229
+ RECENT_CMD_FAILURE_WINDOW = 30.0 # 命令失败多久内视为相关
195
230
  RAW_BUF_CLEAR_THRESHOLD = 1500 # 超过此值且无进度时清空
196
231
 
197
232
  while not self._watchdog_stop_event.is_set():
@@ -229,6 +264,46 @@ class SensorProfileDataCtx:
229
264
  self._concatDataBuffer.clear()
230
265
  # 清空后更新时间戳,避免持续报警
231
266
  self._last_progress_time = time.time()
267
+
268
+ # 非法跳变持续超过阈值则重启数据通知
269
+ if (self._illegal_jump_start_time is not None
270
+ and not self._is_restarting_stream
271
+ and self.isDataTransfering):
272
+ jump_elapsed = time.time() - self._illegal_jump_start_time
273
+ if jump_elapsed > ILLEGAL_JUMP_TIMEOUT:
274
+ SdkLog.w(_TAG, f"Illegal jump persisted for {jump_elapsed:.1f}s, restarting stream for {self.deviceMac}")
275
+ try:
276
+ asyncio.run_coroutine_threadsafe(
277
+ self._restart_streaming(), self.gForce.gforce_event_loop
278
+ )
279
+ except Exception as e:
280
+ SdkLog.exception(_TAG, f"Failed to schedule stream restart for {self.deviceMac}: {e}")
281
+
282
+ # 命令失败后设备异常断开,触发自动重连
283
+ if self._on_reconnect_request is not None:
284
+ client = getattr(self.gForce, "client", None)
285
+ is_connected = getattr(client, "is_connected", False) if client is not None else False
286
+ if is_connected:
287
+ self._disconnected_start_time = None
288
+ self._reconnect_requested = False
289
+ else:
290
+ if self._disconnected_start_time is None:
291
+ self._disconnected_start_time = time.time()
292
+ disconnected_elapsed = time.time() - self._disconnected_start_time
293
+ cmd_fail_time = getattr(self.gForce, "last_command_failure_time", None)
294
+ recent_cmd_failure = (
295
+ cmd_fail_time is not None
296
+ and (time.time() - cmd_fail_time) < RECENT_CMD_FAILURE_WINDOW
297
+ )
298
+ if (disconnected_elapsed > RECONNECT_AFTER_DISCONNECT
299
+ and recent_cmd_failure
300
+ and not self._reconnect_requested):
301
+ SdkLog.w(_TAG, f"Device disconnected after command failure, scheduling reconnect for {self.deviceMac}")
302
+ self._reconnect_requested = True
303
+ try:
304
+ self._on_reconnect_request()
305
+ except Exception as e:
306
+ SdkLog.exception(_TAG, f"Reconnect request failed for {self.deviceMac}: {e}")
232
307
  except Exception as e:
233
308
  SdkLog.exception(_TAG, f"Watchdog error: {e}")
234
309
 
@@ -408,6 +483,15 @@ class SensorProfileDataCtx:
408
483
  self.sensorDatas[SensorDataType.DATA_TYPE_EMG] = data
409
484
  self.isNewEMG = isNewEMG
410
485
 
486
+ # 新 EMG + OYM 芯片:默认只开 EMG,关闭 Gesture 和 IMU
487
+ if isNewEMG and self._chip_type == BLEChipType.OYM:
488
+ self.notify_map["NTF_GEST"] = "OFF"
489
+ self.notify_map["NTF_IMU"] = "OFF"
490
+ self.notify_map["NTF_GFORCE_ACC"] = "OFF"
491
+ self.notify_map["NTF_GFORCE_GYRO"] = "OFF"
492
+ self.notify_map["NTF_GFORCE_QUAT"] = "OFF"
493
+ self.notify_map["NTF_GFORCE_EULER"] = "OFF"
494
+
411
495
  # 老 EMG 设备不支持 FILTER
412
496
  if not isNewEMG:
413
497
  self.filter_map.clear()
@@ -1489,6 +1573,7 @@ class SensorProfileDataCtx:
1489
1573
  if newPackageIndex <= 2 and lastPackageIndex >= maxPackageIndex - 2:
1490
1574
  packageIndex = maxPackageIndex + 1 + newPackageIndex
1491
1575
  elif newPackageIndex == lastPackageIndex:
1576
+ self._illegal_jump_start_time = None
1492
1577
  return ReadSamplesResult.Repeated
1493
1578
  elif newPackageIndex < lastPackageIndex:
1494
1579
  SdkLog.i(_TAG, (
@@ -1498,10 +1583,12 @@ class SensorProfileDataCtx:
1498
1583
  + "|CURR_IDX|" + str(newPackageIndex)
1499
1584
  + "|DELTA|" + str(lastPackageIndex - newPackageIndex)
1500
1585
  ))
1586
+ if self._illegal_jump_start_time is None:
1587
+ self._illegal_jump_start_time = time.time()
1501
1588
  return ReadSamplesResult.Error
1502
1589
 
1503
1590
  deltaPackageIndex = packageIndex - lastPackageIndex
1504
- if deltaPackageIndex > 20:
1591
+ if deltaPackageIndex > _MAX_ALLOWED_PACKAGE_INDEX_DELTA:
1505
1592
  SdkLog.i(_TAG, (
1506
1593
  "Illegal package index jump|MAC|" + str(sensorData.deviceMac)
1507
1594
  + "|TYPE|" + str(sensorData.dataType)
@@ -1509,6 +1596,8 @@ class SensorProfileDataCtx:
1509
1596
  + "|CURR_IDX|" + str(newPackageIndex)
1510
1597
  + "|DELTA|" + str(deltaPackageIndex)
1511
1598
  ))
1599
+ if self._illegal_jump_start_time is None:
1600
+ self._illegal_jump_start_time = time.time()
1512
1601
  return ReadSamplesResult.Error
1513
1602
 
1514
1603
  lostPackageCounter = deltaPackageIndex - 1
@@ -1522,7 +1611,7 @@ class SensorProfileDataCtx:
1522
1611
  + "|COUNT|" + str(lostSampleCount)
1523
1612
  ))
1524
1613
 
1525
- if lostPackageCounter < 20:
1614
+ if lostPackageCounter < _MAX_ALLOWED_PACKAGE_INDEX_DELTA:
1526
1615
  self.readSamples(data, sensorData, 0, dataGap, lostSampleCount)
1527
1616
 
1528
1617
  sensorData.lastPackageIndex = newPackageIndex - 1
@@ -1534,6 +1623,7 @@ class SensorProfileDataCtx:
1534
1623
  self.readSamples(data, sensorData, dataOffset, dataGap, 0)
1535
1624
 
1536
1625
  sensorData.lastPackageCounter += 1
1626
+ self._illegal_jump_start_time = None
1537
1627
  except Exception as e:
1538
1628
  SdkLog.exception(_TAG, "Unexpected error")
1539
1629
  return ReadSamplesResult.Error
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sensor-sdk
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Python sdk for Synchroni
5
5
  Home-page: https://github.com/oymotion/SynchroniSDKPython
6
6
  Author: Martin Ye
@@ -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.2.0",
11
+ version="0.2.1",
12
12
  description="Python sdk for Synchroni",
13
13
  long_description=long_description,
14
14
  long_description_content_type="text/markdown",
File without changes
File without changes
File without changes
File without changes