motorcortex-python 0.25.0__tar.gz → 0.25.4__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.
- {motorcortex_python-0.25.0/motorcortex_python.egg-info → motorcortex_python-0.25.4}/PKG-INFO +12 -3
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/__init__.py +41 -8
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/init_threads.py +2 -2
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/request.py +115 -45
- motorcortex_python-0.25.4/motorcortex/version.py +1 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4/motorcortex_python.egg-info}/PKG-INFO +12 -3
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex_python.egg-info/SOURCES.txt +1 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex_python.egg-info/requires.txt +1 -1
- motorcortex_python-0.25.4/pyproject.toml +3 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/setup.py +3 -2
- motorcortex_python-0.25.0/motorcortex/version.py +0 -1
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/LICENSE +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/MANIFEST.in +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/README.md +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/message_types.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/motorcortex_hash.json +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/motorcortex_pb2.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/nng_url.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/parameter_tree.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/reply.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/setup_logger.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/state_callback_handler.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/subscribe.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/subscription.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/timespec.py +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex_python.egg-info/dependency_links.txt +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex_python.egg-info/top_level.txt +0 -0
- {motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/setup.cfg +0 -0
{motorcortex_python-0.25.0/motorcortex_python.egg-info → motorcortex_python-0.25.4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: motorcortex-python
|
|
3
|
-
Version: 0.25.
|
|
3
|
+
Version: 0.25.4
|
|
4
4
|
Summary: Python bindings for Motorcortex Engine
|
|
5
5
|
Home-page: https://www.motorcortex.io
|
|
6
6
|
Author: Alexey Zakharov
|
|
@@ -8,8 +8,17 @@ Author-email: alexey.zakharov@vectioneer.com
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: pynng
|
|
11
|
+
Requires-Dist: pynng>=0.9.0
|
|
12
12
|
Requires-Dist: protobuf>=3.20
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: description
|
|
16
|
+
Dynamic: description-content-type
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: license
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: summary
|
|
13
22
|
|
|
14
23
|
# motorcortex-python
|
|
15
24
|
|
|
@@ -84,11 +84,21 @@ def connect(
|
|
|
84
84
|
Establishes connections to Motorcortex request and subscribe endpoints, performs login, and loads the parameter tree.
|
|
85
85
|
|
|
86
86
|
Args:
|
|
87
|
-
url (str): Connection URL in the format 'address:req_port:sub_port'.
|
|
88
|
-
motorcortex_types (
|
|
87
|
+
url (str): Connection URL in the format 'address:req_port:sub_port' or 'wss://address'.
|
|
88
|
+
motorcortex_types (MessageTypes): Motorcortex message types instance.
|
|
89
89
|
param_tree (ParameterTree): ParameterTree instance to load parameters into.
|
|
90
90
|
reconnect (bool, optional): Whether to enable automatic reconnection. Defaults to True.
|
|
91
|
-
**kwargs: Additional keyword arguments
|
|
91
|
+
**kwargs: Additional keyword arguments:
|
|
92
|
+
login (str): Username for authentication.
|
|
93
|
+
password (str): Password for authentication.
|
|
94
|
+
certificate (str, optional): Path to a TLS certificate file for secure connections.
|
|
95
|
+
timeout_ms (int, optional): Connection timeout in milliseconds. Defaults to 1000.
|
|
96
|
+
recv_timeout_ms (int, optional): Receive timeout in milliseconds. Defaults to 500.
|
|
97
|
+
token_update_interval_ms (int, optional): Session token refresh interval in milliseconds. Defaults to 30000.
|
|
98
|
+
state_update (Callable, optional): Custom callback for connection state changes.
|
|
99
|
+
If provided, disables the built-in reconnect logic.
|
|
100
|
+
req_number_of_threads (int, optional): Thread pool size for request connection. Defaults to 2.
|
|
101
|
+
sub_number_of_threads (int, optional): Thread pool size for subscribe connection. Defaults to 2.
|
|
92
102
|
|
|
93
103
|
Returns:
|
|
94
104
|
tuple: (req, sub)
|
|
@@ -100,20 +110,40 @@ def connect(
|
|
|
100
110
|
|
|
101
111
|
Examples:
|
|
102
112
|
>>> from motorcortex import connect, MessageTypes, ParameterTree
|
|
103
|
-
>>> url = "127.0.0.1:5555:5556"
|
|
104
113
|
>>> types = MessageTypes()
|
|
105
114
|
>>> tree = ParameterTree()
|
|
106
|
-
>>> req, sub = connect(
|
|
107
|
-
|
|
115
|
+
>>> req, sub = connect("wss://192.168.2.100", types, tree,
|
|
116
|
+
... certificate="mcx.cert.crt", timeout_ms=1000,
|
|
117
|
+
... login="admin", password="admin",
|
|
118
|
+
... token_update_interval_ms=15000)
|
|
108
119
|
"""
|
|
109
120
|
|
|
121
|
+
token_interval_sec = kwargs.get("token_update_interval_ms", 30000) / 1000.0
|
|
122
|
+
|
|
110
123
|
initial_connect_done = [False] # Now reconnections will trigger callback login
|
|
111
124
|
|
|
112
125
|
if reconnect and not kwargs.get("state_update"):
|
|
113
126
|
|
|
114
127
|
def stateUpdate(req, sub, state):
|
|
115
128
|
if state == ConnectionState.CONNECTION_OK and initial_connect_done[0]:
|
|
116
|
-
|
|
129
|
+
# Try to restore session using token, fall back to login
|
|
130
|
+
restored = False
|
|
131
|
+
if req.token:
|
|
132
|
+
try:
|
|
133
|
+
restore_reply = req.restoreSession(req.token)
|
|
134
|
+
restore_msg = restore_reply.get(timeout_ms=5000)
|
|
135
|
+
motorcortex_msg = motorcortex_types.motorcortex()
|
|
136
|
+
if restore_msg and restore_msg.status == motorcortex_msg.OK:
|
|
137
|
+
restored = True
|
|
138
|
+
logger.debug("[SESSION] Session restored using token")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.debug(f"[SESSION] Token restore failed: {e}")
|
|
141
|
+
|
|
142
|
+
if not restored:
|
|
143
|
+
logger.debug("[SESSION] Falling back to login")
|
|
144
|
+
req.login(kwargs.get("login"), kwargs.get("password")).get()
|
|
145
|
+
|
|
146
|
+
req._startTokenRefresh(token_interval_sec)
|
|
117
147
|
sub.resubscribe()
|
|
118
148
|
|
|
119
149
|
kwargs.update(state_update=stateUpdate)
|
|
@@ -143,6 +173,9 @@ def connect(
|
|
|
143
173
|
tree = param_tree_reply.get()
|
|
144
174
|
param_tree.load(tree)
|
|
145
175
|
|
|
176
|
+
# Start session token refresh
|
|
177
|
+
req._startTokenRefresh(token_interval_sec)
|
|
178
|
+
|
|
146
179
|
initial_connect_done[0] = True # Now reconnections will trigger callback login
|
|
147
180
|
|
|
148
181
|
return req, sub
|
|
@@ -192,4 +225,4 @@ def statusToStr(motorcortex_msg: object, code: int) -> str:
|
|
|
192
225
|
|
|
193
226
|
status += str(' (%s)' % hex(code))
|
|
194
227
|
|
|
195
|
-
return status
|
|
228
|
+
return status
|
|
@@ -16,5 +16,5 @@ def init_nng_threads(task=2, expire=1, poller=1, resolver=1):
|
|
|
16
16
|
lib.nng_init_set_parameter(lib.NNG_INIT_NUM_EXPIRE_THREADS, expire)
|
|
17
17
|
lib.nng_init_set_parameter(lib.NNG_INIT_NUM_POLLER_THREADS, poller)
|
|
18
18
|
lib.nng_init_set_parameter(lib.NNG_INIT_NUM_RESOLVER_THREADS, resolver)
|
|
19
|
-
except:
|
|
20
|
-
logger.error(
|
|
19
|
+
except AttributeError:
|
|
20
|
+
logger.error("[INIT-THREADS] Cannot adjust thread count: interface unavailable")
|
|
@@ -17,7 +17,7 @@ import hashlib
|
|
|
17
17
|
import json
|
|
18
18
|
import tempfile
|
|
19
19
|
import os
|
|
20
|
-
from threading import Event
|
|
20
|
+
from threading import Event, Timer
|
|
21
21
|
from concurrent.futures import ThreadPoolExecutor, Future
|
|
22
22
|
from typing import Any, Callable, List, Optional, Union
|
|
23
23
|
from pynng import Req0, TLSConfig
|
|
@@ -33,7 +33,7 @@ from motorcortex.nng_url import NngUrl
|
|
|
33
33
|
|
|
34
34
|
class ConnectionState(Enum):
|
|
35
35
|
"""Enumeration of connection states.
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
- CONNECTING: Connection is being established.
|
|
38
38
|
- CONNECTION_OK: Connection is successfully established.
|
|
39
39
|
- CONNECTION_LOST: Connection was lost.
|
|
@@ -152,7 +152,10 @@ class Request:
|
|
|
152
152
|
self.__pool: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=number_of_threads,
|
|
153
153
|
thread_name_prefix="mcx_req")
|
|
154
154
|
self.__callback_handler: StateCallbackHandler = StateCallbackHandler()
|
|
155
|
-
|
|
155
|
+
self.__token: Optional[str] = None
|
|
156
|
+
self.__token_timer: Optional[Timer] = None
|
|
157
|
+
self.__token_update_interval_sec: float = 30.0
|
|
158
|
+
logger.debug("[REQUEST] Request object initialized with state: DISCONNECTED")
|
|
156
159
|
|
|
157
160
|
def url(self) -> Optional[str]:
|
|
158
161
|
"""Return the current connection URL."""
|
|
@@ -176,70 +179,70 @@ class Request:
|
|
|
176
179
|
Returns:
|
|
177
180
|
Reply: A promise that resolves when the connection is established.
|
|
178
181
|
"""
|
|
179
|
-
logger.debug(f"[CONNECT] Starting connection to {url}")
|
|
180
|
-
logger.debug(f"[CONNECT] Current state: {self.__connection_state.name}")
|
|
182
|
+
logger.debug(f"[REQUEST-CONNECT] Starting connection to {url}")
|
|
183
|
+
logger.debug(f"[REQUEST-CONNECT] Current state: {self.__connection_state.name}")
|
|
181
184
|
|
|
182
185
|
self.__connection_state = ConnectionState.CONNECTING
|
|
183
186
|
conn_timeout_ms, recv_timeout_ms, certificate, state_update = self.parse(**kwargs)
|
|
184
187
|
|
|
185
|
-
logger.debug(f"[CONNECT] Parameters - timeout: {conn_timeout_ms} ms, recv_timeout: {recv_timeout_ms} ms, "
|
|
188
|
+
logger.debug(f"[REQUEST-CONNECT] Parameters - timeout: {conn_timeout_ms} ms, recv_timeout: {recv_timeout_ms} ms, "
|
|
186
189
|
f"has_cert: {certificate is not None}, has_state_update: {state_update is not None}")
|
|
187
190
|
|
|
188
191
|
if state_update:
|
|
189
|
-
logger.debug("[CONNECT] Starting state callback handler")
|
|
192
|
+
logger.debug("[REQUEST-CONNECT] Starting state callback handler")
|
|
190
193
|
self.__callback_handler.start(state_update)
|
|
191
194
|
|
|
192
195
|
self.__url = url
|
|
193
196
|
tls_config = None
|
|
194
197
|
if certificate:
|
|
195
198
|
parsed = NngUrl(url)
|
|
196
|
-
logger.debug(f"[CONNECT] Using TLS with certificate: {certificate}")
|
|
199
|
+
logger.debug(f"[REQUEST-CONNECT] Using TLS with certificate: {certificate}")
|
|
197
200
|
tls_config = TLSConfig(TLSConfig.MODE_CLIENT, ca_files=certificate, server_name=parsed.hostname)
|
|
198
201
|
|
|
199
|
-
logger.debug(f"[CONNECT] Creating Req0 socket with recv_timeout={recv_timeout_ms}ms")
|
|
202
|
+
logger.debug(f"[REQUEST-CONNECT] Creating Req0 socket with recv_timeout={recv_timeout_ms}ms")
|
|
200
203
|
self.__socket = Req0(recv_timeout=recv_timeout_ms, tls_config=tls_config)
|
|
201
204
|
self.__connected_event = Event() # Create a fresh event for each connection
|
|
202
205
|
self.__connected = False # Reset state
|
|
203
206
|
|
|
204
|
-
logger.debug("[CONNECT] Socket created, registering callbacks")
|
|
207
|
+
logger.debug("[REQUEST-CONNECT] Socket created, registering callbacks")
|
|
205
208
|
|
|
206
209
|
def pre_connect_cb(_pipe):
|
|
207
|
-
logger.debug(f"[CALLBACK] PRE_CONNECT fired - Connection established")
|
|
210
|
+
logger.debug(f"[REQUEST-CALLBACK] PRE_CONNECT fired - Connection established")
|
|
208
211
|
old_state = self.__connection_state.name
|
|
209
212
|
self.__connected = True
|
|
210
213
|
self.__connection_state = ConnectionState.CONNECTION_OK
|
|
211
|
-
logger.debug(f"[CALLBACK] State transition: {old_state} -> {self.__connection_state.name}")
|
|
214
|
+
logger.debug(f"[REQUEST-CALLBACK] State transition: {old_state} -> {self.__connection_state.name}")
|
|
212
215
|
self.__callback_handler.notify(self, self.connectionState())
|
|
213
216
|
self.__connected_event.set()
|
|
214
|
-
logger.debug("[CALLBACK] Connection event set, connected=True")
|
|
217
|
+
logger.debug("[REQUEST-CALLBACK] Connection event set, connected=True")
|
|
215
218
|
|
|
216
219
|
def post_remove_cb(_pipe):
|
|
217
|
-
logger.debug(f"[CALLBACK] POST_REMOVE fired - Connection lost/failed")
|
|
220
|
+
logger.debug(f"[REQUEST-CALLBACK] POST_REMOVE fired - Connection lost/failed")
|
|
218
221
|
old_state = self.__connection_state.name
|
|
219
222
|
|
|
220
223
|
if self.__connection_state == ConnectionState.DISCONNECTING:
|
|
221
224
|
self.__connection_state = ConnectionState.DISCONNECTED
|
|
222
|
-
logger.debug(f"[CALLBACK] Clean disconnect: {old_state} -> DISCONNECTED")
|
|
225
|
+
logger.debug(f"[REQUEST-CALLBACK] Clean disconnect: {old_state} -> DISCONNECTED")
|
|
223
226
|
elif self.__connection_state == ConnectionState.CONNECTING:
|
|
224
227
|
self.__connection_state = ConnectionState.CONNECTION_FAILED
|
|
225
|
-
logger.debug(f"[CALLBACK] Connection failed: {old_state} -> CONNECTION_FAILED")
|
|
228
|
+
logger.debug(f"[REQUEST-CALLBACK] Connection failed: {old_state} -> CONNECTION_FAILED")
|
|
226
229
|
elif self.__connection_state == ConnectionState.CONNECTION_OK:
|
|
227
230
|
self.__connection_state = ConnectionState.CONNECTION_LOST
|
|
228
|
-
logger.debug(f"[CALLBACK] Connection lost: {old_state} -> CONNECTION_LOST")
|
|
231
|
+
logger.debug(f"[REQUEST-CALLBACK] Connection lost: {old_state} -> CONNECTION_LOST")
|
|
229
232
|
|
|
230
233
|
self.__connected = False
|
|
231
234
|
self.__callback_handler.notify(self, self.connectionState())
|
|
232
235
|
self.__connected_event.set()
|
|
233
|
-
logger.debug("[CALLBACK] Connection event set, connected=False")
|
|
236
|
+
logger.debug("[REQUEST-CALLBACK] Connection event set, connected=False")
|
|
234
237
|
|
|
235
238
|
self.__socket.add_pre_pipe_connect_cb(pre_connect_cb)
|
|
236
239
|
self.__socket.add_post_pipe_remove_cb(post_remove_cb)
|
|
237
|
-
logger.debug("[CONNECT] Callbacks registered successfully")
|
|
240
|
+
logger.debug("[REQUEST-CONNECT] Callbacks registered successfully")
|
|
238
241
|
|
|
239
|
-
logger.debug(f"[CONNECT] Starting dial to {url} (non-blocking)")
|
|
242
|
+
logger.debug(f"[REQUEST-CONNECT] Starting dial to {url} (non-blocking)")
|
|
240
243
|
self.__socket.dial(url, block=False)
|
|
241
244
|
|
|
242
|
-
logger.debug(f"[CONNECT] Submitting waitForConnection to thread pool with timeout={conn_timeout_ms / 1000.0}s")
|
|
245
|
+
logger.debug(f"[REQUEST-CONNECT] Submitting waitForConnection to thread pool with timeout={conn_timeout_ms / 1000.0}s")
|
|
243
246
|
return Reply(self.__pool.submit(self.waitForConnection, self.__connected_event,
|
|
244
247
|
conn_timeout_ms / 1000.0, lambda: self.__connected))
|
|
245
248
|
|
|
@@ -247,29 +250,30 @@ class Request:
|
|
|
247
250
|
"""
|
|
248
251
|
Close the request connection and clean up resources.
|
|
249
252
|
"""
|
|
250
|
-
logger.debug("[CLOSE] Closing connection")
|
|
251
|
-
logger.debug(f"[CLOSE] Current state: {self.__connection_state.name}, connected: {self.__connected}")
|
|
253
|
+
logger.debug("[REQUEST-CLOSE] Closing connection")
|
|
254
|
+
logger.debug(f"[REQUEST-CLOSE] Current state: {self.__connection_state.name}, connected: {self.__connected}")
|
|
252
255
|
|
|
253
256
|
self.__connection_state = ConnectionState.DISCONNECTING
|
|
257
|
+
self._stopTokenRefresh()
|
|
254
258
|
if self.__connected_event:
|
|
255
259
|
self.__connected = False
|
|
256
260
|
self.__connected_event.set()
|
|
257
|
-
logger.debug("[CLOSE] Connection event set for shutdown")
|
|
261
|
+
logger.debug("[REQUEST-CLOSE] Connection event set for shutdown")
|
|
258
262
|
else:
|
|
259
|
-
logger.debug("[CLOSE] No connection event to set")
|
|
263
|
+
logger.debug("[REQUEST-CLOSE] No connection event to set")
|
|
260
264
|
|
|
261
265
|
if self.__socket:
|
|
262
|
-
logger.debug("[CLOSE] Closing socket")
|
|
266
|
+
logger.debug("[REQUEST-CLOSE] Closing socket")
|
|
263
267
|
self.__socket.close()
|
|
264
268
|
else:
|
|
265
|
-
logger.debug("[CLOSE] No socket to close")
|
|
269
|
+
logger.debug("[REQUEST-CLOSE] No socket to close")
|
|
266
270
|
|
|
267
|
-
logger.debug("[CLOSE] Stopping callback handler")
|
|
271
|
+
logger.debug("[REQUEST-CLOSE] Stopping callback handler")
|
|
268
272
|
self.__callback_handler.stop()
|
|
269
273
|
|
|
270
|
-
logger.debug("[CLOSE] Shutting down thread pool (blocking)")
|
|
274
|
+
logger.debug("[REQUEST-CLOSE] Shutting down thread pool (blocking)")
|
|
271
275
|
self.__pool.shutdown(wait=True)
|
|
272
|
-
logger.debug("[CLOSE] Connection closed successfully")
|
|
276
|
+
logger.debug("[REQUEST-CLOSE] Connection closed successfully")
|
|
273
277
|
|
|
274
278
|
def send(self, encoded_msg: Any, do_not_decode_reply: bool = False) -> Optional[Reply]:
|
|
275
279
|
"""
|
|
@@ -285,7 +289,7 @@ class Request:
|
|
|
285
289
|
if self.__socket is not None:
|
|
286
290
|
return Reply(self.__pool.submit(self.__send, self.__socket, encoded_msg,
|
|
287
291
|
None if do_not_decode_reply else self.__protobuf_types))
|
|
288
|
-
logger.debug("[SEND] Attempted to send on null socket - connection not established?")
|
|
292
|
+
logger.debug("[REQUEST-SEND] Attempted to send on null socket - connection not established?")
|
|
289
293
|
return None
|
|
290
294
|
|
|
291
295
|
def login(self, login: str, password: str) -> Reply:
|
|
@@ -527,6 +531,72 @@ class Request:
|
|
|
527
531
|
# encoding and sending data
|
|
528
532
|
return self.send(self.__protobuf_types.encode(remove_group_msg))
|
|
529
533
|
|
|
534
|
+
@property
|
|
535
|
+
def token(self) -> Optional[str]:
|
|
536
|
+
"""Return the current session token."""
|
|
537
|
+
return self.__token
|
|
538
|
+
|
|
539
|
+
def getSessionToken(self) -> Reply:
|
|
540
|
+
"""Request a session token from the server."""
|
|
541
|
+
msg = self.__protobuf_types.createType('motorcortex.GetSessionTokenMsg')
|
|
542
|
+
return self.send(self.__protobuf_types.encode(msg))
|
|
543
|
+
|
|
544
|
+
def restoreSession(self, token: str) -> Reply:
|
|
545
|
+
"""Restore a session using a saved token."""
|
|
546
|
+
msg = self.__protobuf_types.createType('motorcortex.RestoreSessionMsg')
|
|
547
|
+
msg.token = token
|
|
548
|
+
return self.send(self.__protobuf_types.encode(msg))
|
|
549
|
+
|
|
550
|
+
def _startTokenRefresh(self, interval_sec: float = 30.0) -> None:
|
|
551
|
+
"""Start periodic session token refresh.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
interval_sec: Interval between token refreshes in seconds.
|
|
555
|
+
"""
|
|
556
|
+
self.__token_update_interval_sec = interval_sec
|
|
557
|
+
self._stopTokenRefresh()
|
|
558
|
+
# Fetch token immediately, then schedule periodic refresh
|
|
559
|
+
self.__fetchToken()
|
|
560
|
+
self.__scheduleTokenRefresh()
|
|
561
|
+
|
|
562
|
+
def _stopTokenRefresh(self) -> None:
|
|
563
|
+
"""Stop periodic session token refresh."""
|
|
564
|
+
if self.__token_timer:
|
|
565
|
+
self.__token_timer.cancel()
|
|
566
|
+
self.__token_timer = None
|
|
567
|
+
|
|
568
|
+
def __scheduleTokenRefresh(self) -> None:
|
|
569
|
+
"""Schedule the next token refresh."""
|
|
570
|
+
self._stopTokenRefresh()
|
|
571
|
+
self.__token_timer = Timer(self.__token_update_interval_sec, self.__onTokenTimer)
|
|
572
|
+
self.__token_timer.daemon = True
|
|
573
|
+
self.__token_timer.start()
|
|
574
|
+
logger.debug(f"[REQUEST-KEEPALIVE] Next token refresh in {self.__token_update_interval_sec}s")
|
|
575
|
+
|
|
576
|
+
def __onTokenTimer(self) -> None:
|
|
577
|
+
"""Timer callback: fetch token and reschedule."""
|
|
578
|
+
self.__fetchToken()
|
|
579
|
+
# Always reschedule if not disconnecting/disconnected
|
|
580
|
+
if self.__connection_state not in (ConnectionState.DISCONNECTING, ConnectionState.DISCONNECTED):
|
|
581
|
+
self.__scheduleTokenRefresh()
|
|
582
|
+
|
|
583
|
+
def __fetchToken(self) -> None:
|
|
584
|
+
"""Fetch a session token from the server."""
|
|
585
|
+
if self.__connection_state != ConnectionState.CONNECTION_OK:
|
|
586
|
+
logger.debug(f"[REQUEST-KEEPALIVE] Skipping token fetch, state: {self.__connection_state.name}")
|
|
587
|
+
return
|
|
588
|
+
try:
|
|
589
|
+
reply = self.getSessionToken()
|
|
590
|
+
if reply:
|
|
591
|
+
msg = reply.get(timeout_ms=5000)
|
|
592
|
+
if msg and hasattr(msg, 'token'):
|
|
593
|
+
self.__token = msg.token
|
|
594
|
+
logger.debug("[REQUEST-KEEPALIVE] Session token updated")
|
|
595
|
+
else:
|
|
596
|
+
logger.debug("[REQUEST-KEEPALIVE] Server returned no token")
|
|
597
|
+
except Exception as e:
|
|
598
|
+
logger.debug(f"[REQUEST-KEEPALIVE] Token refresh failed: {e}")
|
|
599
|
+
|
|
530
600
|
@staticmethod
|
|
531
601
|
def __buildSetParameterMsg(
|
|
532
602
|
path: str,
|
|
@@ -791,33 +861,33 @@ class Request:
|
|
|
791
861
|
Returns:
|
|
792
862
|
True if connection is established, raises on failure or timeout.
|
|
793
863
|
"""
|
|
794
|
-
logger.debug(f"[WAIT] waitForConnection started with timeout={timeout_sec}s")
|
|
864
|
+
logger.debug(f"[REQUEST-WAIT] waitForConnection started with timeout={timeout_sec}s")
|
|
795
865
|
|
|
796
866
|
# Wait for the condition to be set or timeout
|
|
797
867
|
if timeout_sec <= 0:
|
|
798
|
-
logger.debug("[WAIT] Waiting indefinitely for connection event")
|
|
868
|
+
logger.debug("[REQUEST-WAIT] Waiting indefinitely for connection event")
|
|
799
869
|
result = event.wait()
|
|
800
870
|
else:
|
|
801
|
-
logger.debug(f"[WAIT] Waiting up to {timeout_sec}s for connection event")
|
|
871
|
+
logger.debug(f"[REQUEST-WAIT] Waiting up to {timeout_sec}s for connection event")
|
|
802
872
|
result = event.wait(timeout_sec)
|
|
803
873
|
|
|
804
874
|
if not result:
|
|
805
|
-
logger.error(f"[WAIT] Connection timeout after {timeout_sec}s - no event received")
|
|
875
|
+
logger.error(f"[REQUEST-WAIT] Connection timeout after {timeout_sec}s - no event received")
|
|
806
876
|
raise TimeoutError(f"Connection timeout after {timeout_sec}s")
|
|
807
877
|
|
|
808
|
-
logger.debug("[WAIT] Event received - checking connection status")
|
|
878
|
+
logger.debug("[REQUEST-WAIT] Event received - checking connection status")
|
|
809
879
|
|
|
810
880
|
# Check if we actually connected (event could be set by close() or failure)
|
|
811
881
|
if is_connected_fn:
|
|
812
882
|
connected = is_connected_fn()
|
|
813
|
-
logger.debug(f"[WAIT] Connection status check: connected={connected}")
|
|
883
|
+
logger.debug(f"[REQUEST-WAIT] Connection status check: connected={connected}")
|
|
814
884
|
if not connected:
|
|
815
|
-
logger.error("[WAIT] Event was set but connection failed or was closed")
|
|
885
|
+
logger.error("[REQUEST-WAIT] Event was set but connection failed or was closed")
|
|
816
886
|
raise ConnectionError("Connection failed or was closed before completion")
|
|
817
887
|
else:
|
|
818
|
-
logger.debug("[WAIT] No connection status check function provided")
|
|
888
|
+
logger.debug("[REQUEST-WAIT] No connection status check function provided")
|
|
819
889
|
|
|
820
|
-
logger.debug("[WAIT] Connection successfully established")
|
|
890
|
+
logger.debug("[REQUEST-WAIT] Connection successfully established")
|
|
821
891
|
return True
|
|
822
892
|
|
|
823
893
|
@staticmethod
|
|
@@ -841,10 +911,10 @@ class Request:
|
|
|
841
911
|
path = os.sep.join([tempfile.gettempdir(), "mcx-python-pt-" + str(tree_hash.hash)])
|
|
842
912
|
tree = Request.loadParameterTreeFile(path, protobuf_types)
|
|
843
913
|
if tree:
|
|
844
|
-
logger.debug('Found parameter tree in the cache')
|
|
914
|
+
logger.debug('[REQUEST] Found parameter tree in the cache')
|
|
845
915
|
return tree
|
|
846
916
|
else:
|
|
847
|
-
logger.debug('Failed to find parameter tree in the cache')
|
|
917
|
+
logger.debug('[REQUEST] Failed to find parameter tree in the cache')
|
|
848
918
|
|
|
849
919
|
# getting and instantiating data type from the loaded dict
|
|
850
920
|
param_tree_msg = protobuf_types.createType('motorcortex.GetParameterTreeMsg')
|
|
@@ -868,7 +938,7 @@ class Request:
|
|
|
868
938
|
Returns:
|
|
869
939
|
The saved ParameterTree instance.
|
|
870
940
|
"""
|
|
871
|
-
logger.debug('Saved parameter tree to the cache')
|
|
941
|
+
logger.debug('[REQUEST] Saved parameter tree to the cache')
|
|
872
942
|
json_data = {}
|
|
873
943
|
base64_data = base64.b64encode(parameter_tree.SerializeToString())
|
|
874
944
|
json_data['md5'] = hashlib.md5(base64_data).hexdigest()
|
|
@@ -894,7 +964,7 @@ class Request:
|
|
|
894
964
|
Returns:
|
|
895
965
|
The loaded ParameterTree instance, or None if not found/invalid.
|
|
896
966
|
"""
|
|
897
|
-
logger.debug('Loaded parameter tree from the cache')
|
|
967
|
+
logger.debug('[REQUEST] Loaded parameter tree from the cache')
|
|
898
968
|
param_tree_hash_msg = None
|
|
899
969
|
if os.path.exists(path):
|
|
900
970
|
with open(path, "r") as outfile:
|
|
@@ -907,4 +977,4 @@ class Request:
|
|
|
907
977
|
tree_raw = base64.b64decode(json_data['data'])
|
|
908
978
|
param_tree_hash_msg.ParseFromString(tree_raw)
|
|
909
979
|
|
|
910
|
-
return param_tree_hash_msg
|
|
980
|
+
return param_tree_hash_msg
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.25.4'
|
{motorcortex_python-0.25.0 → motorcortex_python-0.25.4/motorcortex_python.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: motorcortex-python
|
|
3
|
-
Version: 0.25.
|
|
3
|
+
Version: 0.25.4
|
|
4
4
|
Summary: Python bindings for Motorcortex Engine
|
|
5
5
|
Home-page: https://www.motorcortex.io
|
|
6
6
|
Author: Alexey Zakharov
|
|
@@ -8,8 +8,17 @@ Author-email: alexey.zakharov@vectioneer.com
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: pynng
|
|
11
|
+
Requires-Dist: pynng>=0.9.0
|
|
12
12
|
Requires-Dist: protobuf>=3.20
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: description
|
|
16
|
+
Dynamic: description-content-type
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: license
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: summary
|
|
13
22
|
|
|
14
23
|
# motorcortex-python
|
|
15
24
|
|
{motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex_python.egg-info/requires.txt
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
pynng
|
|
1
|
+
pynng>=0.9.0
|
|
2
2
|
protobuf>=3.20
|
|
@@ -33,7 +33,8 @@ setup(name='motorcortex-python',
|
|
|
33
33
|
url='https://www.motorcortex.io',
|
|
34
34
|
license='MIT',
|
|
35
35
|
packages=['motorcortex'],
|
|
36
|
-
install_requires=[
|
|
37
|
-
|
|
36
|
+
install_requires=[
|
|
37
|
+
'pynng>=0.9.0',
|
|
38
|
+
'protobuf>=3.20'],
|
|
38
39
|
include_package_data=True,
|
|
39
40
|
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.25.0'
|
|
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
|
{motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex/state_callback_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{motorcortex_python-0.25.0 → motorcortex_python-0.25.4}/motorcortex_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|