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.
- {sensor_sdk-0.0.49/sensor_sdk.egg-info → sensor_sdk-0.0.51}/PKG-INFO +1 -1
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/bleak_process.py +1 -1
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/gforce.py +11 -29
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_controller.py +1 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_data_context.py +69 -20
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_profile.py +3 -1
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51/sensor_sdk.egg-info}/PKG-INFO +1 -1
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/setup.py +1 -1
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/LICENSE.txt +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/README.md +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/__init__.py +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/bleak_host.py +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_data.py +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_device.py +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor/sensor_utils.py +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/SOURCES.txt +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/dependency_links.txt +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/requires.txt +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/top_level.txt +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/sensor_sdk.egg-info/zip-safe +0 -0
- {sensor_sdk-0.0.49 → sensor_sdk-0.0.51}/setup.cfg +0 -0
|
@@ -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=[
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
1138
|
-
self._append_imu_vector_sample(sensor_data_gyro, sampleIndex,
|
|
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,
|
|
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,
|
|
1156
|
-
self._append_imu_vector_sample(sensor_data_gyro, sampleIndex,
|
|
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 =
|
|
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
|
-
|
|
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)
|
|
@@ -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.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
|
|
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
|