sensor-sdk 0.0.43__tar.gz → 0.0.45__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.43 → sensor_sdk-0.0.45}/PKG-INFO +2 -2
- sensor_sdk-0.0.45/sensor/bleak_host.py +279 -0
- sensor_sdk-0.0.45/sensor/bleak_process.py +1001 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor/gforce.py +24 -4
- sensor_sdk-0.0.45/sensor/sensor_controller.py +255 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor/sensor_data.py +24 -24
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor/sensor_data_context.py +107 -223
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor/sensor_device.py +23 -28
- sensor_sdk-0.0.45/sensor/sensor_profile.py +499 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor/sensor_utils.py +1 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor_sdk.egg-info/PKG-INFO +2 -2
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor_sdk.egg-info/SOURCES.txt +2 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/setup.py +2 -2
- sensor_sdk-0.0.43/sensor/sensor_controller.py +0 -276
- sensor_sdk-0.0.43/sensor/sensor_profile.py +0 -686
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/LICENSE.txt +0 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/README.md +0 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor/__init__.py +0 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor_sdk.egg-info/dependency_links.txt +0 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor_sdk.egg-info/requires.txt +0 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor_sdk.egg-info/top_level.txt +0 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/sensor_sdk.egg-info/zip-safe +0 -0
- {sensor_sdk-0.0.43 → sensor_sdk-0.0.45}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sensor-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.45
|
|
4
4
|
Summary: Python sdk for Synchroni
|
|
5
5
|
Home-page: https://github.com/oymotion/SynchroniSDKPython
|
|
6
6
|
Author: Martin Ye
|
|
7
7
|
Author-email: yecq_82@hotmail.com
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.10.0
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.txt
|
|
11
11
|
Requires-Dist: numpy
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import multiprocessing
|
|
3
|
+
import queue
|
|
4
|
+
import threading
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from sensor.bleak_process import BleakProcess
|
|
8
|
+
from sensor import sensor_utils
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BleakHost:
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self._cmd_queue = multiprocessing.Queue(maxsize=50)
|
|
16
|
+
self._result_queue = multiprocessing.Queue(maxsize=sensor_utils.BLEAK_RESULT_QUEUE_MAXSIZE)
|
|
17
|
+
self._bleak_process = BleakProcess(self._cmd_queue, self._result_queue)
|
|
18
|
+
self._started = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
self._result_loop = None
|
|
22
|
+
self._result_thread = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
self._cmd_loop = None
|
|
26
|
+
self._cmd_thread = None
|
|
27
|
+
|
|
28
|
+
# Pending commands: cmd_id -> (waiter_type, waiter, container, loop)
|
|
29
|
+
self._pending_lock = threading.Lock()
|
|
30
|
+
self._pending = {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
self.on_scan_once_result = None # callable(msg)
|
|
34
|
+
self.on_scan_result = None # callable(msg)
|
|
35
|
+
self.on_device_message = None # callable(device_mac, msg)
|
|
36
|
+
|
|
37
|
+
# ------------------------------------------------------------------
|
|
38
|
+
# Lifecycle
|
|
39
|
+
# ------------------------------------------------------------------
|
|
40
|
+
def start(self):
|
|
41
|
+
if self._started:
|
|
42
|
+
return
|
|
43
|
+
self._bleak_process.start()
|
|
44
|
+
self._started = True
|
|
45
|
+
self._start_result_loop()
|
|
46
|
+
self._start_cmd_loop()
|
|
47
|
+
|
|
48
|
+
def stop(self):
|
|
49
|
+
if not self._started:
|
|
50
|
+
return
|
|
51
|
+
try:
|
|
52
|
+
self._cmd_queue.put({"type": "terminate"})
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
if self._bleak_process.is_alive():
|
|
57
|
+
self._bleak_process.join(timeout=5)
|
|
58
|
+
if self._bleak_process.is_alive():
|
|
59
|
+
self._bleak_process.terminate()
|
|
60
|
+
|
|
61
|
+
self._stop_result_loop()
|
|
62
|
+
self._stop_cmd_loop()
|
|
63
|
+
self._started = False
|
|
64
|
+
|
|
65
|
+
# ------------------------------------------------------------------
|
|
66
|
+
# Result loop
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
def _start_result_loop(self):
|
|
69
|
+
from sensor import sensor_utils
|
|
70
|
+
result_loop = asyncio.new_event_loop()
|
|
71
|
+
result_thread = threading.Thread(target=sensor_utils.start_loop, args=(result_loop,))
|
|
72
|
+
result_thread.daemon = True
|
|
73
|
+
result_thread.name = "BleakHost result"
|
|
74
|
+
result_thread.start()
|
|
75
|
+
self._result_loop = result_loop
|
|
76
|
+
self._result_thread = result_thread
|
|
77
|
+
asyncio.run_coroutine_threadsafe(self._consume_results(), result_loop)
|
|
78
|
+
|
|
79
|
+
def _stop_result_loop(self):
|
|
80
|
+
loop = getattr(self, '_result_loop', None)
|
|
81
|
+
thread = getattr(self, '_result_thread', None)
|
|
82
|
+
if loop is not None and not loop.is_closed():
|
|
83
|
+
try:
|
|
84
|
+
loop.call_soon_threadsafe(loop.stop)
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
if thread is not None and thread.is_alive():
|
|
88
|
+
try:
|
|
89
|
+
thread.join(timeout=2)
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
self._result_loop = None
|
|
93
|
+
self._result_thread = None
|
|
94
|
+
|
|
95
|
+
async def _consume_results(self):
|
|
96
|
+
|
|
97
|
+
while True:
|
|
98
|
+
try:
|
|
99
|
+
msg = await asyncio.get_event_loop().run_in_executor(
|
|
100
|
+
None, self._result_queue.get, True, 0.5
|
|
101
|
+
)
|
|
102
|
+
except queue.Empty:
|
|
103
|
+
continue
|
|
104
|
+
except Exception:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
msg_type = msg.get("type")
|
|
108
|
+
if msg_type == "scan_once_result":
|
|
109
|
+
if self.on_scan_once_result is not None:
|
|
110
|
+
try:
|
|
111
|
+
self.on_scan_once_result(msg)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
elif msg_type == "devices":
|
|
115
|
+
if self.on_scan_result is not None:
|
|
116
|
+
try:
|
|
117
|
+
self.on_scan_result(msg)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
elif msg_type == "command_result":
|
|
121
|
+
self._handle_command_result(msg)
|
|
122
|
+
elif msg_type in ("state_changed", "power_changed", "sensor_data", "error"):
|
|
123
|
+
if self.on_device_message is not None:
|
|
124
|
+
device_mac = msg.get("device_mac")
|
|
125
|
+
try:
|
|
126
|
+
self.on_device_message(device_mac, msg)
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
def _handle_command_result(self, msg):
|
|
131
|
+
|
|
132
|
+
cmd_id = msg.get("cmd_id")
|
|
133
|
+
with self._pending_lock:
|
|
134
|
+
pending = self._pending.pop(cmd_id, None)
|
|
135
|
+
if pending is None:
|
|
136
|
+
return
|
|
137
|
+
waiter_type, waiter, container, loop = pending
|
|
138
|
+
if waiter_type == "async_event":
|
|
139
|
+
container["result"] = msg
|
|
140
|
+
if loop is not None and not loop.is_closed():
|
|
141
|
+
loop.call_soon_threadsafe(waiter.set)
|
|
142
|
+
elif waiter_type == "future":
|
|
143
|
+
if loop is not None and not loop.is_closed() and not waiter.done():
|
|
144
|
+
loop.call_soon_threadsafe(waiter.set_result, msg)
|
|
145
|
+
|
|
146
|
+
# ------------------------------------------------------------------
|
|
147
|
+
# Command loop
|
|
148
|
+
# ------------------------------------------------------------------
|
|
149
|
+
def _start_cmd_loop(self):
|
|
150
|
+
from sensor import sensor_utils
|
|
151
|
+
cmd_loop = asyncio.new_event_loop()
|
|
152
|
+
cmd_thread = threading.Thread(target=sensor_utils.start_loop, args=(cmd_loop,))
|
|
153
|
+
cmd_thread.daemon = True
|
|
154
|
+
cmd_thread.name = "BleakHost cmd"
|
|
155
|
+
cmd_thread.start()
|
|
156
|
+
self._cmd_loop = cmd_loop
|
|
157
|
+
self._cmd_thread = cmd_thread
|
|
158
|
+
|
|
159
|
+
def _stop_cmd_loop(self):
|
|
160
|
+
loop = getattr(self, '_cmd_loop', None)
|
|
161
|
+
thread = getattr(self, '_cmd_thread', None)
|
|
162
|
+
if loop is not None and not loop.is_closed():
|
|
163
|
+
try:
|
|
164
|
+
loop.call_soon_threadsafe(loop.stop)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
if thread is not None and thread.is_alive():
|
|
168
|
+
try:
|
|
169
|
+
thread.join(timeout=2)
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
self._cmd_loop = None
|
|
173
|
+
self._cmd_thread = None
|
|
174
|
+
|
|
175
|
+
def _ensure_cmd_loop(self):
|
|
176
|
+
if self._cmd_loop is None or self._cmd_loop.is_closed():
|
|
177
|
+
self._start_cmd_loop()
|
|
178
|
+
|
|
179
|
+
# ------------------------------------------------------------------
|
|
180
|
+
# Public APIs
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
def scan_once(self, period):
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
self._cmd_queue.put_nowait({"type": "scan_once", "period": period})
|
|
186
|
+
except queue.Full:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def start_scan(self, period):
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
self._cmd_queue.put_nowait({"type": "start_scan", "period": period})
|
|
193
|
+
except queue.Full:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
def stop_scan(self):
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
self._cmd_queue.put_nowait({"type": "stop_scan"})
|
|
200
|
+
except queue.Full:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
def send_command(self, cmd: dict):
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
self._cmd_queue.put_nowait(cmd)
|
|
207
|
+
except queue.Full:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
def send_command_sync(self, cmd: dict, timeout: float = 10.0) -> dict:
|
|
211
|
+
|
|
212
|
+
if not self._started:
|
|
213
|
+
return {}
|
|
214
|
+
self._ensure_cmd_loop()
|
|
215
|
+
|
|
216
|
+
async def _do_send():
|
|
217
|
+
cmd_id = str(uuid.uuid4())
|
|
218
|
+
cmd["cmd_id"] = cmd_id
|
|
219
|
+
event = asyncio.Event()
|
|
220
|
+
container = {}
|
|
221
|
+
with self._pending_lock:
|
|
222
|
+
self._pending[cmd_id] = ("async_event", event, container, asyncio.get_event_loop())
|
|
223
|
+
try:
|
|
224
|
+
self._cmd_queue.put(cmd, timeout=1.0)
|
|
225
|
+
except queue.Full:
|
|
226
|
+
with self._pending_lock:
|
|
227
|
+
self._pending.pop(cmd_id, None)
|
|
228
|
+
return {}
|
|
229
|
+
try:
|
|
230
|
+
await asyncio.wait_for(event.wait(), timeout=timeout)
|
|
231
|
+
except asyncio.TimeoutError:
|
|
232
|
+
with self._pending_lock:
|
|
233
|
+
self._pending.pop(cmd_id, None)
|
|
234
|
+
return {}
|
|
235
|
+
with self._pending_lock:
|
|
236
|
+
self._pending.pop(cmd_id, None)
|
|
237
|
+
return container.get("result", {})
|
|
238
|
+
|
|
239
|
+
future = asyncio.run_coroutine_threadsafe(_do_send(), self._cmd_loop)
|
|
240
|
+
try:
|
|
241
|
+
return future.result(timeout=timeout)
|
|
242
|
+
except Exception:
|
|
243
|
+
return {}
|
|
244
|
+
|
|
245
|
+
async def send_command_async(self, cmd: dict, timeout: float = 10.0) -> dict:
|
|
246
|
+
|
|
247
|
+
if not self._started:
|
|
248
|
+
return {}
|
|
249
|
+
self._ensure_cmd_loop()
|
|
250
|
+
|
|
251
|
+
async def _do_send():
|
|
252
|
+
cmd_id = str(uuid.uuid4())
|
|
253
|
+
cmd["cmd_id"] = cmd_id
|
|
254
|
+
future = asyncio.get_running_loop().create_future()
|
|
255
|
+
with self._pending_lock:
|
|
256
|
+
self._pending[cmd_id] = ("future", future, None, asyncio.get_running_loop())
|
|
257
|
+
try:
|
|
258
|
+
self._cmd_queue.put(cmd, timeout=1.0)
|
|
259
|
+
except queue.Full:
|
|
260
|
+
with self._pending_lock:
|
|
261
|
+
self._pending.pop(cmd_id, None)
|
|
262
|
+
return {}
|
|
263
|
+
try:
|
|
264
|
+
return await asyncio.wait_for(future, timeout=timeout)
|
|
265
|
+
except asyncio.TimeoutError:
|
|
266
|
+
with self._pending_lock:
|
|
267
|
+
self._pending.pop(cmd_id, None)
|
|
268
|
+
return {}
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
caller_loop = asyncio.get_running_loop()
|
|
272
|
+
except RuntimeError:
|
|
273
|
+
caller_loop = None
|
|
274
|
+
|
|
275
|
+
future = asyncio.run_coroutine_threadsafe(_do_send(), self._cmd_loop)
|
|
276
|
+
if caller_loop is not None:
|
|
277
|
+
return await asyncio.wrap_future(future, loop=caller_loop)
|
|
278
|
+
else:
|
|
279
|
+
return future.result()
|