sensor-sdk 0.0.1__py3-none-any.whl

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.

@@ -0,0 +1,449 @@
1
+
2
+
3
+ # 设备状态枚举
4
+ # 该枚举类定义了设备的各种状态,用于表示设备在不同操作阶段的状态信息
5
+ from enum import Enum, IntEnum
6
+ from queue import Queue
7
+ import struct
8
+ import time
9
+ from typing import Callable, Dict, List, Optional
10
+
11
+ import bleak
12
+ from bleak import (
13
+ BleakClient,
14
+ BleakGATTCharacteristic,
15
+ )
16
+
17
+ import sensor
18
+ from sensor.gforce import GForce
19
+ from sensor.sensor_data import DataType, Sample, SensorData
20
+ import asyncio
21
+ import threading
22
+
23
+ from sensor.sensor_data_context import SensorProfileDataCtx
24
+ from sensor.sensor_device import BLEDevice, DeviceInfo, DeviceStateEx
25
+ from sensor.utils import start_loop, sync_timer, timer
26
+ from contextlib import suppress
27
+ from dataclasses import dataclass
28
+
29
+ SERVICE_GUID = "0000ffd0-0000-1000-8000-00805f9b34fb"
30
+ OYM_CMD_NOTIFY_CHAR_UUID = "f000ffe1-0451-4000-b000-000000000000"
31
+ OYM_DATA_NOTIFY_CHAR_UUID = "f000ffe2-0451-4000-b000-000000000000"
32
+
33
+ RFSTAR_SERVICE_GUID = "00001812-0000-1000-8000-00805f9b34fb"
34
+ RFSTAR_CMD_UUID = "00000002-0000-1000-8000-00805f9b34fb"
35
+ RFSTAR_DATA_UUID = "00000003-0000-1000-8000-00805f9b34fb"
36
+
37
+
38
+ class SensorProfile:
39
+ """
40
+ SensorProfile 类用于蓝牙设备的连接,获取详细设备信息,初始化,数据接收。
41
+
42
+ 包含回调函数,用于处理传感器的状态变化、错误、数据接收和电量变化等事件。
43
+ """
44
+
45
+ def __init__(self, device: bleak.BLEDevice, adv: bleak.AdvertisementData, gforce_event_loop: asyncio.AbstractEventLoop):
46
+ """
47
+ 初始化 SensorProfile 类的实例。
48
+
49
+ :param device (BLEDevice): 蓝牙设备对象,包含设备的名称、地址和信号强度等信息。
50
+ """
51
+ self._detail_device = device
52
+ self._device = BLEDevice(device.name, device.address, adv.rssi)
53
+ self._device_state = DeviceStateEx.Disconnected
54
+ self._on_state_changed: Callable[['SensorProfile', DeviceStateEx], None] = None
55
+ self._on_error_callback: Callable[['SensorProfile', str], None] = None
56
+ self._on_data_callback: Callable[['SensorProfile', SensorData], None] = None
57
+ self._on_power_changed: Callable[['SensorProfile', int], None] = None
58
+ self._power = -1
59
+ self._power_interval = 0
60
+ self._adv = adv
61
+ self._data_ctx: SensorProfileDataCtx = None
62
+ self._gforce: GForce = None
63
+ self._data_event_loop:asyncio.AbstractEventLoop = None
64
+ self._gforce_event_loop:asyncio.AbstractEventLoop = gforce_event_loop
65
+ self._event_loop:asyncio.AbstractEventLoop = None
66
+ self._event_thread = None
67
+
68
+ def __del__(self) -> None:
69
+ """
70
+ 反初始化 SensorProfile 类的实例。
71
+
72
+ """
73
+ self.destroy()
74
+
75
+ def destroy(self):
76
+ if self._device_state == DeviceStateEx.Connected or self._device_state == DeviceStateEx.Ready:
77
+ self.disconnect()
78
+ if (self._data_event_loop != None):
79
+ try:
80
+ self._data_event_loop.stop()
81
+ self._data_event_loop.close()
82
+ self._data_event_thread.join()
83
+ except Exception as e:
84
+ pass
85
+ if (self._event_loop != None):
86
+ try:
87
+ self._event_loop.stop()
88
+ self._event_loop.close()
89
+ self._event_thread.join()
90
+ except Exception as e:
91
+ pass
92
+
93
+ @property
94
+ def deviceState(self) -> DeviceStateEx:
95
+ """
96
+ 获取蓝牙连接状态。
97
+
98
+ :return: DeviceStateEx: 设备的状态,如 Disconnected、Connecting、Connected 等。
99
+ """
100
+ return self._device_state
101
+
102
+ def _set_device_state(self, newState: DeviceStateEx):
103
+ if self._device_state != newState:
104
+ self._device_state = newState
105
+ if self._on_state_changed != None:
106
+ try:
107
+ self._event_loop.call_soon_threadsafe(self._on_state_changed,self, newState)
108
+ except Exception as e:
109
+ print(e)
110
+ pass
111
+
112
+ @property
113
+ def hasInited(self) -> bool:
114
+ """
115
+ 检查传感器是否已经初始化。
116
+
117
+ :return: bool: 如果传感器已经初始化,返回 True;否则返回 False。
118
+ """
119
+ if self._data_ctx == None:
120
+ return False
121
+ return self._data_ctx.hasInit()
122
+
123
+ @property
124
+ def isDataTransfering(self) -> bool:
125
+ """
126
+ 检查传感器是否正在进行数据传输。
127
+
128
+ :return: bool: 如果传感器正在进行数据传输,返回 True;否则返回 False。
129
+ """
130
+ if self._data_ctx == None:
131
+ return False
132
+ return self._data_ctx.isDataTransfering
133
+
134
+ @property
135
+ def BLEDevice(self) -> BLEDevice:
136
+ """
137
+ 获取传感器的蓝牙设备信息。
138
+
139
+ :return: BLEDevice: 蓝牙设备对象,包含设备的名称、地址和信号强度等信息。
140
+ """
141
+ return self._device
142
+
143
+ @property
144
+ def onStateChanged(self) -> Callable[['SensorProfile', DeviceStateEx], None]:
145
+ """
146
+ 获取状态变化的回调函数。
147
+
148
+ :return: Callable[['SensorProfile', DeviceStateEx], None]: 状态变化的回调函数。
149
+ """
150
+ return self._on_state_changed
151
+
152
+ @onStateChanged.setter
153
+ def onStateChanged(self, callback: Callable[['SensorProfile', DeviceStateEx], None]):
154
+ """
155
+ 设置状态变化的回调函数。
156
+
157
+ :param callback (Callable[['SensorProfile', DeviceStateEx], None]): 状态变化的回调函数。
158
+ """
159
+ self._on_state_changed = callback
160
+
161
+ @property
162
+ def onErrorCallback(self) -> Callable[['SensorProfile', str], None]:
163
+ """
164
+ 获取错误回调函数。
165
+
166
+ :return: Callable[['SensorProfile', str], None]: 错误回调函数。
167
+ """
168
+ return self._on_error_callback
169
+
170
+ @onErrorCallback.setter
171
+ def onErrorCallback(self, callback: Callable[['SensorProfile', str], None]):
172
+ """
173
+ 设置错误回调函数。
174
+
175
+ :param callback (Callable[['SensorProfile', str], None]): 错误回调函数。
176
+ """
177
+ self._on_error_callback = callback
178
+
179
+ @property
180
+ def onDataCallback(self) -> Callable[['SensorProfile', SensorData], None]:
181
+ """
182
+ 获取数据接收的回调函数。
183
+
184
+ :return: Callable[['SensorProfile', SensorData], None]: 数据接收的回调函数。
185
+ """
186
+ return self._on_data_callback
187
+
188
+ @onDataCallback.setter
189
+ def onDataCallback(self, callback: Callable[['SensorProfile', SensorData], None]):
190
+ """
191
+ 设置数据接收的回调函数。
192
+
193
+ :param callback (Callable[['SensorProfile', SensorData], None]): 数据接收的回调函数。
194
+ """
195
+ self._on_data_callback = callback
196
+
197
+ @property
198
+ def onPowerChanged(self) -> Callable[['SensorProfile', int], None]:
199
+ """
200
+ 获取电量变化的回调函数。
201
+
202
+ :return: Callable[['SensorProfile', int], None]: 电量变化的回调函数。
203
+ """
204
+ return self._on_power_changed
205
+
206
+ @onPowerChanged.setter
207
+ def onPowerChanged(self, callback: Callable[['SensorProfile', int], None]):
208
+ """
209
+ 设置电量变化的回调函数。
210
+
211
+ :param callback (Callable[['SensorProfile', int], None]): 电量变化的回调函数。
212
+ """
213
+ self._on_power_changed = callback
214
+ async def _connect(self) -> bool:
215
+ if self.deviceState == DeviceStateEx.Connected or self.deviceState == DeviceStateEx.Ready:
216
+ return True
217
+ self._set_device_state(DeviceStateEx.Connecting)
218
+ def handle_disconnect(_: BleakClient):
219
+ self.stopDataNotification()
220
+ self._data_ctx.close()
221
+ time.sleep(1)
222
+ self._data_buffer.queue.clear()
223
+ self._data_ctx = None
224
+ self._gforce = None
225
+ self._set_device_state(DeviceStateEx.Disconnected)
226
+ pass
227
+
228
+ await self._gforce.connect(handle_disconnect, self._raw_data_buf)
229
+
230
+ if (self._gforce.client.is_connected):
231
+ self._set_device_state(DeviceStateEx.Connected)
232
+ if (self._gforce.client.mtu_size >= 80):
233
+ self._set_device_state(DeviceStateEx.Ready)
234
+ else:
235
+ self.disconnect()
236
+ else:
237
+ self._set_device_state(DeviceStateEx.Disconnected)
238
+
239
+ return True
240
+ def connect(self) -> bool:
241
+ """
242
+ 连接传感器。
243
+
244
+ :return: bool: 如果连接成功,返回 True;否则返回 False。
245
+
246
+ """
247
+ if (self._event_thread == None):
248
+ self._event_loop = asyncio.new_event_loop()
249
+ self._event_thread = threading.Thread(target=start_loop, args=(self._event_loop,))
250
+ self._event_thread.daemon = True
251
+ self._event_thread.name = self._device.Name + " event"
252
+ self._event_thread.start()
253
+ self._data_buffer: Queue[SensorData] = Queue()
254
+ self._raw_data_buf: Queue[bytes] = Queue()
255
+
256
+ if (self._gforce == None):
257
+ if (self._adv.service_data.get(SERVICE_GUID) != None):
258
+ # print("OYM_SERVICE:" + self._detail_device.name)
259
+ self._gforce = GForce(self._detail_device, OYM_CMD_NOTIFY_CHAR_UUID, OYM_DATA_NOTIFY_CHAR_UUID, False)
260
+ elif (self._adv.service_data.get(RFSTAR_SERVICE_GUID) != None):
261
+ # print("RFSTAR_SERVICE:" + self._detail_device.name)
262
+ self._gforce = GForce(self._detail_device, RFSTAR_CMD_UUID, RFSTAR_DATA_UUID, True)
263
+ self._data_event_loop = asyncio.new_event_loop()
264
+ self._data_event_thread = threading.Thread(target=start_loop, args=(self._data_event_loop,))
265
+ self._data_event_thread.daemon = True
266
+ self._data_event_thread.name = self._detail_device.name + " data"
267
+ self._data_event_thread.start()
268
+ else:
269
+ print("Invalid device service uuid:" + self._detail_device.name + str(self._adv))
270
+ return False
271
+
272
+
273
+ if (self._data_ctx == None and self._gforce != None):
274
+ self._data_ctx = SensorProfileDataCtx(self._gforce, self._device.Address, self._raw_data_buf)
275
+ if (self._data_ctx.isUniversalStream):
276
+ timer(self._data_event_loop, 0, self._process_universal_data())
277
+
278
+ result = sync_timer(self._gforce_event_loop, 0, self._connect())
279
+ return result
280
+
281
+ async def _waitForDisconnect(self) -> bool:
282
+ while(self.deviceState != DeviceStateEx.Disconnected):
283
+ asyncio.sleep(1)
284
+ return True
285
+
286
+ async def _disconnect(self) -> bool:
287
+ if self.deviceState != DeviceStateEx.Connected and self.deviceState != DeviceStateEx.Ready:
288
+ return True
289
+ if self._data_ctx == None:
290
+ return False
291
+ self._set_device_state(DeviceStateEx.Disconnecting)
292
+ await self._gforce.disconnect()
293
+ await asyncio.wait_for(self._waitForDisconnect(), 5)
294
+ return True
295
+ def disconnect(self) -> bool:
296
+ """
297
+ 断开传感器连接。
298
+
299
+ :return: bool: 如果断开连接成功,返回 True;否则返回 False。
300
+
301
+ """
302
+ return sync_timer(self._gforce_event_loop, 0, self._disconnect())
303
+
304
+ async def _process_data(self):
305
+ while(self._data_ctx._is_running and self._data_ctx.isDataTransfering):
306
+ self._data_ctx.process_data(self._data_buffer)
307
+ while(self._data_ctx._is_running and self._data_ctx.isDataTransfering):
308
+ sensorData: SensorData = None
309
+ try:
310
+ sensorData = self._data_buffer.get_nowait()
311
+ except Exception as e:
312
+ break
313
+ if (sensorData != None and self._on_data_callback != None):
314
+ try:
315
+ self._event_loop.call_soon_threadsafe(self._on_data_callback,self, sensorData)
316
+ except Exception as e:
317
+ print(e)
318
+ self._data_buffer.task_done()
319
+
320
+ async def _process_universal_data(self):
321
+ self._data_ctx.processUniversalData(self._data_buffer, self._event_loop, self, self._on_data_callback)
322
+
323
+ async def _startDataNotification(self) -> bool:
324
+ result = await self._data_ctx.start_streaming()
325
+ self._data_buffer.queue.clear()
326
+ self._data_ctx.clear()
327
+ if (not self._data_ctx.isUniversalStream):
328
+ timer(self._data_event_loop, 0, self._process_data())
329
+ return result
330
+
331
+ def startDataNotification(self) -> bool:
332
+ """
333
+ 开始数据通知。
334
+
335
+ :return: bool: 如果开始数据通知成功,返回 True;否则返回 False。
336
+
337
+ """
338
+ if self.deviceState != DeviceStateEx.Ready:
339
+ return False
340
+ if self._data_ctx == None:
341
+ return False
342
+ if not self._data_ctx.hasInit():
343
+ return False
344
+
345
+ if self._data_ctx.isDataTransfering:
346
+ return True
347
+
348
+ if (self._data_event_loop == None):
349
+ self._data_event_loop = asyncio.new_event_loop()
350
+ self._data_event_thread = threading.Thread(target=start_loop, args=(self._data_event_loop,))
351
+ self._data_event_thread.daemon = True
352
+ self._data_event_thread.name = self.BLEDevice.Name + " data"
353
+ self._data_event_thread.start()
354
+
355
+ return sync_timer(self._gforce_event_loop, 0, self._startDataNotification())
356
+
357
+ async def _stopDataNotification(self) -> bool:
358
+
359
+ return not await self._data_ctx.stop_streaming()
360
+
361
+ def stopDataNotification(self) -> bool:
362
+ """
363
+ 停止数据通知。
364
+
365
+ :return: bool: 如果停止数据通知成功,返回 True;否则返回 False。
366
+
367
+ """
368
+ if self.deviceState != DeviceStateEx.Ready:
369
+ return False
370
+ if self._data_ctx == None:
371
+ return False
372
+ if not self._data_ctx.hasInit():
373
+ return False
374
+
375
+ if not self._data_ctx.isDataTransfering:
376
+ return True
377
+
378
+ return sync_timer(self._gforce_event_loop, 0, self._stopDataNotification())
379
+
380
+ async def _refresh_power(self):
381
+ self._power = await self._gforce.get_battery_level()
382
+
383
+ if self._on_power_changed != None:
384
+ try:
385
+ self._event_loop.call_soon_threadsafe(self._on_power_changed, self, self._power)
386
+ except Exception as e:
387
+ print(e)
388
+
389
+ if self.deviceState == DeviceStateEx.Ready:
390
+ timer(self._gforce_event_loop, self._power_interval / 1000, self._refresh_power())
391
+
392
+ async def _init(self, packageSampleCount: int, powerRefreshInterval: int) -> bool:
393
+ if self.deviceState != DeviceStateEx.Ready:
394
+ return False
395
+ if self._data_ctx == None:
396
+ return False
397
+ if self._data_ctx.hasInit():
398
+ return True
399
+
400
+ if (await self._data_ctx.init(packageSampleCount)):
401
+ self._power_interval = powerRefreshInterval
402
+ timer(self._gforce_event_loop, self._power_interval / 1000, self._refresh_power())
403
+
404
+ return self._data_ctx.hasInit()
405
+ def init(self, packageSampleCount: int, powerRefreshInterval: int) -> bool:
406
+ """
407
+ 初始化数据采集。
408
+
409
+ :param packageSampleCount (int): 数据包中的样本数量。
410
+ :param powerRefreshInterval (int): 电量刷新间隔。
411
+
412
+ :return: bool: 初始化结果。True 表示成功,False 表示失败。
413
+
414
+ """
415
+ return sync_timer(self._gforce_event_loop, 0, self._init(packageSampleCount, powerRefreshInterval))
416
+
417
+ def getBatteryLevel(self) -> int:
418
+ """
419
+ 获取传感器的电池电量。
420
+
421
+ :return: int: 传感器的电池电量。 正常0-100,-1为未知。
422
+
423
+ """
424
+ return self._power
425
+
426
+
427
+ def getDeviceInfo(self) -> Optional[DeviceInfo]:
428
+ """
429
+ 获取传感器的设备信息。
430
+
431
+ :return: DeviceInfo: 传感器的设备信息。
432
+
433
+ """
434
+ if (self.hasInited):
435
+ return self._data_ctx._device_info
436
+ return None
437
+
438
+
439
+ def setParam(self, key: str, value: str) -> str:
440
+ """
441
+ 设置传感器的参数。
442
+
443
+ :param key (str): 参数的键。
444
+ :param value (str): 参数的值。
445
+
446
+ :return: str: 设置参数的结果。
447
+
448
+ """
449
+ return ""
sensor/utils.py ADDED
@@ -0,0 +1,28 @@
1
+ import asyncio
2
+ import signal
3
+
4
+ async def delay(time:float, function)->any:
5
+ if (time > 0):
6
+ await asyncio.sleep(time)
7
+ return await function
8
+
9
+ def timer(_loop: asyncio.AbstractEventLoop, time: float, function):
10
+ try:
11
+ asyncio.run_coroutine_threadsafe(delay(time, function), _loop)
12
+ except Exception as e:
13
+ print(e)
14
+ pass
15
+
16
+
17
+ def sync_timer(_loop: asyncio.AbstractEventLoop, time: float, function)->any:
18
+ try:
19
+ f = asyncio.run_coroutine_threadsafe(delay(time, function), _loop)
20
+ return f.result()
21
+ except Exception as e:
22
+ print(e)
23
+ pass
24
+
25
+ def start_loop(loop: asyncio.BaseEventLoop):
26
+ asyncio.set_event_loop(loop)
27
+ loop.run_forever()
28
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OYMotion Technologies Co., Ltd..
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.