dexcontrol 0.2.1__py3-none-any.whl → 0.2.3__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 dexcontrol might be problematic. Click here for more details.
- dexcontrol/__init__.py +14 -3
- dexcontrol/apps/dualsense_teleop_base.py +16 -11
- dexcontrol/config/__init__.py +10 -5
- dexcontrol/config/core/__init__.py +8 -3
- dexcontrol/config/core/arm.py +8 -3
- dexcontrol/config/core/chassis.py +10 -5
- dexcontrol/config/core/hand.py +14 -9
- dexcontrol/config/core/head.py +8 -3
- dexcontrol/config/core/misc.py +8 -3
- dexcontrol/config/core/torso.py +8 -3
- dexcontrol/config/sensors/__init__.py +8 -3
- dexcontrol/config/sensors/cameras/__init__.py +9 -4
- dexcontrol/config/sensors/cameras/rgb_camera.py +18 -5
- dexcontrol/config/sensors/cameras/zed_camera.py +36 -0
- dexcontrol/config/sensors/imu/__init__.py +10 -5
- dexcontrol/config/sensors/imu/chassis_imu.py +21 -0
- dexcontrol/config/sensors/imu/zed_imu.py +21 -0
- dexcontrol/config/sensors/lidar/__init__.py +8 -3
- dexcontrol/config/sensors/lidar/rplidar.py +9 -3
- dexcontrol/config/sensors/ultrasonic/__init__.py +8 -3
- dexcontrol/config/sensors/ultrasonic/ultrasonic.py +9 -3
- dexcontrol/config/sensors/vega_sensors.py +34 -21
- dexcontrol/config/vega.py +14 -6
- dexcontrol/core/__init__.py +9 -0
- dexcontrol/core/arm.py +21 -6
- dexcontrol/core/chassis.py +8 -3
- dexcontrol/core/component.py +26 -6
- dexcontrol/core/hand.py +8 -3
- dexcontrol/core/head.py +18 -3
- dexcontrol/core/misc.py +94 -16
- dexcontrol/core/torso.py +8 -3
- dexcontrol/proto/dexcontrol_msg_pb2.py +17 -15
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +24 -0
- dexcontrol/robot.py +82 -28
- dexcontrol/sensors/__init__.py +13 -8
- dexcontrol/sensors/camera/__init__.py +11 -6
- dexcontrol/sensors/camera/rgb_camera.py +33 -24
- dexcontrol/sensors/camera/zed_camera.py +364 -0
- dexcontrol/sensors/imu/__init__.py +13 -8
- dexcontrol/sensors/imu/chassis_imu.py +155 -0
- dexcontrol/sensors/imu/{nine_axis_imu.py → zed_imu.py} +41 -26
- dexcontrol/sensors/lidar/__init__.py +11 -1
- dexcontrol/sensors/lidar/rplidar.py +8 -3
- dexcontrol/sensors/manager.py +22 -9
- dexcontrol/sensors/ultrasonic.py +8 -3
- dexcontrol/utils/__init__.py +8 -3
- dexcontrol/utils/constants.py +10 -0
- dexcontrol/utils/io_utils.py +8 -3
- dexcontrol/utils/motion_utils.py +8 -3
- dexcontrol/utils/os_utils.py +23 -4
- dexcontrol/utils/pb_utils.py +8 -3
- dexcontrol/utils/rate_limiter.py +8 -3
- dexcontrol/utils/rtc_utils.py +144 -0
- dexcontrol/utils/subscribers/__init__.py +11 -3
- dexcontrol/utils/subscribers/base.py +26 -5
- dexcontrol/utils/subscribers/camera.py +10 -6
- dexcontrol/utils/subscribers/decoders.py +8 -3
- dexcontrol/utils/subscribers/generic.py +8 -3
- dexcontrol/utils/subscribers/imu.py +8 -3
- dexcontrol/utils/subscribers/lidar.py +8 -3
- dexcontrol/utils/subscribers/protobuf.py +8 -3
- dexcontrol/utils/subscribers/rtc.py +315 -0
- dexcontrol/utils/timer.py +8 -3
- dexcontrol/utils/trajectory_utils.py +8 -3
- dexcontrol/utils/viz_utils.py +8 -3
- dexcontrol/utils/zenoh_utils.py +83 -0
- dexcontrol-0.2.3.dist-info/METADATA +265 -0
- dexcontrol-0.2.3.dist-info/RECORD +72 -0
- {dexcontrol-0.2.1.dist-info → dexcontrol-0.2.3.dist-info}/WHEEL +1 -2
- dexcontrol-0.2.3.dist-info/licenses/LICENSE +184 -0
- dexcontrol/config/sensors/cameras/gemini_camera.py +0 -16
- dexcontrol/config/sensors/imu/gemini_imu.py +0 -15
- dexcontrol/config/sensors/imu/nine_axis_imu.py +0 -15
- dexcontrol/sensors/camera/gemini_camera.py +0 -139
- dexcontrol/sensors/imu/gemini_imu.py +0 -139
- dexcontrol/utils/reset_orbbec_camera_usb.py +0 -98
- dexcontrol-0.2.1.dist-info/METADATA +0 -369
- dexcontrol-0.2.1.dist-info/RECORD +0 -72
- dexcontrol-0.2.1.dist-info/licenses/LICENSE +0 -188
- dexcontrol-0.2.1.dist-info/licenses/NOTICE +0 -13
- dexcontrol-0.2.1.dist-info/top_level.txt +0 -1
dexcontrol/core/__init__.py
CHANGED
dexcontrol/core/arm.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Robot arm control module.
|
|
7
12
|
|
|
@@ -15,6 +20,7 @@ from typing import Final, Literal
|
|
|
15
20
|
import numpy as np
|
|
16
21
|
import zenoh
|
|
17
22
|
from jaxtyping import Float
|
|
23
|
+
from loguru import logger
|
|
18
24
|
|
|
19
25
|
from dexcontrol.config.core.arm import ArmConfig
|
|
20
26
|
from dexcontrol.core.component import RobotComponent, RobotJointComponent
|
|
@@ -97,7 +103,7 @@ class Arm(RobotJointComponent):
|
|
|
97
103
|
joint_pos: Joint positions as numpy array.
|
|
98
104
|
"""
|
|
99
105
|
control_msg = dexcontrol_msg_pb2.ArmCommand()
|
|
100
|
-
control_msg.joint_pos.extend(joint_pos.
|
|
106
|
+
control_msg.joint_pos.extend(joint_pos.tolist())
|
|
101
107
|
self._publish_control(control_msg)
|
|
102
108
|
|
|
103
109
|
def set_joint_pos(
|
|
@@ -249,8 +255,17 @@ class Arm(RobotJointComponent):
|
|
|
249
255
|
def shutdown(self) -> None:
|
|
250
256
|
"""Cleans up all Zenoh resources."""
|
|
251
257
|
super().shutdown()
|
|
252
|
-
|
|
253
|
-
self.mode_querier
|
|
258
|
+
try:
|
|
259
|
+
if hasattr(self, "mode_querier") and self.mode_querier:
|
|
260
|
+
self.mode_querier.undeclare()
|
|
261
|
+
except Exception as e:
|
|
262
|
+
# Don't log "Undeclared querier" errors as warnings - they're expected during shutdown
|
|
263
|
+
error_msg = str(e).lower()
|
|
264
|
+
if not ("undeclared" in error_msg or "closed" in error_msg):
|
|
265
|
+
logger.warning(
|
|
266
|
+
f"Error undeclaring mode querier for {self.__class__.__name__}: {e}"
|
|
267
|
+
)
|
|
268
|
+
|
|
254
269
|
if self.wrench_sensor:
|
|
255
270
|
self.wrench_sensor.shutdown()
|
|
256
271
|
|
dexcontrol/core/chassis.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Robot base control module.
|
|
7
12
|
|
dexcontrol/core/component.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Base module for robot components with Zenoh communication.
|
|
7
12
|
|
|
@@ -17,6 +22,7 @@ import numpy as np
|
|
|
17
22
|
import zenoh
|
|
18
23
|
from google.protobuf.message import Message
|
|
19
24
|
from jaxtyping import Float
|
|
25
|
+
from loguru import logger
|
|
20
26
|
|
|
21
27
|
from dexcontrol.utils.os_utils import resolve_key_name
|
|
22
28
|
from dexcontrol.utils.subscribers import ProtobufZenohSubscriber
|
|
@@ -115,7 +121,13 @@ class RobotComponent:
|
|
|
115
121
|
if hasattr(self, "stop"):
|
|
116
122
|
method = getattr(self, "stop")
|
|
117
123
|
if callable(method):
|
|
118
|
-
|
|
124
|
+
try:
|
|
125
|
+
method()
|
|
126
|
+
except Exception as e:
|
|
127
|
+
# During shutdown, stop() methods may fail due to inactive subscribers
|
|
128
|
+
logger.debug(
|
|
129
|
+
f"Error during stop() for {self.__class__.__name__}: {e}"
|
|
130
|
+
)
|
|
119
131
|
|
|
120
132
|
# Shutdown subscriber to release resources
|
|
121
133
|
if hasattr(self, "_subscriber") and self._subscriber:
|
|
@@ -194,8 +206,16 @@ class RobotJointComponent(RobotComponent):
|
|
|
194
206
|
def shutdown(self) -> None:
|
|
195
207
|
"""Cleans up all Zenoh resources."""
|
|
196
208
|
super().shutdown()
|
|
197
|
-
|
|
198
|
-
self._publisher
|
|
209
|
+
try:
|
|
210
|
+
if hasattr(self, "_publisher") and self._publisher:
|
|
211
|
+
self._publisher.undeclare()
|
|
212
|
+
except Exception as e:
|
|
213
|
+
# Don't log "Undeclared publisher" errors as warnings - they're expected during shutdown
|
|
214
|
+
error_msg = str(e).lower()
|
|
215
|
+
if not ("undeclared" in error_msg or "closed" in error_msg):
|
|
216
|
+
logger.warning(
|
|
217
|
+
f"Error undeclaring publisher for {self.__class__.__name__}: {e}"
|
|
218
|
+
)
|
|
199
219
|
|
|
200
220
|
@property
|
|
201
221
|
def joint_name(self) -> list[str]:
|
dexcontrol/core/hand.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Robot hand control module.
|
|
7
12
|
|
dexcontrol/core/head.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Robot head control module.
|
|
7
12
|
|
|
@@ -194,6 +199,16 @@ class Head(RobotJointComponent):
|
|
|
194
199
|
"""Clean up Zenoh resources for the head component."""
|
|
195
200
|
self.stop()
|
|
196
201
|
super().shutdown()
|
|
202
|
+
try:
|
|
203
|
+
if hasattr(self, "mode_querier") and self.mode_querier:
|
|
204
|
+
self.mode_querier.undeclare()
|
|
205
|
+
except Exception as e:
|
|
206
|
+
# Don't log "Undeclared querier" errors as warnings - they're expected during shutdown
|
|
207
|
+
error_msg = str(e).lower()
|
|
208
|
+
if not ("undeclared" in error_msg or "closed" in error_msg):
|
|
209
|
+
logger.warning(
|
|
210
|
+
f"Error undeclaring mode querier for {self.__class__.__name__}: {e}"
|
|
211
|
+
)
|
|
197
212
|
|
|
198
213
|
def _process_joint_velocities(
|
|
199
214
|
self,
|
dexcontrol/core/misc.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Miscellaneous robot components module.
|
|
7
12
|
|
|
@@ -11,6 +16,7 @@ EStop (emergency stop), and UltraSonicSensor.
|
|
|
11
16
|
|
|
12
17
|
import os
|
|
13
18
|
import threading
|
|
19
|
+
import time
|
|
14
20
|
from typing import TypeVar
|
|
15
21
|
|
|
16
22
|
import zenoh
|
|
@@ -124,8 +130,10 @@ class Battery(RobotComponent):
|
|
|
124
130
|
def shutdown(self) -> None:
|
|
125
131
|
"""Shuts down the battery component and stops monitoring thread."""
|
|
126
132
|
self._shutdown_event.set()
|
|
127
|
-
if self._monitor_thread.is_alive():
|
|
128
|
-
self._monitor_thread.join(timeout=
|
|
133
|
+
if self._monitor_thread and self._monitor_thread.is_alive():
|
|
134
|
+
self._monitor_thread.join(timeout=2.0) # Extended timeout
|
|
135
|
+
if self._monitor_thread.is_alive():
|
|
136
|
+
logger.warning("Battery monitor thread did not terminate cleanly")
|
|
129
137
|
super().shutdown()
|
|
130
138
|
|
|
131
139
|
@staticmethod
|
|
@@ -284,8 +292,10 @@ class EStop(RobotComponent):
|
|
|
284
292
|
def shutdown(self) -> None:
|
|
285
293
|
"""Shuts down the EStop component and stops monitoring thread."""
|
|
286
294
|
self._shutdown_event.set()
|
|
287
|
-
if self._monitor_thread.is_alive():
|
|
288
|
-
self._monitor_thread.join(timeout=
|
|
295
|
+
if self._monitor_thread and self._monitor_thread.is_alive():
|
|
296
|
+
self._monitor_thread.join(timeout=2.0) # Extended timeout
|
|
297
|
+
if self._monitor_thread.is_alive():
|
|
298
|
+
logger.warning("EStop monitor thread did not terminate cleanly")
|
|
289
299
|
super().shutdown()
|
|
290
300
|
|
|
291
301
|
def show(self) -> None:
|
|
@@ -325,6 +335,7 @@ class Heartbeat:
|
|
|
325
335
|
_shutdown_event: Event to signal thread shutdown.
|
|
326
336
|
_timeout_seconds: Timeout in seconds before triggering emergency exit.
|
|
327
337
|
_enabled: Whether heartbeat monitoring is enabled.
|
|
338
|
+
_paused: Whether heartbeat monitoring is temporarily paused.
|
|
328
339
|
"""
|
|
329
340
|
|
|
330
341
|
def __init__(
|
|
@@ -340,8 +351,9 @@ class Heartbeat:
|
|
|
340
351
|
"""
|
|
341
352
|
self._timeout_seconds = configs.timeout_seconds
|
|
342
353
|
self._enabled = configs.enabled
|
|
354
|
+
self._paused = False
|
|
355
|
+
self._paused_lock = threading.Lock()
|
|
343
356
|
self._shutdown_event = threading.Event()
|
|
344
|
-
|
|
345
357
|
if not self._enabled:
|
|
346
358
|
logger.info("Heartbeat monitoring is DISABLED via configuration")
|
|
347
359
|
# Create a dummy subscriber that's never active
|
|
@@ -386,6 +398,13 @@ class Heartbeat:
|
|
|
386
398
|
|
|
387
399
|
while not self._shutdown_event.is_set():
|
|
388
400
|
try:
|
|
401
|
+
# Skip monitoring if paused
|
|
402
|
+
with self._paused_lock:
|
|
403
|
+
is_paused = self._paused
|
|
404
|
+
if is_paused:
|
|
405
|
+
self._shutdown_event.wait(0.1)
|
|
406
|
+
continue
|
|
407
|
+
|
|
389
408
|
# Check if fresh data is being received within the timeout period
|
|
390
409
|
if self._subscriber.is_active():
|
|
391
410
|
if not self._subscriber.is_data_fresh(self._timeout_seconds):
|
|
@@ -413,16 +432,65 @@ class Heartbeat:
|
|
|
413
432
|
# Continue monitoring even if there's an error
|
|
414
433
|
self._shutdown_event.wait(0.1)
|
|
415
434
|
|
|
435
|
+
def pause(self) -> None:
|
|
436
|
+
"""Pause heartbeat monitoring temporarily.
|
|
437
|
+
|
|
438
|
+
When paused, the heartbeat monitor will not check for timeouts or exit
|
|
439
|
+
the program. This is useful for scenarios where you need to temporarily
|
|
440
|
+
disable safety monitoring (e.g., during system maintenance or testing).
|
|
441
|
+
|
|
442
|
+
Warning: Use with caution as this disables a critical safety mechanism.
|
|
443
|
+
"""
|
|
444
|
+
if not self._enabled:
|
|
445
|
+
logger.warning("Cannot pause heartbeat monitoring - it's already disabled")
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
with self._paused_lock:
|
|
449
|
+
self._paused = True
|
|
450
|
+
logger.warning(
|
|
451
|
+
"Heartbeat monitoring PAUSED - safety mechanism temporarily disabled"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def resume(self) -> None:
|
|
455
|
+
"""Resume heartbeat monitoring after being paused.
|
|
456
|
+
|
|
457
|
+
This re-enables the heartbeat timeout checking that was temporarily
|
|
458
|
+
disabled by pause(). The monitor will immediately start checking
|
|
459
|
+
for fresh heartbeat data again.
|
|
460
|
+
"""
|
|
461
|
+
if not self._enabled:
|
|
462
|
+
logger.warning("Cannot resume heartbeat monitoring - it's disabled")
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
with self._paused_lock:
|
|
466
|
+
if not self._paused:
|
|
467
|
+
logger.info("Heartbeat monitoring is already active")
|
|
468
|
+
return
|
|
469
|
+
self._paused = False
|
|
470
|
+
# sleep for some time to make sure the heartbeat subscriber is resumed
|
|
471
|
+
time.sleep(self._timeout_seconds)
|
|
472
|
+
logger.info("Heartbeat monitoring RESUMED - safety mechanism re-enabled")
|
|
473
|
+
|
|
474
|
+
def is_paused(self) -> bool:
|
|
475
|
+
"""Check if heartbeat monitoring is currently paused.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
True if monitoring is paused, False if active or disabled.
|
|
479
|
+
"""
|
|
480
|
+
with self._paused_lock:
|
|
481
|
+
return self._paused
|
|
482
|
+
|
|
416
483
|
def get_status(self) -> dict[str, bool | float | float | None]:
|
|
417
484
|
"""Gets the current heartbeat status information.
|
|
418
485
|
|
|
419
486
|
Returns:
|
|
420
487
|
Dictionary containing heartbeat metrics including:
|
|
421
488
|
- is_active: Whether heartbeat signal is being received (bool)
|
|
422
|
-
- last_value: Last received heartbeat value (
|
|
423
|
-
- time_since_last: Time since last fresh data in seconds (
|
|
489
|
+
- last_value: Last received heartbeat value (float | None)
|
|
490
|
+
- time_since_last: Time since last fresh data in seconds (float | None)
|
|
424
491
|
- timeout_seconds: Configured timeout value (float)
|
|
425
492
|
- enabled: Whether heartbeat monitoring is enabled (bool)
|
|
493
|
+
- paused: Whether heartbeat monitoring is paused (bool)
|
|
426
494
|
"""
|
|
427
495
|
if not self._enabled or self._subscriber is None:
|
|
428
496
|
return {
|
|
@@ -431,17 +499,22 @@ class Heartbeat:
|
|
|
431
499
|
"time_since_last": None,
|
|
432
500
|
"timeout_seconds": self._timeout_seconds,
|
|
433
501
|
"enabled": False,
|
|
502
|
+
"paused": False,
|
|
434
503
|
}
|
|
435
504
|
|
|
436
505
|
last_value = self._subscriber.get_latest_data()
|
|
437
506
|
time_since_last = self._subscriber.get_time_since_last_data()
|
|
438
507
|
|
|
508
|
+
with self._paused_lock:
|
|
509
|
+
paused = self._paused
|
|
510
|
+
|
|
439
511
|
return {
|
|
440
512
|
"is_active": self._subscriber.is_active(),
|
|
441
513
|
"last_value": last_value,
|
|
442
514
|
"time_since_last": time_since_last,
|
|
443
515
|
"timeout_seconds": self._timeout_seconds,
|
|
444
516
|
"enabled": True,
|
|
517
|
+
"paused": paused,
|
|
445
518
|
}
|
|
446
519
|
|
|
447
520
|
def is_active(self) -> bool:
|
|
@@ -458,11 +531,11 @@ class Heartbeat:
|
|
|
458
531
|
"""Shuts down the heartbeat monitor and stops monitoring thread."""
|
|
459
532
|
if not self._enabled:
|
|
460
533
|
return
|
|
461
|
-
|
|
462
|
-
logger.info("Shutting down heartbeat monitor")
|
|
463
534
|
self._shutdown_event.set()
|
|
464
535
|
if self._monitor_thread and self._monitor_thread.is_alive():
|
|
465
|
-
self._monitor_thread.join(timeout=
|
|
536
|
+
self._monitor_thread.join(timeout=2.0) # Extended timeout
|
|
537
|
+
if self._monitor_thread.is_alive():
|
|
538
|
+
logger.warning("Heartbeat monitor thread did not terminate cleanly")
|
|
466
539
|
if self._subscriber:
|
|
467
540
|
self._subscriber.shutdown()
|
|
468
541
|
|
|
@@ -485,9 +558,14 @@ class Heartbeat:
|
|
|
485
558
|
console.print(table)
|
|
486
559
|
return
|
|
487
560
|
|
|
488
|
-
#
|
|
489
|
-
|
|
490
|
-
|
|
561
|
+
# Paused status
|
|
562
|
+
paused = status.get("paused", False)
|
|
563
|
+
if paused:
|
|
564
|
+
table.add_row("Status", "[yellow]PAUSED[/]")
|
|
565
|
+
else:
|
|
566
|
+
# Active status
|
|
567
|
+
active_style = "bold dark_green" if status["is_active"] else "bold red"
|
|
568
|
+
table.add_row("Signal Active", f"[{active_style}]{status['is_active']}[/]")
|
|
491
569
|
|
|
492
570
|
# Last heartbeat value
|
|
493
571
|
last_value = status["last_value"]
|
dexcontrol/core/torso.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Robot torso control module.
|
|
7
12
|
|
|
@@ -25,7 +25,7 @@ _sym_db = _symbol_database.Default()
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x64\x65xcontrol_msg.proto\x12\ndexcontrol\"V\n\x08\x41rmState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\x12\x11\n\tjoint_cur\x18\x03 \x03(\x02\x12\x11\n\tjoint_err\x18\x04 \x03(\r\"F\n\tHandState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\x12\x13\n\x0bjoint_statu\x18\x03 \x03(\r\"1\n\tHeadState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"2\n\nTorsoState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"a\n\x10SingleWheelState\x12\x14\n\x0csteering_pos\x18\x01 \x01(\x02\x12\x11\n\twheel_pos\x18\x02 \x01(\x02\x12\x11\n\twheel_vel\x18\x03 \x01(\x02\x12\x11\n\twheel_cur\x18\x04 \x01(\x02\"g\n\x0c\x43hassisState\x12*\n\x04left\x18\x01 \x01(\x0b\x32\x1c.dexcontrol.SingleWheelState\x12+\n\x05right\x18\x02 \x01(\x0b\x32\x1c.dexcontrol.SingleWheelState\"j\n\x08\x42MSState\x12\x0f\n\x07voltage\x18\x01 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x02 \x01(\x02\x12\x13\n\x0btemperature\x18\x03 \x01(\x02\x12\x12\n\npercentage\x18\x04 \x01(\r\x12\x13\n\x0bis_charging\x18\x05 \x01(\x08\"H\n\x0bWrenchState\x12\x0e\n\x06wrench\x18\x01 \x03(\x02\x12\x13\n\x0b\x62lue_button\x18\x02 \x01(\x08\x12\x14\n\x0cgreen_button\x18\x03 \x01(\x08\"D\n\nEStopState\x12\x16\n\x0e\x62utton_pressed\x18\x01 \x01(\x08\x12\x1e\n\x16software_estop_enabled\x18\x02 \x01(\x08\"a\n\x0fUltrasonicState\x12\x12\n\nfront_left\x18\x01 \x01(\x02\x12\x13\n\x0b\x66ront_right\x18\x02 \x01(\x02\x12\x11\n\tback_left\x18\x03 \x01(\x02\x12\x12\n\nback_right\x18\x04 \x01(\x02\"\xa3\x01\n\nArmCommand\x12\x38\n\x0c\x63ommand_type\x18\x01 \x01(\x0e\x32\".dexcontrol.ArmCommand.CommandType\x12\x11\n\tjoint_pos\x18\x02 \x03(\x02\x12\x11\n\tjoint_vel\x18\x03 \x03(\x02\"5\n\x0b\x43ommandType\x12\x0c\n\x08POSITION\x10\x00\x12\x18\n\x14VELOCITY_FEEDFORWARD\x10\x01\" \n\x0bHandCommand\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\"3\n\x0bHeadCommand\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"4\n\x0cTorsoCommand\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"=\n\x12SingleWheelCommand\x12\x14\n\x0csteering_pos\x18\x01 \x01(\x02\x12\x11\n\twheel_vel\x18\x02 \x01(\x02\"m\n\x0e\x43hassisCommand\x12,\n\x04left\x18\x01 \x01(\x0b\x32\x1e.dexcontrol.SingleWheelCommand\x12-\n\x05right\x18\x02 \x01(\x0b\x32\x1e.dexcontrol.SingleWheelCommandb\x06proto3')
|
|
28
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x64\x65xcontrol_msg.proto\x12\ndexcontrol\"V\n\x08\x41rmState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\x12\x11\n\tjoint_cur\x18\x03 \x03(\x02\x12\x11\n\tjoint_err\x18\x04 \x03(\r\"F\n\tHandState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\x12\x13\n\x0bjoint_statu\x18\x03 \x03(\r\"1\n\tHeadState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"2\n\nTorsoState\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"a\n\x10SingleWheelState\x12\x14\n\x0csteering_pos\x18\x01 \x01(\x02\x12\x11\n\twheel_pos\x18\x02 \x01(\x02\x12\x11\n\twheel_vel\x18\x03 \x01(\x02\x12\x11\n\twheel_cur\x18\x04 \x01(\x02\"g\n\x0c\x43hassisState\x12*\n\x04left\x18\x01 \x01(\x0b\x32\x1c.dexcontrol.SingleWheelState\x12+\n\x05right\x18\x02 \x01(\x0b\x32\x1c.dexcontrol.SingleWheelState\"j\n\x08\x42MSState\x12\x0f\n\x07voltage\x18\x01 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x02 \x01(\x02\x12\x13\n\x0btemperature\x18\x03 \x01(\x02\x12\x12\n\npercentage\x18\x04 \x01(\r\x12\x13\n\x0bis_charging\x18\x05 \x01(\x08\"H\n\x0bWrenchState\x12\x0e\n\x06wrench\x18\x01 \x03(\x02\x12\x13\n\x0b\x62lue_button\x18\x02 \x01(\x08\x12\x14\n\x0cgreen_button\x18\x03 \x01(\x08\"D\n\nEStopState\x12\x16\n\x0e\x62utton_pressed\x18\x01 \x01(\x08\x12\x1e\n\x16software_estop_enabled\x18\x02 \x01(\x08\"a\n\x0fUltrasonicState\x12\x12\n\nfront_left\x18\x01 \x01(\x02\x12\x13\n\x0b\x66ront_right\x18\x02 \x01(\x02\x12\x11\n\tback_left\x18\x03 \x01(\x02\x12\x12\n\nback_right\x18\x04 \x01(\x02\"\xa7\x01\n\x08IMUState\x12\r\n\x05\x61\x63\x63_x\x18\x01 \x01(\x02\x12\r\n\x05\x61\x63\x63_y\x18\x02 \x01(\x02\x12\r\n\x05\x61\x63\x63_z\x18\x03 \x01(\x02\x12\x0e\n\x06gyro_x\x18\x04 \x01(\x02\x12\x0e\n\x06gyro_y\x18\x05 \x01(\x02\x12\x0e\n\x06gyro_z\x18\x06 \x01(\x02\x12\x0e\n\x06quat_w\x18\x07 \x01(\x02\x12\x0e\n\x06quat_x\x18\x08 \x01(\x02\x12\x0e\n\x06quat_y\x18\t \x01(\x02\x12\x0e\n\x06quat_z\x18\n \x01(\x02\"\xa3\x01\n\nArmCommand\x12\x38\n\x0c\x63ommand_type\x18\x01 \x01(\x0e\x32\".dexcontrol.ArmCommand.CommandType\x12\x11\n\tjoint_pos\x18\x02 \x03(\x02\x12\x11\n\tjoint_vel\x18\x03 \x03(\x02\"5\n\x0b\x43ommandType\x12\x0c\n\x08POSITION\x10\x00\x12\x18\n\x14VELOCITY_FEEDFORWARD\x10\x01\" \n\x0bHandCommand\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\"3\n\x0bHeadCommand\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"4\n\x0cTorsoCommand\x12\x11\n\tjoint_pos\x18\x01 \x03(\x02\x12\x11\n\tjoint_vel\x18\x02 \x03(\x02\"=\n\x12SingleWheelCommand\x12\x14\n\x0csteering_pos\x18\x01 \x01(\x02\x12\x11\n\twheel_vel\x18\x02 \x01(\x02\"m\n\x0e\x43hassisCommand\x12,\n\x04left\x18\x01 \x01(\x0b\x32\x1e.dexcontrol.SingleWheelCommand\x12-\n\x05right\x18\x02 \x01(\x0b\x32\x1e.dexcontrol.SingleWheelCommandb\x06proto3')
|
|
29
29
|
|
|
30
30
|
_globals = globals()
|
|
31
31
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -52,18 +52,20 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
|
|
52
52
|
_globals['_ESTOPSTATE']._serialized_end=753
|
|
53
53
|
_globals['_ULTRASONICSTATE']._serialized_start=755
|
|
54
54
|
_globals['_ULTRASONICSTATE']._serialized_end=852
|
|
55
|
-
_globals['
|
|
56
|
-
_globals['
|
|
57
|
-
_globals['
|
|
58
|
-
_globals['
|
|
59
|
-
_globals['
|
|
60
|
-
_globals['
|
|
61
|
-
_globals['
|
|
62
|
-
_globals['
|
|
63
|
-
_globals['
|
|
64
|
-
_globals['
|
|
65
|
-
_globals['
|
|
66
|
-
_globals['
|
|
67
|
-
_globals['
|
|
68
|
-
_globals['
|
|
55
|
+
_globals['_IMUSTATE']._serialized_start=855
|
|
56
|
+
_globals['_IMUSTATE']._serialized_end=1022
|
|
57
|
+
_globals['_ARMCOMMAND']._serialized_start=1025
|
|
58
|
+
_globals['_ARMCOMMAND']._serialized_end=1188
|
|
59
|
+
_globals['_ARMCOMMAND_COMMANDTYPE']._serialized_start=1135
|
|
60
|
+
_globals['_ARMCOMMAND_COMMANDTYPE']._serialized_end=1188
|
|
61
|
+
_globals['_HANDCOMMAND']._serialized_start=1190
|
|
62
|
+
_globals['_HANDCOMMAND']._serialized_end=1222
|
|
63
|
+
_globals['_HEADCOMMAND']._serialized_start=1224
|
|
64
|
+
_globals['_HEADCOMMAND']._serialized_end=1275
|
|
65
|
+
_globals['_TORSOCOMMAND']._serialized_start=1277
|
|
66
|
+
_globals['_TORSOCOMMAND']._serialized_end=1329
|
|
67
|
+
_globals['_SINGLEWHEELCOMMAND']._serialized_start=1331
|
|
68
|
+
_globals['_SINGLEWHEELCOMMAND']._serialized_end=1392
|
|
69
|
+
_globals['_CHASSISCOMMAND']._serialized_start=1394
|
|
70
|
+
_globals['_CHASSISCOMMAND']._serialized_end=1503
|
|
69
71
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -113,6 +113,30 @@ class UltrasonicState(_message.Message):
|
|
|
113
113
|
back_right: float
|
|
114
114
|
def __init__(self, front_left: _Optional[float] = ..., front_right: _Optional[float] = ..., back_left: _Optional[float] = ..., back_right: _Optional[float] = ...) -> None: ...
|
|
115
115
|
|
|
116
|
+
class IMUState(_message.Message):
|
|
117
|
+
__slots__ = ("acc_x", "acc_y", "acc_z", "gyro_x", "gyro_y", "gyro_z", "quat_w", "quat_x", "quat_y", "quat_z")
|
|
118
|
+
ACC_X_FIELD_NUMBER: _ClassVar[int]
|
|
119
|
+
ACC_Y_FIELD_NUMBER: _ClassVar[int]
|
|
120
|
+
ACC_Z_FIELD_NUMBER: _ClassVar[int]
|
|
121
|
+
GYRO_X_FIELD_NUMBER: _ClassVar[int]
|
|
122
|
+
GYRO_Y_FIELD_NUMBER: _ClassVar[int]
|
|
123
|
+
GYRO_Z_FIELD_NUMBER: _ClassVar[int]
|
|
124
|
+
QUAT_W_FIELD_NUMBER: _ClassVar[int]
|
|
125
|
+
QUAT_X_FIELD_NUMBER: _ClassVar[int]
|
|
126
|
+
QUAT_Y_FIELD_NUMBER: _ClassVar[int]
|
|
127
|
+
QUAT_Z_FIELD_NUMBER: _ClassVar[int]
|
|
128
|
+
acc_x: float
|
|
129
|
+
acc_y: float
|
|
130
|
+
acc_z: float
|
|
131
|
+
gyro_x: float
|
|
132
|
+
gyro_y: float
|
|
133
|
+
gyro_z: float
|
|
134
|
+
quat_w: float
|
|
135
|
+
quat_x: float
|
|
136
|
+
quat_y: float
|
|
137
|
+
quat_z: float
|
|
138
|
+
def __init__(self, acc_x: _Optional[float] = ..., acc_y: _Optional[float] = ..., acc_z: _Optional[float] = ..., gyro_x: _Optional[float] = ..., gyro_y: _Optional[float] = ..., gyro_z: _Optional[float] = ..., quat_w: _Optional[float] = ..., quat_x: _Optional[float] = ..., quat_y: _Optional[float] = ..., quat_z: _Optional[float] = ...) -> None: ...
|
|
139
|
+
|
|
116
140
|
class ArmCommand(_message.Message):
|
|
117
141
|
__slots__ = ("command_type", "joint_pos", "joint_vel")
|
|
118
142
|
class CommandType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|