sensor-sdk 0.0.49__tar.gz → 0.0.51__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 (21) hide show
  1. {sensor_sdk-0.0.49/sensor_sdk.egg-info → sensor_sdk-0.0.51}/PKG-INFO +1 -1
  2. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/bleak_process.py +1 -1
  3. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/gforce.py +11 -29
  4. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_controller.py +1 -0
  5. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_data_context.py +69 -20
  6. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_profile.py +3 -1
  7. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51/sensor_sdk.egg-info}/PKG-INFO +1 -1
  8. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/setup.py +1 -1
  9. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/LICENSE.txt +0 -0
  10. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/README.md +0 -0
  11. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/__init__.py +0 -0
  12. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/bleak_host.py +0 -0
  13. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_data.py +0 -0
  14. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_device.py +0 -0
  15. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_utils.py +0 -0
  16. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/SOURCES.txt +0 -0
  17. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/dependency_links.txt +0 -0
  18. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/requires.txt +0 -0
  19. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/top_level.txt +0 -0
  20. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/zip-safe +0 -0
  21. {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sensor-sdk
3
- Version: 0.0.49
3
+ Version: 0.0.51
4
4
  Summary: Python sdk for Synchroni
5
5
  Home-page: https://github.com/oymotion/SynchroniSDKPython
6
6
  Author: Martin Ye
@@ -148,7 +148,7 @@ class BleakProcess(multiprocessing.Process):
148
148
  if self._scanner is None:
149
149
  self._scanner = BleakScanner(
150
150
  detection_callback=_match_device,
151
- service_uuids=[SERVICE_GUID, RFSTAR_SERVICE_GUID],
151
+ service_uuids=[RFSTAR_SERVICE_GUID, SERVICE_GUID],
152
152
  )
153
153
 
154
154
  def _ensure_gforce_loop(self):
@@ -502,34 +502,11 @@ class GForce:
502
502
  raise ConnectionError("Connect %s fail: %s" % (self._device.name , e))
503
503
 
504
504
  def _on_data_response(self, q: queue.Queue[bytes], bs: bytearray):
505
- # bs = bytes(bs)
506
-
507
- # full_packet = []
508
-
509
- # is_partial_data = bs[0] == ResponseCode.PARTIAL_PACKET
510
- # if is_partial_data:
511
- # packet_id = bs[1]
512
- # if self.packet_id != 0 and self.packet_id != packet_id + 1:
513
- # raise Exception(
514
- # "Unexpected packet id: expected {} got {}".format(
515
- # self.packet_id + 1,
516
- # packet_id,
517
- # )
518
- # )
519
- # elif self.packet_id == 0 or self.packet_id > packet_id:
520
- # self.packet_id = packet_id
521
- # self.data_packet += bs[2:]
522
-
523
- # if self.packet_id == 0:
524
- # full_packet = self.data_packet
525
- # self.data_packet = []
526
- # else:
527
- # full_packet = bs
528
-
529
- full_packet = bs
530
- if len(full_packet) == 0:
531
- return
532
- q.put_nowait(bytes(full_packet))
505
+ try:
506
+ q.put_nowait(bytes(bs))
507
+ except queue.Full:
508
+ while not q.empty():
509
+ q.get_nowait()
533
510
 
534
511
  @staticmethod
535
512
  def _convert_acceleration_to_g(data: bytes) -> np.ndarray[np.float32]:
@@ -591,7 +568,12 @@ class GForce:
591
568
  return emg_gesture_data.reshape(-1, num_channels)
592
569
 
593
570
  def _on_universal_response(self, _: BleakGATTCharacteristic, bs: bytearray):
594
- self._raw_data_buf.put_nowait(bytes(bs))
571
+ q = self._raw_data_buf
572
+ try:
573
+ q.put_nowait(bytes(bs))
574
+ except queue.Full:
575
+ while not q.empty():
576
+ q.get_nowait()
595
577
 
596
578
  def _on_cmd_response(self, _: BleakGATTCharacteristic, bs: bytearray):
597
579
  sensor_utils.async_exec(self.async_on_cmd_response(bs), self.event_loop)
@@ -137,6 +137,7 @@ class SensorController:
137
137
  if mac is None:
138
138
  continue
139
139
  if deviceMap.get(mac) is not None:
140
+ self._sensor_profiles[mac].BLEDevice.RSSI = serialized.get("rssi")
140
141
  devices.append(self._sensor_profiles[mac].BLEDevice)
141
142
  else:
142
143
  newSensor = SensorProfile(serialized=serialized, bleak_host=self._bleak_host)
@@ -76,7 +76,10 @@ class SensorProfileDataCtx:
76
76
 
77
77
  # Quaternion support
78
78
  self.isContainQAT6 = False
79
-
79
+ self.lastQuatData: List[float] = [0.0, 0.0, 0.0, 0.0]
80
+ self.lastEulerData: List[float] = [0.0, 0.0, 0.0]
81
+ self.lastAccData: List[float] = [0.0, 0.0, 0.0]
82
+ self.lastGyroData: List[float] = [0.0, 0.0, 0.0]
80
83
  # PPG configuration
81
84
  self.model = PPGDataMode.PPG_RAW
82
85
 
@@ -373,6 +376,20 @@ class SensorProfileDataCtx:
373
376
  data.clear()
374
377
  self.sensorDatas[SensorDataType.DATA_TYPE_QUATERNION] = data
375
378
 
379
+ data = SensorData()
380
+ data.deviceMac = self.deviceMac
381
+ data.dataType = DataType.NTF_EULER_DATA
382
+ data.sampleRate = config.fs
383
+ data.resolutionBits = 32 # float32
384
+ data.channelCount = 3 # pitch, roll, yaw
385
+ data.channelMask = 0b0111
386
+ data.packageIndexLength = 0
387
+ data.minPackageSampleCount = packageCount
388
+ data.packageSampleCount = 1
389
+ data.K = 1.0
390
+ data.clear()
391
+ self.sensorDatas[SensorDataType.DATA_TYPE_EULER] = data
392
+
376
393
  self.notifyDataFlag |= DataSubscription.DNF_IMU
377
394
 
378
395
  return config.channel_count
@@ -549,7 +566,11 @@ class SensorProfileDataCtx:
549
566
  info.GyroChannelCount = 3
550
567
  info.AccSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_ACC].sampleRate
551
568
  info.GyroSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_GYRO].sampleRate
552
-
569
+ if self.isContainQAT6:
570
+ info.QuatChannelCount = 4
571
+ info.EulerChannelCount = 3
572
+ info.QuatSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_QUATERNION].sampleRate
573
+ info.EulerSampleRate = self.sensorDatas[SensorDataType.DATA_TYPE_EULER].sampleRate
553
574
 
554
575
  if self.hasEuler() and (self.init_map["NTF_GFORCE_EULER"] == "ON"):
555
576
  info.EulerChannelCount = await self.initEuler(packageCount)
@@ -818,16 +839,12 @@ class SensorProfileDataCtx:
818
839
  elif v == DataType.NTF_SPO2:
819
840
  self._process_spo2_data(data, buf)
820
841
  elif v == DataType.NTF_EULER_DATA:
821
-
822
842
  self._process_gforce_float_data(data, buf, SensorDataType.DATA_TYPE_EULER, 3)
823
843
  elif v == DataType.NTF_QUATERNION:
824
-
825
844
  self._process_gforce_float_data(data, buf, SensorDataType.DATA_TYPE_GFORCE_QUAT, 4)
826
845
  elif v == DataType.NTF_ACC:
827
-
828
846
  self._process_gforce_int_data(data, buf, SensorDataType.DATA_TYPE_ACC, 3)
829
847
  elif v == DataType.NTF_GYRO:
830
-
831
848
  self._process_gforce_int_data(data, buf, SensorDataType.DATA_TYPE_GYRO, 3)
832
849
 
833
850
  def _process_gforce_float_data(self, data: bytes, buf: Queue[SensorData],
@@ -838,9 +855,9 @@ class SensorProfileDataCtx:
838
855
 
839
856
  EXPECTED_SIZE = HEADER_SIZE + channel_count * FLOAT_SIZE
840
857
 
841
- print(f"[DataTask] 异常: len(data){len(data)} ")
842
-
858
+
843
859
  if len(data) != EXPECTED_SIZE:
860
+ print(f"[DataTask] 异常: len(data){len(data)} ")
844
861
  return
845
862
 
846
863
  sensor_data = self.sensorDatas[data_type_index]
@@ -927,8 +944,8 @@ class SensorProfileDataCtx:
927
944
  sample.isLost = False
928
945
  sensor_data.channelSamples[ch].append(sample)
929
946
 
930
- type_name = "ACC" if data[0] & 0x7F == DataType.NTF_ACC else "GYRO"
931
- print(f"[{type_name}] pkgIdx={pkg_index} x={channel_values[0]} y={channel_values[1]} z={channel_values[2]}")
947
+ # type_name = "ACC" if data[0] & 0x7F == DataType.NTF_ACC else "GYRO"
948
+ # print(f"[{type_name}] pkgIdx={pkg_index} x={channel_values[0]} y={channel_values[1]} z={channel_values[2]}")
932
949
 
933
950
  sensor_data.lastPackageCounter += 1
934
951
  self.sendSensorData(sensor_data, buf)
@@ -988,17 +1005,20 @@ class SensorProfileDataCtx:
988
1005
  sensor_data_acc = self.sensorDatas[SensorDataType.DATA_TYPE_ACC]
989
1006
  sensor_data_gyro = self.sensorDatas[SensorDataType.DATA_TYPE_GYRO]
990
1007
  sensor_data_quat = None
1008
+ sensor_data_euler = None
991
1009
 
992
1010
  sensor_datas = [sensor_data_acc, sensor_data_gyro]
993
1011
  if self.isContainQAT6:
994
1012
  sensor_data_quat = self.sensorDatas[SensorDataType.DATA_TYPE_QUATERNION]
1013
+ sensor_data_euler = self.sensorDatas[SensorDataType.DATA_TYPE_EULER]
995
1014
  sensor_datas.append(sensor_data_quat)
1015
+ sensor_datas.append(sensor_data_euler)
996
1016
 
997
- if self._check_read_imu_samples(data, sensor_datas, sensor_data_quat, on_error_callback):
1017
+ if self._check_read_imu_samples(data, sensor_datas, sensor_data_quat, sensor_data_euler, on_error_callback):
998
1018
  for sensor_data in sensor_datas:
999
1019
  self.sendSensorData(sensor_data, buf)
1000
1020
 
1001
- def _check_read_imu_samples(self, data: bytes, sensor_datas: List[SensorData], sensor_data_quat, on_error_callback=None):
1021
+ def _check_read_imu_samples(self, data: bytes, sensor_datas: List[SensorData], sensor_data_quat, sensor_data_euler,on_error_callback=None):
1002
1022
  if not self._is_data_transfering or not sensor_datas:
1003
1023
  return False
1004
1024
 
@@ -1079,7 +1099,7 @@ class SensorProfileDataCtx:
1079
1099
  for sensor_data in sensor_datas:
1080
1100
  sensor_data.lastPackageIndex = newPackageIndex
1081
1101
 
1082
- if not self._read_imu_frame_samples(data, sensor_datas, sensor_data_quat, offset, 0):
1102
+ if not self._read_imu_frame_samples(data, sensor_datas, sensor_data_quat, sensor_data_euler, offset, 0):
1083
1103
  return False
1084
1104
 
1085
1105
  for sensor_data in sensor_datas:
@@ -1107,6 +1127,7 @@ class SensorProfileDataCtx:
1107
1127
  data: bytes,
1108
1128
  sensor_datas: List[SensorData],
1109
1129
  sensor_data_quat: SensorData,
1130
+ sensor_data_euler: SensorData,
1110
1131
  dataOffset: int,
1111
1132
  lostSampleCount: int,
1112
1133
  ) -> bool:
@@ -1134,10 +1155,11 @@ class SensorProfileDataCtx:
1134
1155
  sampleIndex = lastSampleIndex + sampleOffset
1135
1156
 
1136
1157
  if lostSampleCount > 0:
1137
- self._append_imu_vector_sample(sensor_data_acc, sampleIndex, [0, 0, 0], True)
1138
- self._append_imu_vector_sample(sensor_data_gyro, sampleIndex, [0, 0, 0], True)
1158
+ self._append_imu_vector_sample(sensor_data_acc, sampleIndex, self.lastAccData, True)
1159
+ self._append_imu_vector_sample(sensor_data_gyro, sampleIndex, self.lastGyroData, True)
1139
1160
  if sensor_data_quat is not None:
1140
- self._append_imu_quaternion_sample(sensor_data_quat, sampleIndex, [0, 0, 0], True)
1161
+ self._append_imu_quaternion_sample(sensor_data_quat, sensor_data_euler, sampleIndex, self.lastQuatData, True)
1162
+
1141
1163
  continue
1142
1164
 
1143
1165
  accRaw = [
@@ -1145,15 +1167,17 @@ class SensorProfileDataCtx:
1145
1167
  struct.unpack_from("<h", data, offset + 2)[0],
1146
1168
  struct.unpack_from("<h", data, offset + 4)[0],
1147
1169
  ]
1170
+ self.lastAccData = accRaw.copy()
1148
1171
  gyroOffset = offset + 6
1149
1172
  gyroRaw = [
1150
1173
  struct.unpack_from("<h", data, gyroOffset)[0],
1151
1174
  struct.unpack_from("<h", data, gyroOffset + 2)[0],
1152
1175
  struct.unpack_from("<h", data, gyroOffset + 4)[0],
1153
1176
  ]
1177
+ self.lastGyroData = gyroRaw.copy()
1154
1178
 
1155
- self._append_imu_vector_sample(sensor_data_acc, sampleIndex, accRaw, False)
1156
- self._append_imu_vector_sample(sensor_data_gyro, sampleIndex, gyroRaw, False)
1179
+ self._append_imu_vector_sample(sensor_data_acc, sampleIndex, self.lastAccData, False)
1180
+ self._append_imu_vector_sample(sensor_data_gyro, sampleIndex, self.lastGyroData, False)
1157
1181
 
1158
1182
  if sensor_data_quat is not None:
1159
1183
  quatOffset = offset + 12
@@ -1162,7 +1186,7 @@ class SensorProfileDataCtx:
1162
1186
  struct.unpack_from("<i", data, quatOffset + 4)[0],
1163
1187
  struct.unpack_from("<i", data, quatOffset + 8)[0],
1164
1188
  ]
1165
- self._append_imu_quaternion_sample(sensor_data_quat, sampleIndex, quatRaw, False)
1189
+ self._append_imu_quaternion_sample(sensor_data_quat, sensor_data_euler, sampleIndex, quatRaw, False)
1166
1190
 
1167
1191
  offset += frameSize
1168
1192
 
@@ -1191,6 +1215,7 @@ class SensorProfileDataCtx:
1191
1215
  def _append_imu_quaternion_sample(
1192
1216
  self,
1193
1217
  sensor_data_quat: SensorData,
1218
+ sensor_data_euler: SensorData,
1194
1219
  sampleIndex: int,
1195
1220
  rawValues: List[int],
1196
1221
  isLost: bool,
@@ -1200,16 +1225,25 @@ class SensorProfileDataCtx:
1200
1225
 
1201
1226
  raw = [0, 0, 0, 0]
1202
1227
  value = [0.0, 0.0, 0.0, 0.0]
1228
+ euler = [0.0, 0.0, 0.0]
1203
1229
 
1204
1230
  if not isLost:
1205
1231
  raw[1] = rawValues[0]
1206
1232
  raw[2] = rawValues[1]
1207
1233
  raw[3] = rawValues[2]
1234
+
1208
1235
  value[1] = raw[1] / scale
1209
1236
  value[2] = raw[2] / scale
1210
1237
  value[3] = raw[3] / scale
1211
1238
  value[0] = math.sqrt(max(0.0, 1.0 - value[1] ** 2 - value[2] ** 2 - value[3] ** 2))
1212
1239
 
1240
+ euler[0] = math.atan2(2 * (value[0] * value[1] + value[2] * value[3]), 1 - 2 * (value[1] ** 2 + value[2] ** 2)) * 180 / math.pi
1241
+ euler[1] = math.asin(max(-1.0, min(1.0, 2 * (value[0] * value[2] - value[3] * value[1])))) * 180 / math.pi
1242
+ euler[2] = math.atan2(2 * (value[0] * value[3] + value[1] * value[2]), 1 - 2 * (value[2] ** 2 + value[3] ** 2)) * 180 / math.pi
1243
+
1244
+ self.lastQuatData = value.copy()
1245
+ self.lastEulerData = euler.copy()
1246
+
1213
1247
  for channelIndex in range(sensor_data_quat.channelCount):
1214
1248
  if (sensor_data_quat.channelMask & (1 << channelIndex)) == 0:
1215
1249
  continue
@@ -1219,12 +1253,27 @@ class SensorProfileDataCtx:
1219
1253
  sample.sampleIndex = sampleIndex
1220
1254
  sample.timeStampInMs = sampleIndex * sampleInterval
1221
1255
  sample.rawData = raw[channelIndex]
1222
- sample.data = value[channelIndex]
1256
+ sample.data = self.lastQuatData[channelIndex]
1223
1257
  sample.impedance = 0.0
1224
1258
  sample.saturation = 0.0
1225
1259
  sample.isLost = isLost
1226
1260
  sensor_data_quat.channelSamples[channelIndex].append(sample)
1227
1261
 
1262
+ for channelIndex in range(sensor_data_euler.channelCount):
1263
+ if (sensor_data_euler.channelMask & (1 << channelIndex)) == 0:
1264
+ continue
1265
+
1266
+ sample = Sample()
1267
+ sample.channelIndex = channelIndex
1268
+ sample.sampleIndex = sampleIndex
1269
+ sample.timeStampInMs = sampleIndex * sampleInterval
1270
+ sample.rawData = raw[channelIndex]
1271
+ sample.data = self.lastEulerData[channelIndex]
1272
+ sample.impedance = 0.0
1273
+ sample.saturation = 0.0
1274
+ sample.isLost = isLost
1275
+ sensor_data_euler.channelSamples[channelIndex].append(sample)
1276
+
1228
1277
  def checkReadSamples(self, data: bytes, sensorData: SensorData, dataOffset: int, dataGap: int, on_error_callback=None):
1229
1278
  offset = 1
1230
1279
 
@@ -127,7 +127,9 @@ class SensorProfile:
127
127
  new_state = _STATE_NAME_MAP.get(state_name, self._device_state)
128
128
  self._set_device_state(new_state)
129
129
  elif msg_type == "power_changed":
130
- self._power = msg.get("power", -1)
130
+ power = msg.get("power", -1)
131
+ if power < self._power or self._power == -1:
132
+ self._power = power
131
133
  if self._on_power_changed is not None:
132
134
  try:
133
135
  self._on_power_changed(self, self._power)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sensor-sdk
3
- Version: 0.0.49
3
+ Version: 0.0.51
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.0.49",
11
+ version="0.0.51",
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