dexcontrol 0.3.0__py3-none-any.whl → 0.3.2__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.

Files changed (51) hide show
  1. dexcontrol/__init__.py +16 -7
  2. dexcontrol/apps/dualsense_teleop_base.py +1 -1
  3. dexcontrol/comm/__init__.py +51 -0
  4. dexcontrol/comm/base.py +421 -0
  5. dexcontrol/comm/rtc.py +400 -0
  6. dexcontrol/comm/subscribers.py +329 -0
  7. dexcontrol/config/sensors/cameras/__init__.py +1 -2
  8. dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
  9. dexcontrol/config/sensors/vega_sensors.py +12 -18
  10. dexcontrol/core/arm.py +29 -25
  11. dexcontrol/core/chassis.py +3 -12
  12. dexcontrol/core/component.py +68 -43
  13. dexcontrol/core/hand.py +50 -52
  14. dexcontrol/core/head.py +14 -26
  15. dexcontrol/core/misc.py +188 -166
  16. dexcontrol/core/robot_query_interface.py +140 -117
  17. dexcontrol/core/torso.py +0 -4
  18. dexcontrol/robot.py +15 -37
  19. dexcontrol/sensors/__init__.py +1 -2
  20. dexcontrol/sensors/camera/__init__.py +0 -2
  21. dexcontrol/sensors/camera/base_camera.py +144 -0
  22. dexcontrol/sensors/camera/rgb_camera.py +67 -63
  23. dexcontrol/sensors/camera/zed_camera.py +89 -147
  24. dexcontrol/sensors/imu/chassis_imu.py +76 -56
  25. dexcontrol/sensors/imu/zed_imu.py +54 -43
  26. dexcontrol/sensors/lidar/rplidar.py +16 -20
  27. dexcontrol/sensors/manager.py +4 -11
  28. dexcontrol/sensors/ultrasonic.py +14 -27
  29. dexcontrol/utils/__init__.py +0 -11
  30. dexcontrol/utils/comm_helper.py +111 -0
  31. dexcontrol/utils/constants.py +1 -1
  32. dexcontrol/utils/os_utils.py +8 -22
  33. {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/METADATA +2 -1
  34. dexcontrol-0.3.2.dist-info/RECORD +68 -0
  35. dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
  36. dexcontrol/sensors/camera/luxonis_camera.py +0 -169
  37. dexcontrol/utils/rate_limiter.py +0 -172
  38. dexcontrol/utils/rtc_utils.py +0 -144
  39. dexcontrol/utils/subscribers/__init__.py +0 -52
  40. dexcontrol/utils/subscribers/base.py +0 -281
  41. dexcontrol/utils/subscribers/camera.py +0 -332
  42. dexcontrol/utils/subscribers/decoders.py +0 -88
  43. dexcontrol/utils/subscribers/generic.py +0 -110
  44. dexcontrol/utils/subscribers/imu.py +0 -175
  45. dexcontrol/utils/subscribers/lidar.py +0 -172
  46. dexcontrol/utils/subscribers/protobuf.py +0 -111
  47. dexcontrol/utils/subscribers/rtc.py +0 -316
  48. dexcontrol/utils/zenoh_utils.py +0 -369
  49. dexcontrol-0.3.0.dist-info/RECORD +0 -76
  50. {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/WHEEL +0 -0
  51. {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -11,9 +11,8 @@
11
11
  """ZED IMU sensor implementation using Zenoh subscriber."""
12
12
 
13
13
  import numpy as np
14
- import zenoh
15
14
 
16
- from dexcontrol.utils.subscribers.imu import IMUSubscriber
15
+ from dexcontrol.comm import create_imu_subscriber
17
16
 
18
17
 
19
18
  class ZedIMUSensor:
@@ -28,23 +27,17 @@ class ZedIMUSensor:
28
27
  def __init__(
29
28
  self,
30
29
  configs,
31
- zenoh_session: zenoh.Session,
32
30
  ) -> None:
33
31
  """Initialize the ZED IMU sensor.
34
32
 
35
33
  Args:
36
34
  configs: Configuration object containing topic, name, and other settings.
37
- zenoh_session: Active Zenoh session for communication.
38
35
  """
39
36
  self._name = configs.name
40
37
 
41
- # Create the IMU subscriber
42
- self._subscriber = IMUSubscriber(
38
+
39
+ self._subscriber = create_imu_subscriber(
43
40
  topic=configs.topic,
44
- zenoh_session=zenoh_session,
45
- name=f"{self._name}_subscriber",
46
- enable_fps_tracking=configs.enable_fps_tracking,
47
- fps_log_interval=configs.fps_log_interval,
48
41
  )
49
42
 
50
43
  def shutdown(self) -> None:
@@ -57,7 +50,8 @@ class ZedIMUSensor:
57
50
  Returns:
58
51
  True if receiving data, False otherwise.
59
52
  """
60
- return self._subscriber.is_active()
53
+ data = self._subscriber.get_latest()
54
+ return data is not None
61
55
 
62
56
  def wait_for_active(self, timeout: float = 5.0) -> bool:
63
57
  """Wait for the ZED IMU sensor to start receiving data.
@@ -68,92 +62,109 @@ class ZedIMUSensor:
68
62
  Returns:
69
63
  True if sensor becomes active, False if timeout is reached.
70
64
  """
71
- return self._subscriber.wait_for_active(timeout)
65
+ msg = self._subscriber.wait_for_message(timeout)
66
+ return msg is not None
72
67
 
73
68
  def get_obs(self, obs_keys: list[str] | None = None) -> dict[str, np.ndarray] | None:
74
69
  """Get observation data for the ZED IMU sensor.
75
70
 
76
71
  Args:
77
72
  obs_keys: List of observation keys to retrieve. If None, returns all available data.
78
- Valid keys: ['ang_vel', 'acc', 'quat', 'timestamp']
73
+ Valid keys: ['ang_vel', 'acc', 'quat', 'mag', 'timestamp']
79
74
 
80
75
  Returns:
81
76
  Dictionary with observation data including all IMU measurements.
82
77
  Keys are mapped as follows:
83
- - 'ang_vel': Angular velocity from 'angular_velocity'
84
- - 'acc': Linear acceleration from 'acceleration'
85
- - 'quat': Orientation quaternion from 'orientation'
78
+ - 'ang_vel': Angular velocity from 'gyro'
79
+ - 'acc': Linear acceleration from 'acc'
80
+ - 'quat': Orientation quaternion from 'quat'
81
+ - 'mag': Magnetometer from 'mag' (if available)
86
82
  - 'timestamp_ns': Timestamp from 'timestamp'
87
83
  """
88
84
  if obs_keys is None:
89
85
  obs_keys = ['ang_vel', 'acc', 'quat']
90
86
 
91
- data = self._subscriber.get_latest_data()
87
+ data = self._subscriber.get_latest()
92
88
  if data is None:
93
89
  return None
94
90
 
95
- obs_out = {'timestamp_ns': data['timestamp']}
91
+ obs_out = {}
92
+
93
+ # Add timestamp if available
94
+ if 'timestamp' in data:
95
+ obs_out['timestamp_ns'] = data['timestamp']
96
96
 
97
97
  for key in obs_keys:
98
98
  if key == 'ang_vel':
99
- obs_out[key] = data['angular_velocity']
99
+ obs_out[key] = data.get('gyro', np.zeros(3))
100
100
  elif key == 'acc':
101
- obs_out[key] = data['acceleration']
101
+ obs_out[key] = data.get('acc', np.zeros(3))
102
102
  elif key == 'quat':
103
- obs_out[key] = data['orientation']
104
- else:
105
- raise ValueError(f"Invalid observation key: {key}")
103
+ obs_out[key] = data.get('quat', np.array([1.0, 0.0, 0.0, 0.0]))
104
+ elif key == 'mag' and 'mag' in data:
105
+ obs_out[key] = data['mag']
106
+ elif key == 'timestamp' and 'timestamp' in data:
107
+ obs_out['timestamp_ns'] = data['timestamp']
106
108
 
107
109
  return obs_out
108
110
 
109
- def get_acceleration(self) -> np.ndarray | None:
111
+ def get_acc(self) -> np.ndarray | None:
110
112
  """Get the latest linear acceleration from ZED IMU.
111
113
 
112
114
  Returns:
113
115
  Linear acceleration [x, y, z] in m/s² if available, None otherwise.
114
116
  """
115
- return self._subscriber.get_acceleration()
117
+ data = self._subscriber.get_latest()
118
+ return data.get('acc') if data else None
116
119
 
117
- def get_angular_velocity(self) -> np.ndarray | None:
120
+ def get_gyro(self) -> np.ndarray | None:
118
121
  """Get the latest angular velocity from ZED IMU.
119
122
 
120
123
  Returns:
121
124
  Angular velocity [x, y, z] in rad/s if available, None otherwise.
122
125
  """
123
- return self._subscriber.get_angular_velocity()
126
+ data = self._subscriber.get_latest()
127
+ return data.get('gyro') if data else None
124
128
 
125
- def get_orientation(self) -> np.ndarray | None:
129
+ def get_quat(self) -> np.ndarray | None:
126
130
  """Get the latest orientation quaternion from ZED IMU.
127
131
 
128
132
  Returns:
129
- Orientation quaternion [x, y, z, w] if available, None otherwise.
133
+ Orientation quaternion [w, x, y, z] if available, None otherwise.
134
+ Note: dexcomm uses [w, x, y, z] quaternion format.
130
135
  """
131
- return self._subscriber.get_orientation()
136
+ data = self._subscriber.get_latest()
137
+ return data.get('quat') if data else None
132
138
 
133
- def get_magnetometer(self) -> np.ndarray | None:
139
+ def get_mag(self) -> np.ndarray | None:
134
140
  """Get the latest magnetometer reading from ZED IMU.
135
141
 
136
142
  Returns:
137
143
  Magnetic field [x, y, z] in µT if available, None otherwise.
138
144
  """
139
- return self._subscriber.get_magnetometer()
145
+ data = self._subscriber.get_latest()
146
+ if not data or not isinstance(data, dict):
147
+ return None
148
+ return data.get('mag', None)
140
149
 
141
- def has_magnetometer(self) -> bool:
150
+ def has_mag(self) -> bool:
142
151
  """Check if the ZED IMU has magnetometer data available.
143
152
 
144
153
  Returns:
145
154
  True if magnetometer data is available, False otherwise.
146
155
  """
147
- return self._subscriber.has_magnetometer()
148
-
149
- @property
150
- def fps(self) -> float:
151
- """Get the current FPS measurement.
152
-
153
- Returns:
154
- Current frames per second measurement.
155
- """
156
- return self._subscriber.fps
156
+ data = self._subscriber.get_latest()
157
+ if not data or not isinstance(data, dict):
158
+ return False
159
+ return 'mag' in data and data['mag'] is not None
160
+
161
+
162
+ # Backward compatibility aliases
163
+ get_acceleration = get_acc
164
+ get_angular_velocity = get_gyro
165
+ get_orientation = get_quat
166
+ get_magnetometer = get_mag
167
+ has_magnetometer = has_mag
157
168
 
158
169
  @property
159
170
  def name(self) -> str:
@@ -17,9 +17,8 @@ subscriber for scan data.
17
17
  from typing import Any
18
18
 
19
19
  import numpy as np
20
- import zenoh
21
20
 
22
- from dexcontrol.utils.subscribers.lidar import LidarSubscriber
21
+ from dexcontrol.comm import create_lidar_subscriber
23
22
 
24
23
 
25
24
  class RPLidarSensor:
@@ -32,26 +31,17 @@ class RPLidarSensor:
32
31
  def __init__(
33
32
  self,
34
33
  configs,
35
- zenoh_session: zenoh.Session,
36
34
  ) -> None:
37
35
  """Initialize the LIDAR sensor.
38
36
 
39
37
  Args:
40
- topic: Zenoh topic to subscribe to for LIDAR data.
41
- zenoh_session: Active Zenoh session for communication.
42
- name: Name for the sensor instance.
43
- enable_fps_tracking: Whether to track and log FPS metrics.
44
- fps_log_interval: Number of frames between FPS calculations.
38
+ configs: Configuration for the LIDAR sensor.
45
39
  """
46
40
  self._name = configs.name
47
41
 
48
42
  # Create the LIDAR subscriber
49
- self._subscriber = LidarSubscriber(
50
- topic=configs.topic,
51
- zenoh_session=zenoh_session,
52
- name=f"{self._name}_subscriber",
53
- enable_fps_tracking=configs.enable_fps_tracking,
54
- fps_log_interval=configs.fps_log_interval,
43
+ self._subscriber = create_lidar_subscriber(
44
+ topic=configs.topic
55
45
  )
56
46
 
57
47
 
@@ -65,7 +55,8 @@ class RPLidarSensor:
65
55
  Returns:
66
56
  True if receiving data, False otherwise.
67
57
  """
68
- return self._subscriber.is_active()
58
+ data = self._subscriber.get_latest()
59
+ return data is not None
69
60
 
70
61
  def wait_for_active(self, timeout: float = 5.0) -> bool:
71
62
  """Wait for the LIDAR sensor to start receiving data.
@@ -76,7 +67,8 @@ class RPLidarSensor:
76
67
  Returns:
77
68
  True if sensor becomes active, False if timeout is reached.
78
69
  """
79
- return self._subscriber.wait_for_active(timeout)
70
+ msg = self._subscriber.wait_for_message(timeout)
71
+ return msg is not None
80
72
 
81
73
  def get_obs(self) -> dict[str, Any] | None:
82
74
  """Get the latest LIDAR scan data.
@@ -89,7 +81,8 @@ class RPLidarSensor:
89
81
  - qualities: Array of quality values (0-255) if available, None otherwise
90
82
  - timestamp: Timestamp in nanoseconds (int)
91
83
  """
92
- return self._subscriber.get_latest_data()
84
+ data = self._subscriber.get_latest()
85
+ return data
93
86
 
94
87
  def get_ranges(self) -> np.ndarray | None:
95
88
  """Get the latest range measurements.
@@ -97,7 +90,8 @@ class RPLidarSensor:
97
90
  Returns:
98
91
  Array of range measurements in meters if available, None otherwise.
99
92
  """
100
- return self._subscriber.get_ranges()
93
+ data = self._subscriber.get_latest()
94
+ return data.ranges if data else None
101
95
 
102
96
  def get_angles(self) -> np.ndarray | None:
103
97
  """Get the latest angle measurements.
@@ -105,7 +99,8 @@ class RPLidarSensor:
105
99
  Returns:
106
100
  Array of angle measurements in radians if available, None otherwise.
107
101
  """
108
- return self._subscriber.get_angles()
102
+ data = self._subscriber.get_latest()
103
+ return data.angles if data else None
109
104
 
110
105
  def get_qualities(self) -> np.ndarray | None:
111
106
  """Get the latest quality measurements.
@@ -113,7 +108,8 @@ class RPLidarSensor:
113
108
  Returns:
114
109
  Array of quality values (0-255) if available, None otherwise.
115
110
  """
116
- return self._subscriber.get_qualities()
111
+ data = self._subscriber.get_latest()
112
+ return data.qualities if data else None
117
113
 
118
114
  def get_point_count(self) -> int:
119
115
  """Get the number of points in the latest scan.
@@ -8,7 +8,7 @@
8
8
  # 2. Commercial License
9
9
  # For commercial licensing terms, contact: contact@dexmate.ai
10
10
 
11
- """Sensor manager for managing robot sensors with Zenoh communication.
11
+ """Sensor manager for managing robot sensors.
12
12
 
13
13
  This module provides the Sensors class that manages multiple sensor instances
14
14
  based on configuration and provides unified access to them.
@@ -19,7 +19,6 @@ import traceback
19
19
  from typing import TYPE_CHECKING, Any
20
20
 
21
21
  import hydra.utils as hydra_utils
22
- import zenoh
23
22
  from loguru import logger
24
23
  from omegaconf import DictConfig, OmegaConf
25
24
 
@@ -44,7 +43,6 @@ class Sensors:
44
43
 
45
44
  Attributes:
46
45
  _sensors: List of instantiated sensor objects.
47
- _zenoh_session: Active Zenoh session for communication.
48
46
  """
49
47
 
50
48
  if TYPE_CHECKING:
@@ -61,16 +59,13 @@ class Sensors:
61
59
  lidar: RPLidarSensor
62
60
  ultrasonic: UltrasonicSensor
63
61
 
64
- def __init__(self, configs: DictConfig | VegaSensorsConfig,
65
- zenoh_session: zenoh.Session) -> None:
62
+ def __init__(self, configs: DictConfig | VegaSensorsConfig) -> None:
66
63
  """Initialize sensors from configuration.
67
64
 
68
65
  Args:
69
66
  configs: Configuration for all sensors (VegaSensorsConfig or DictConfig).
70
- zenoh_session: Active Zenoh session for communication.
71
67
  """
72
68
  self._sensors: list[Any] = []
73
- self._zenoh_session = zenoh_session
74
69
 
75
70
  dict_configs = (OmegaConf.structured(configs) if isinstance(configs, VegaSensorsConfig)
76
71
  else configs)
@@ -108,10 +103,8 @@ class Sensors:
108
103
  'configs': {k: v for k, v in config.items() if k != '_target_'}
109
104
  })
110
105
 
111
- sensor = hydra_utils.instantiate(
112
- temp_config,
113
- zenoh_session=self._zenoh_session,
114
- )
106
+ # Note: zenoh_session no longer needed as DexComm handles sessions
107
+ sensor = hydra_utils.instantiate(temp_config)
115
108
 
116
109
  if hasattr(sensor, "start"):
117
110
  sensor.start()
@@ -8,21 +8,21 @@
8
8
  # 2. Commercial License
9
9
  # For commercial licensing terms, contact: contact@dexmate.ai
10
10
 
11
- """Ultrasonic sensor implementations using Zenoh subscribers.
11
+ """Ultrasonic sensor implementations using DexComm subscribers.
12
12
 
13
- This module provides ultrasonic sensor classes that use the generic
14
- subscriber for distance measurements.
13
+ This module provides ultrasonic sensor classes that use DexComm's
14
+ Raw API for distance measurements.
15
15
  """
16
16
 
17
17
  import numpy as np
18
- import zenoh
18
+ from dexcomm.serialization import deserialize_protobuf
19
19
 
20
+ from dexcontrol.comm import create_subscriber
20
21
  from dexcontrol.proto import dexcontrol_msg_pb2
21
- from dexcontrol.utils.subscribers import ProtobufZenohSubscriber
22
22
 
23
23
 
24
24
  class UltrasonicSensor:
25
- """Ultrasonic sensor using Zenoh subscriber.
25
+ """Ultrasonic sensor using DexComm subscriber.
26
26
 
27
27
  This sensor provides distance measurements from ultrasonic sensors
28
28
  """
@@ -30,24 +30,18 @@ class UltrasonicSensor:
30
30
  def __init__(
31
31
  self,
32
32
  configs,
33
- zenoh_session: zenoh.Session,
34
33
  ) -> None:
35
34
  """Initialize the ultrasonic sensor.
36
35
 
37
36
  Args:
38
37
  configs: Configuration for the ultrasonic sensor.
39
- zenoh_session: Active Zenoh session for communication.
40
38
  """
41
39
  self._name = configs.name
42
40
 
43
- # Create the generic subscriber with JSON decoder
44
- self._subscriber = ProtobufZenohSubscriber(
41
+ # Create the protobuf subscriber using our clean DexComm integration
42
+ self._subscriber = create_subscriber(
45
43
  topic=configs.topic,
46
- zenoh_session=zenoh_session,
47
- message_type=dexcontrol_msg_pb2.UltrasonicState,
48
- name=f"{self._name}_subscriber",
49
- enable_fps_tracking=configs.enable_fps_tracking,
50
- fps_log_interval=configs.fps_log_interval,
44
+ deserializer=lambda data: deserialize_protobuf(data, dexcontrol_msg_pb2.UltrasonicState),
51
45
  )
52
46
 
53
47
 
@@ -61,7 +55,8 @@ class UltrasonicSensor:
61
55
  Returns:
62
56
  True if receiving data, False otherwise.
63
57
  """
64
- return self._subscriber.is_active()
58
+ data = self._subscriber.get_latest()
59
+ return data is not None
65
60
 
66
61
  def wait_for_active(self, timeout: float = 5.0) -> bool:
67
62
  """Wait for the ultrasonic sensor to start receiving data.
@@ -72,7 +67,8 @@ class UltrasonicSensor:
72
67
  Returns:
73
68
  True if sensor becomes active, False if timeout is reached.
74
69
  """
75
- return self._subscriber.wait_for_active(timeout)
70
+ msg = self._subscriber.wait_for_message(timeout)
71
+ return msg is not None
76
72
 
77
73
  def get_obs(self) -> np.ndarray | None:
78
74
  """Get observation data for the ultrasonic sensor.
@@ -84,7 +80,7 @@ class UltrasonicSensor:
84
80
  Numpy array of distances in meters with shape (4,) in the order:
85
81
  [front_left, front_right, back_left, back_right].
86
82
  """
87
- data = self._subscriber.get_latest_data()
83
+ data = self._subscriber.get_latest()
88
84
  if data is not None:
89
85
  obs = [
90
86
  data.front_left,
@@ -96,15 +92,6 @@ class UltrasonicSensor:
96
92
 
97
93
  return None
98
94
 
99
- @property
100
- def fps(self) -> float:
101
- """Get the current FPS measurement.
102
-
103
- Returns:
104
- Current frames per second measurement.
105
- """
106
- return self._subscriber.fps
107
-
108
95
  @property
109
96
  def name(self) -> str:
110
97
  """Get the sensor name.
@@ -7,14 +7,3 @@
7
7
  #
8
8
  # 2. Commercial License
9
9
  # For commercial licensing terms, contact: contact@dexmate.ai
10
-
11
- from .subscribers import (
12
- BaseZenohSubscriber,
13
- DecoderFunction,
14
- GenericZenohSubscriber,
15
- ProtobufZenohSubscriber,
16
- json_decoder,
17
- protobuf_decoder,
18
- raw_bytes_decoder,
19
- string_decoder,
20
- )
@@ -0,0 +1,111 @@
1
+ # Copyright (C) 2025 Dexmate Inc.
2
+ #
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
10
+
11
+ """Communication helper utilities for DexControl using DexComm.
12
+
13
+ This module provides simple helper functions for DexControl's communication
14
+ needs using the DexComm library's Raw API.
15
+ """
16
+
17
+ import json
18
+ import time
19
+ from pathlib import Path
20
+ from typing import Optional
21
+
22
+ from dexcomm import ZenohConfig, call_service
23
+ from dexcomm.serialization import deserialize_json
24
+ from loguru import logger
25
+
26
+ import dexcontrol
27
+ from dexcontrol.utils.os_utils import resolve_key_name
28
+
29
+
30
+ def get_robot_config() -> ZenohConfig:
31
+ """Get DexComm configuration for robot communication.
32
+
33
+ This checks for the robot's Zenoh configuration file and creates
34
+ appropriate DexComm config.
35
+
36
+ Returns:
37
+ ZenohConfig instance configured for the robot.
38
+ """
39
+ config_path = dexcontrol.COMM_CFG_PATH
40
+
41
+ if config_path and config_path != Path("/tmp/no_config") and config_path.exists():
42
+ logger.debug(f"Loading config from: {config_path}")
43
+ return ZenohConfig.from_file(config_path)
44
+ else:
45
+ logger.debug("No config file found, using default peer mode")
46
+ return ZenohConfig.default_peer()
47
+
48
+
49
+ def get_zenoh_config_path() -> Optional[Path]:
50
+ """Get robot config only if not using default SessionManager.
51
+
52
+ DexComm's SessionManager will automatically use the config from
53
+ environment variables if available, so we only need to provide
54
+ config explicitly if we have a specific robot config file.
55
+
56
+ Returns:
57
+ path to the config file
58
+ """
59
+ config_path = dexcontrol.COMM_CFG_PATH
60
+ return config_path
61
+
62
+
63
+ def query_json_service(
64
+ topic: str,
65
+ timeout: float = 2.0,
66
+ max_retries: int = 1,
67
+ retry_delay: float = 0.5,
68
+ ) -> dict | None:
69
+ """Query for JSON information using DexComm with retry logic.
70
+
71
+ Args:
72
+ topic: Topic to query (will be resolved with robot namespace).
73
+ timeout: Maximum time to wait for a response in seconds.
74
+ max_retries: Maximum number of retry attempts.
75
+ retry_delay: Initial delay between retries (doubles each retry).
76
+
77
+ Returns:
78
+ Dictionary containing the parsed JSON response if successful, None otherwise.
79
+ """
80
+ resolved_topic = resolve_key_name(topic)
81
+ logger.debug(f"Querying topic: {resolved_topic}")
82
+
83
+ current_delay = retry_delay
84
+ for attempt in range(max_retries + 1):
85
+ try:
86
+ data = call_service(
87
+ resolved_topic,
88
+ timeout=timeout,
89
+ config=get_zenoh_config_path(),
90
+ request_serializer=None,
91
+ response_deserializer=deserialize_json,
92
+ )
93
+
94
+ if data:
95
+ logger.debug(f"Successfully received JSON data from {resolved_topic}")
96
+ return data
97
+
98
+ except json.JSONDecodeError as e:
99
+ logger.warning(f"Failed to parse JSON response from {resolved_topic}: {e}")
100
+ except Exception as e:
101
+ logger.warning(
102
+ f"Query failed for {resolved_topic} (attempt {attempt + 1}/{max_retries + 1}): {e}"
103
+ )
104
+
105
+ if attempt < max_retries:
106
+ logger.debug(f"Retrying in {current_delay:.1f} seconds...")
107
+ time.sleep(current_delay)
108
+ current_delay *= 2 # Exponential backoff
109
+
110
+ logger.error(f"Failed to query {resolved_topic} after {max_retries + 1} attempts")
111
+ return None
@@ -16,7 +16,7 @@ from typing import Final
16
16
  ROBOT_NAME_ENV_VAR: Final[str] = "ROBOT_NAME"
17
17
 
18
18
  # Environment variable for communication config path
19
- COMM_CFG_PATH_ENV_VAR: Final[str] = "DEXMATE_COMM_CFG_PATH"
19
+ COMM_CFG_PATH_ENV_VAR: Final[str] = "ZENOH_CONFIG"
20
20
 
21
21
  # Environment variable to disable heartbeat monitoring
22
22
  DISABLE_HEARTBEAT_ENV_VAR: Final[str] = "DEXCONTROL_DISABLE_HEARTBEAT"
@@ -137,28 +137,14 @@ def validate_server_version(version_info: dict[str, Any]) -> None:
137
137
  return
138
138
 
139
139
  # Check each component's software version
140
- components_below_min = []
141
- for component_name, component_info in server_info.items():
142
- if isinstance(component_info, dict):
143
- software_version = component_info.get("software_version")
144
- if software_version is not None:
145
- try:
146
- # Convert to int if it's a string
147
- software_version_int = int(software_version)
148
- if software_version_int < dexcontrol.MIN_SOC_SOFTWARE_VERSION:
149
- components_below_min.append(
150
- (component_name, software_version_int)
151
- )
152
- except (ValueError, TypeError) as e:
153
- logger.debug(
154
- f"Could not parse software version for {component_name}: {e}"
155
- )
156
-
157
- # If any components are below minimum version, show warning
158
- if components_below_min:
159
- show_server_version_warning(
160
- components_below_min, dexcontrol.MIN_SOC_SOFTWARE_VERSION
161
- )
140
+ soc_info = server_info.get("soc", {})
141
+ software_version = soc_info.get("software_version", {})
142
+ if software_version is not None:
143
+ software_version_int = int(software_version)
144
+ if software_version_int < dexcontrol.MIN_SOC_SOFTWARE_VERSION:
145
+ show_server_version_warning(
146
+ [("soc", software_version_int)], dexcontrol.MIN_SOC_SOFTWARE_VERSION
147
+ )
162
148
 
163
149
 
164
150
  def show_server_version_warning(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dexcontrol
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A Python library of Sensing and Control for Dexmate's Robot
5
5
  Project-URL: Repository, https://github.com/dexmate-ai/dexcontrol
6
6
  Author-email: Dexmate <contact@dexmate.ai>
@@ -202,6 +202,7 @@ Classifier: Programming Language :: Python :: 3.13
202
202
  Classifier: Typing :: Typed
203
203
  Requires-Python: <3.14,>=3.10
204
204
  Requires-Dist: aiortc
205
+ Requires-Dist: dexcomm>=0.1.2
205
206
  Requires-Dist: eclipse-zenoh>=1.2.0
206
207
  Requires-Dist: hydra-core==1.3.2
207
208
  Requires-Dist: jaxtyping>=0.2.38