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

@@ -10,3 +10,5 @@
10
10
 
11
11
  from .rgb_camera import RGBCameraConfig
12
12
  from .zed_camera import ZedCameraConfig
13
+
14
+ __all__ = ["RGBCameraConfig", "ZedCameraConfig"]
@@ -13,16 +13,33 @@ from dataclasses import dataclass, field
13
13
 
14
14
  @dataclass
15
15
  class RGBCameraConfig:
16
+ """Configuration for the RGBCameraSensor.
17
+
18
+ Attributes:
19
+ _target_: The target class to instantiate.
20
+ name: A unique name for the sensor instance.
21
+ enable: Whether the sensor is enabled.
22
+ use_rtc: If True, uses the high-performance RTCSubscriber. If False,
23
+ uses the standard RGBCameraSubscriber with a Zenoh topic.
24
+ enable_fps_tracking: If True, tracks and logs the FPS of the stream.
25
+ fps_log_interval: The interval in seconds for logging FPS.
26
+ subscriber_config: A dictionary containing the configuration for the
27
+ underlying subscriber (either RTC or standard).
28
+ """
29
+
16
30
  _target_: str = "dexcontrol.sensors.camera.rgb_camera.RGBCameraSensor"
17
31
  name: str = "rgb_camera"
32
+ enable: bool = False
33
+ use_rtc: bool = True
18
34
  enable_fps_tracking: bool = False
19
35
  fps_log_interval: int = 30
20
- enable: bool = False
36
+
21
37
  subscriber_config: dict = field(
22
- default_factory=lambda: dict(
23
- rgb=dict(
24
- enable=True,
25
- info_key="camera/rgb/info",
26
- )
27
- )
38
+ default_factory=lambda: {
39
+ "rgb": {
40
+ "enable": True,
41
+ "info_key": "camera/rgb/info",
42
+ "topic": "camera/rgb",
43
+ }
44
+ }
28
45
  )
@@ -13,24 +13,43 @@ from dataclasses import dataclass, field
13
13
 
14
14
  @dataclass
15
15
  class ZedCameraConfig:
16
+ """Configuration for the ZedCameraSensor.
17
+
18
+ Attributes:
19
+ _target_: The target class to instantiate.
20
+ name: A unique name for the sensor instance.
21
+ enable: Whether the sensor is enabled.
22
+ use_rtc: If True, RGB streams use the high-performance RTCSubscriber.
23
+ If False, they use the standard RGBCameraSubscriber. Depth
24
+ always uses a standard Zenoh subscriber.
25
+ enable_fps_tracking: If True, tracks and logs the FPS of all streams.
26
+ fps_log_interval: The interval in seconds for logging FPS.
27
+ subscriber_config: A dictionary containing the configurations for each
28
+ individual stream (left_rgb, right_rgb, depth).
29
+ """
30
+
16
31
  _target_: str = "dexcontrol.sensors.camera.zed_camera.ZedCameraSensor"
17
32
  name: str = "zed_camera"
33
+ enable: bool = False
34
+ use_rtc: bool = True
18
35
  enable_fps_tracking: bool = False
19
36
  fps_log_interval: int = 30
20
- enable: bool = False
37
+
21
38
  subscriber_config: dict = field(
22
- default_factory=lambda: dict(
23
- left_rgb=dict(
24
- enable=True,
25
- info_key="camera/head/left_rgb/info",
26
- ),
27
- right_rgb=dict(
28
- enable=True,
29
- info_key="camera/head/right_rgb/info",
30
- ),
31
- depth=dict(
32
- enable=True,
33
- topic="camera/head/depth",
34
- ),
35
- )
39
+ default_factory=lambda: {
40
+ "left_rgb": {
41
+ "enable": True,
42
+ "info_key": "camera/head/left_rgb/info",
43
+ "topic": "camera/head/left_rgb",
44
+ },
45
+ "right_rgb": {
46
+ "enable": True,
47
+ "info_key": "camera/head/right_rgb/info",
48
+ "topic": "camera/head/right_rgb",
49
+ },
50
+ "depth": {
51
+ "enable": True,
52
+ "topic": "camera/head/depth",
53
+ },
54
+ }
36
55
  )
@@ -38,6 +38,7 @@ def _make_rgb_camera(name: str) -> Callable[[], RGBCameraConfig]:
38
38
  rgb=dict(
39
39
  enable=True,
40
40
  info_key=f"camera/base/{name}/rgb/info",
41
+ topic=f"camera/base/{name}/rgb",
41
42
  )
42
43
  ),
43
44
  name=f"base_{name}_camera",
dexcontrol/config/vega.py CHANGED
@@ -167,6 +167,12 @@ class Vega1Config(VegaConfig):
167
167
  wheels_dist=0.45,
168
168
  )
169
169
  )
170
+ head: HeadConfig = field(
171
+ default_factory=lambda: HeadConfig(
172
+ joint_limit_lower=[-1.483, -2.792, -1.378],
173
+ joint_limit_upper=[1.483, 2.792, 1.483],
174
+ )
175
+ )
170
176
 
171
177
 
172
178
  # Register variant configurations
@@ -25,7 +25,7 @@ _sym_db = _symbol_database.Default()
25
25
 
26
26
 
27
27
 
28
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x64\x65xcontrol_query.proto\x12\ndexcontrol\"Z\n\nSetArmMode\x12)\n\x04mode\x18\x01 \x01(\x0e\x32\x1b.dexcontrol.SetArmMode.Mode\"!\n\x04Mode\x12\x0c\n\x08POSITION\x10\x00\x12\x0b\n\x07\x44ISABLE\x10\x01\"Z\n\x0bSetHeadMode\x12*\n\x04mode\x18\x01 \x01(\x0e\x32\x1c.dexcontrol.SetHeadMode.Mode\"\x1f\n\x04Mode\x12\n\n\x06\x45NABLE\x10\x00\x12\x0b\n\x07\x44ISABLE\x10\x01\"\x1a\n\x08SetEstop\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"\x18\n\x06SetLed\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"\x82\x01\n\nClearError\x12\x33\n\tcomponent\x18\x01 \x01(\x0e\x32 .dexcontrol.ClearError.Component\"?\n\tComponent\x12\x0c\n\x08LEFT_ARM\x10\x00\x12\r\n\tRIGHT_ARM\x10\x01\x12\x08\n\x04HEAD\x10\x02\x12\x0b\n\x07\x43HASSIS\x10\x03\"y\n\x0fRebootComponent\x12\x38\n\tcomponent\x18\x01 \x01(\x0e\x32%.dexcontrol.RebootComponent.Component\",\n\tComponent\x12\x07\n\x03\x41RM\x10\x00\x12\t\n\x05TORSO\x10\x01\x12\x0b\n\x07\x43HASSIS\x10\x02\"X\n\x0f\x46irmwareVersion\x12\x18\n\x10hardware_version\x18\x01 \x01(\x05\x12\x18\n\x10software_version\x18\x02 \x01(\x05\x12\x11\n\tmain_hash\x18\x03 \x01(\t\"\xb2\x01\n\x0fSoftwareVersion\x12J\n\x10\x66irmware_version\x18\x01 \x03(\x0b\x32\x30.dexcontrol.SoftwareVersion.FirmwareVersionEntry\x1aS\n\x14\x46irmwareVersionEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.dexcontrol.FirmwareVersion:\x02\x38\x01\"\x9d\x01\n\x14SingleComponentState\x12\x11\n\tconnected\x18\x01 \x01(\x08\x12,\n\x07\x65nabled\x18\x02 \x01(\x0e\x32\x1b.dexcontrol.ComponentStatus\x12\x30\n\x0b\x65rror_state\x18\x03 \x01(\x0e\x32\x1b.dexcontrol.ComponentStatus\x12\x12\n\nerror_code\x18\x04 \x01(\t\"\x9b\x01\n\x0f\x43omponentStates\x12\x37\n\x06states\x18\x01 \x03(\x0b\x32\'.dexcontrol.ComponentStates.StatesEntry\x1aO\n\x0bStatesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12/\n\x05value\x18\x02 \x01(\x0b\x32 .dexcontrol.SingleComponentState:\x02\x38\x01*0\n\x0f\x43omponentStatus\x12\n\n\x06NORMAL\x10\x00\x12\x06\n\x02NA\x10\x01\x12\t\n\x05\x45RROR\x10\x02\x62\x06proto3')
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x64\x65xcontrol_query.proto\x12\ndexcontrol\"Z\n\nSetArmMode\x12)\n\x04mode\x18\x01 \x01(\x0e\x32\x1b.dexcontrol.SetArmMode.Mode\"!\n\x04Mode\x12\x0c\n\x08POSITION\x10\x00\x12\x0b\n\x07\x44ISABLE\x10\x01\"Z\n\x0bSetHeadMode\x12*\n\x04mode\x18\x01 \x01(\x0e\x32\x1c.dexcontrol.SetHeadMode.Mode\"\x1f\n\x04Mode\x12\n\n\x06\x45NABLE\x10\x00\x12\x0b\n\x07\x44ISABLE\x10\x01\"\x1a\n\x08SetEstop\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"\x18\n\x06SetLed\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"\x82\x01\n\nClearError\x12\x33\n\tcomponent\x18\x01 \x01(\x0e\x32 .dexcontrol.ClearError.Component\"?\n\tComponent\x12\x0c\n\x08LEFT_ARM\x10\x00\x12\r\n\tRIGHT_ARM\x10\x01\x12\x08\n\x04HEAD\x10\x02\x12\x0b\n\x07\x43HASSIS\x10\x03\"y\n\x0fRebootComponent\x12\x38\n\tcomponent\x18\x01 \x01(\x0e\x32%.dexcontrol.RebootComponent.Component\",\n\tComponent\x12\x07\n\x03\x41RM\x10\x00\x12\t\n\x05TORSO\x10\x01\x12\x0b\n\x07\x43HASSIS\x10\x02\"\x80\x01\n\x0f\x46irmwareVersion\x12\x18\n\x10hardware_version\x18\x01 \x01(\x05\x12\x18\n\x10software_version\x18\x02 \x01(\x05\x12\x14\n\x0c\x63ompile_time\x18\x03 \x01(\x05\x12\x11\n\tmain_hash\x18\x04 \x01(\t\x12\x10\n\x08sub_hash\x18\x05 \x01(\t\"\xb2\x01\n\x0fSoftwareVersion\x12J\n\x10\x66irmware_version\x18\x01 \x03(\x0b\x32\x30.dexcontrol.SoftwareVersion.FirmwareVersionEntry\x1aS\n\x14\x46irmwareVersionEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.dexcontrol.FirmwareVersion:\x02\x38\x01\"\x9d\x01\n\x14SingleComponentState\x12\x11\n\tconnected\x18\x01 \x01(\x08\x12,\n\x07\x65nabled\x18\x02 \x01(\x0e\x32\x1b.dexcontrol.ComponentStatus\x12\x30\n\x0b\x65rror_state\x18\x03 \x01(\x0e\x32\x1b.dexcontrol.ComponentStatus\x12\x12\n\nerror_code\x18\x04 \x01(\t\"\x9b\x01\n\x0f\x43omponentStates\x12\x37\n\x06states\x18\x01 \x03(\x0b\x32\'.dexcontrol.ComponentStates.StatesEntry\x1aO\n\x0bStatesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12/\n\x05value\x18\x02 \x01(\x0b\x32 .dexcontrol.SingleComponentState:\x02\x38\x01*0\n\x0f\x43omponentStatus\x12\n\n\x06NORMAL\x10\x00\x12\x06\n\x02NA\x10\x01\x12\t\n\x05\x45RROR\x10\x02\x62\x06proto3')
29
29
 
30
30
  _globals = globals()
31
31
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -36,8 +36,8 @@ if not _descriptor._USE_C_DESCRIPTORS:
36
36
  _globals['_SOFTWAREVERSION_FIRMWAREVERSIONENTRY']._serialized_options = b'8\001'
37
37
  _globals['_COMPONENTSTATES_STATESENTRY']._loaded_options = None
38
38
  _globals['_COMPONENTSTATES_STATESENTRY']._serialized_options = b'8\001'
39
- _globals['_COMPONENTSTATUS']._serialized_start=1121
40
- _globals['_COMPONENTSTATUS']._serialized_end=1169
39
+ _globals['_COMPONENTSTATUS']._serialized_start=1162
40
+ _globals['_COMPONENTSTATUS']._serialized_end=1210
41
41
  _globals['_SETARMMODE']._serialized_start=38
42
42
  _globals['_SETARMMODE']._serialized_end=128
43
43
  _globals['_SETARMMODE_MODE']._serialized_start=95
@@ -58,16 +58,16 @@ if not _descriptor._USE_C_DESCRIPTORS:
58
58
  _globals['_REBOOTCOMPONENT']._serialized_end=530
59
59
  _globals['_REBOOTCOMPONENT_COMPONENT']._serialized_start=486
60
60
  _globals['_REBOOTCOMPONENT_COMPONENT']._serialized_end=530
61
- _globals['_FIRMWAREVERSION']._serialized_start=532
62
- _globals['_FIRMWAREVERSION']._serialized_end=620
63
- _globals['_SOFTWAREVERSION']._serialized_start=623
64
- _globals['_SOFTWAREVERSION']._serialized_end=801
65
- _globals['_SOFTWAREVERSION_FIRMWAREVERSIONENTRY']._serialized_start=718
66
- _globals['_SOFTWAREVERSION_FIRMWAREVERSIONENTRY']._serialized_end=801
67
- _globals['_SINGLECOMPONENTSTATE']._serialized_start=804
68
- _globals['_SINGLECOMPONENTSTATE']._serialized_end=961
69
- _globals['_COMPONENTSTATES']._serialized_start=964
70
- _globals['_COMPONENTSTATES']._serialized_end=1119
71
- _globals['_COMPONENTSTATES_STATESENTRY']._serialized_start=1040
72
- _globals['_COMPONENTSTATES_STATESENTRY']._serialized_end=1119
61
+ _globals['_FIRMWAREVERSION']._serialized_start=533
62
+ _globals['_FIRMWAREVERSION']._serialized_end=661
63
+ _globals['_SOFTWAREVERSION']._serialized_start=664
64
+ _globals['_SOFTWAREVERSION']._serialized_end=842
65
+ _globals['_SOFTWAREVERSION_FIRMWAREVERSIONENTRY']._serialized_start=759
66
+ _globals['_SOFTWAREVERSION_FIRMWAREVERSIONENTRY']._serialized_end=842
67
+ _globals['_SINGLECOMPONENTSTATE']._serialized_start=845
68
+ _globals['_SINGLECOMPONENTSTATE']._serialized_end=1002
69
+ _globals['_COMPONENTSTATES']._serialized_start=1005
70
+ _globals['_COMPONENTSTATES']._serialized_end=1160
71
+ _globals['_COMPONENTSTATES_STATESENTRY']._serialized_start=1081
72
+ _globals['_COMPONENTSTATES_STATESENTRY']._serialized_end=1160
73
73
  # @@protoc_insertion_point(module_scope)
@@ -86,14 +86,18 @@ class RebootComponent(_message.Message):
86
86
  def __init__(self, component: _Optional[_Union[RebootComponent.Component, str]] = ...) -> None: ...
87
87
 
88
88
  class FirmwareVersion(_message.Message):
89
- __slots__ = ("hardware_version", "software_version", "main_hash")
89
+ __slots__ = ("hardware_version", "software_version", "compile_time", "main_hash", "sub_hash")
90
90
  HARDWARE_VERSION_FIELD_NUMBER: _ClassVar[int]
91
91
  SOFTWARE_VERSION_FIELD_NUMBER: _ClassVar[int]
92
+ COMPILE_TIME_FIELD_NUMBER: _ClassVar[int]
92
93
  MAIN_HASH_FIELD_NUMBER: _ClassVar[int]
94
+ SUB_HASH_FIELD_NUMBER: _ClassVar[int]
93
95
  hardware_version: int
94
96
  software_version: int
97
+ compile_time: int
95
98
  main_hash: str
96
- def __init__(self, hardware_version: _Optional[int] = ..., software_version: _Optional[int] = ..., main_hash: _Optional[str] = ...) -> None: ...
99
+ sub_hash: str
100
+ def __init__(self, hardware_version: _Optional[int] = ..., software_version: _Optional[int] = ..., compile_time: _Optional[int] = ..., main_hash: _Optional[str] = ..., sub_hash: _Optional[str] = ...) -> None: ...
97
101
 
98
102
  class SoftwareVersion(_message.Message):
99
103
  __slots__ = ("firmware_version",)
dexcontrol/robot.py CHANGED
@@ -51,6 +51,7 @@ from dexcontrol.sensors import Sensors
51
51
  from dexcontrol.utils.constants import ROBOT_NAME_ENV_VAR
52
52
  from dexcontrol.utils.os_utils import get_robot_model, resolve_key_name
53
53
  from dexcontrol.utils.pb_utils import (
54
+ TYPE_SOFTWARE_VERSION,
54
55
  ComponentStatus,
55
56
  software_version_to_dict,
56
57
  status_to_dict,
@@ -416,9 +417,12 @@ class Robot:
416
417
  timeout_sec: Final[int] = 8
417
418
  check_interval: Final[float] = 0.1 # Check every 100ms
418
419
 
419
- with console.status(
420
+ status = console.status(
420
421
  "[bold green]Waiting for components to become active..."
421
- ) as status:
422
+ )
423
+ status.start()
424
+
425
+ try:
422
426
  for name in component_names:
423
427
  # Check if shutdown was triggered
424
428
  if self._shutdown_called:
@@ -447,6 +451,8 @@ class Robot:
447
451
 
448
452
  # Wait a short interval before checking again
449
453
  time.sleep(check_interval)
454
+ finally:
455
+ status.stop()
450
456
 
451
457
  if not all(actives):
452
458
  self.shutdown()
@@ -527,34 +533,30 @@ class Robot:
527
533
  f"Error stopping component {component.__class__.__name__}: {e}"
528
534
  )
529
535
 
530
- # Longer delay to ensure all stop operations complete and background threads settle
531
- time.sleep(0.3)
536
+ # Brief delay to ensure all stop operations complete
537
+ time.sleep(0.1)
532
538
 
533
539
  # Shutdown sensors first (they may have background threads)
534
540
  try:
535
- self.sensors.shutdown()
536
- # Give extra time for all sensor subscribers to undeclare cleanly
537
- time.sleep(0.5)
541
+ if hasattr(self, "sensors") and self.sensors is not None:
542
+ self.sensors.shutdown()
543
+ # Give time for sensor subscribers to undeclare cleanly
544
+ time.sleep(0.2)
538
545
  except Exception as e: # pylint: disable=broad-except
539
546
  logger.error(f"Error shutting down sensors: {e}")
540
547
 
541
- # Additional delay for sensor shutdown to complete
542
- time.sleep(0.2)
543
-
544
548
  # Shutdown components in reverse order
545
549
  for component in reversed(self._components):
546
550
  if component is not None:
547
551
  try:
548
552
  component.shutdown()
549
- # Small delay between each component shutdown to prevent race conditions
550
- time.sleep(0.05)
551
553
  except Exception as e: # pylint: disable=broad-except
552
554
  logger.error(
553
555
  f"Error shutting down component {component.__class__.__name__}: {e}"
554
556
  )
555
557
 
556
- # Longer delay to allow all component shutdown to complete
557
- time.sleep(0.5)
558
+ # Brief delay to allow component shutdown to complete
559
+ time.sleep(0.1)
558
560
 
559
561
  # Enhanced Zenoh session close with better synchronization
560
562
  zenoh_close_success = False
@@ -563,8 +565,8 @@ class Robot:
563
565
  """Close zenoh session in a separate thread."""
564
566
  nonlocal zenoh_close_success
565
567
  try:
566
- # Give Zenoh even more time to clean up internal resources and settle threads
567
- time.sleep(0.5)
568
+ # Brief delay for Zenoh cleanup
569
+ time.sleep(0.1)
568
570
  self._zenoh_session.close()
569
571
  zenoh_close_success = True
570
572
  except Exception as e: # pylint: disable=broad-except
@@ -574,20 +576,19 @@ class Robot:
574
576
  close_thread = threading.Thread(target=_close_zenoh_session, daemon=False)
575
577
  close_thread.start()
576
578
 
577
- # Wait for close with extended timeout to avoid race conditions
579
+ # Wait for close with reasonable timeout
578
580
  close_thread.join(timeout=3.0)
579
581
 
580
582
  if close_thread.is_alive():
581
583
  logger.warning(
582
- "Zenoh session close timed out after 5s, continuing shutdown"
584
+ "Zenoh session close timed out after 1s, continuing shutdown"
583
585
  )
584
- # Force daemon mode so it doesn't block process exit
585
- close_thread.daemon = True
586
+ # Thread will be left to finish in background, but won't block shutdown
586
587
  elif zenoh_close_success:
587
588
  logger.debug("Zenoh session closed cleanly")
588
589
 
589
- # Extended final delay to allow any remaining Zenoh background threads to finish
590
- time.sleep(0.8)
590
+ # Brief final delay for cleanup
591
+ time.sleep(0.5)
591
592
 
592
593
  logger.info("Robot shutdown complete")
593
594
 
@@ -601,13 +602,7 @@ class Robot:
601
602
 
602
603
  def get_software_version(
603
604
  self, show: bool = True
604
- ) -> dict[
605
- str,
606
- dict[
607
- Literal["hardware_version", "software_version", "main_hash"],
608
- int | str,
609
- ],
610
- ]:
605
+ ) -> dict[str, TYPE_SOFTWARE_VERSION]:
611
606
  """Retrieve software version information for all components.
612
607
 
613
608
  Args:
@@ -8,64 +8,94 @@
8
8
  # 2. Commercial License
9
9
  # For commercial licensing terms, contact: contact@dexmate.ai
10
10
 
11
- """RGB camera sensor implementation using RTC subscriber with Zenoh query."""
11
+ """RGB camera sensor implementation using RTC or Zenoh subscriber."""
12
12
 
13
13
  import logging
14
+ from typing import Optional, Union
14
15
 
15
16
  import numpy as np
16
17
  import zenoh
17
18
 
19
+ from dexcontrol.config.sensors.cameras import RGBCameraConfig
18
20
  from dexcontrol.utils.rtc_utils import create_rtc_subscriber_with_config
21
+ from dexcontrol.utils.subscribers.camera import RGBCameraSubscriber
22
+ from dexcontrol.utils.subscribers.rtc import RTCSubscriber
19
23
 
20
24
  logger = logging.getLogger(__name__)
21
25
 
22
26
 
23
27
  class RGBCameraSensor:
24
- """RGB camera sensor using RTC subscriber.
28
+ """RGB camera sensor that supports both RTC and standard Zenoh subscribers.
25
29
 
26
- This sensor provides RGB image data from a camera using RTC.
27
- It first queries Zenoh for RTC connection information, then creates
28
- a RTC subscriber for efficient data handling.
30
+ This sensor provides RGB image data from a camera. It can be configured to use
31
+ either a high-performance RTC subscriber for real-time video streams or a
32
+ standard Zenoh subscriber for raw image topics. The mode is controlled by the
33
+ `use_rtc` flag in the `RGBCameraConfig`.
29
34
  """
30
35
 
31
36
  def __init__(
32
37
  self,
33
- configs,
38
+ configs: RGBCameraConfig,
34
39
  zenoh_session: zenoh.Session,
35
- *args,
36
- **kwargs,
37
40
  ) -> None:
38
- """Initialize the RGB camera sensor.
41
+ """Initialize the RGB camera sensor based on the provided configuration.
39
42
 
40
43
  Args:
41
- configs: Configuration for the RGB camera sensor.
44
+ configs: Configuration object for the RGB camera sensor.
42
45
  zenoh_session: Active Zenoh session for communication.
43
46
  """
44
47
  self._name = configs.name
45
-
46
- # Create the RTC subscriber using the utility function
47
- self._subscriber = create_rtc_subscriber_with_config(
48
- zenoh_session=zenoh_session,
49
- config=configs.subscriber_config.rgb,
50
- name=f"{self._name}_subscriber",
51
- enable_fps_tracking=configs.enable_fps_tracking,
52
- fps_log_interval=configs.fps_log_interval,
53
- )
54
-
55
- if self._subscriber is None:
56
- logger.warning(f"Failed to create RTC subscriber for {self._name}")
57
- # Continue initialization without subscriber
48
+ self._subscriber: Optional[Union[RTCSubscriber, RGBCameraSubscriber]] = None
49
+
50
+ subscriber_config = configs.subscriber_config.get("rgb", {})
51
+ if not subscriber_config or not subscriber_config.get("enable", False):
52
+ logger.info(f"RGBCameraSensor '{self._name}' is disabled in config.")
53
+ return
54
+
55
+ try:
56
+ if configs.use_rtc:
57
+ logger.info(f"'{self._name}': Using RTC subscriber.")
58
+ self._subscriber = create_rtc_subscriber_with_config(
59
+ zenoh_session=zenoh_session,
60
+ config=subscriber_config,
61
+ name=f"{self._name}_subscriber",
62
+ enable_fps_tracking=configs.enable_fps_tracking,
63
+ fps_log_interval=configs.fps_log_interval,
64
+ )
65
+ else:
66
+ logger.info(f"'{self._name}': Using standard Zenoh subscriber.")
67
+ topic = subscriber_config.get("topic")
68
+ if topic:
69
+ self._subscriber = RGBCameraSubscriber(
70
+ topic=topic,
71
+ zenoh_session=zenoh_session,
72
+ name=f"{self._name}_subscriber",
73
+ enable_fps_tracking=configs.enable_fps_tracking,
74
+ fps_log_interval=configs.fps_log_interval,
75
+ )
76
+ else:
77
+ logger.warning(
78
+ f"No 'topic' specified for '{self._name}' in non-RTC mode."
79
+ )
80
+
81
+ if self._subscriber is None:
82
+ logger.warning(f"Failed to create subscriber for '{self._name}'.")
83
+
84
+ except Exception as e:
85
+ logger.error(f"Error creating subscriber for '{self._name}': {e}")
86
+ self._subscriber = None
58
87
 
59
88
  def shutdown(self) -> None:
60
- """Shutdown the camera sensor."""
89
+ """Shutdown the camera sensor and release all resources."""
61
90
  if self._subscriber:
62
91
  self._subscriber.shutdown()
92
+ logger.info(f"'{self._name}' sensor shut down.")
63
93
 
64
94
  def is_active(self) -> bool:
65
95
  """Check if the camera sensor is actively receiving data.
66
96
 
67
97
  Returns:
68
- True if receiving data, False otherwise.
98
+ True if the subscriber exists and is receiving data, False otherwise.
69
99
  """
70
100
  return self._subscriber.is_active() if self._subscriber else False
71
101
 
@@ -76,15 +106,18 @@ class RGBCameraSensor:
76
106
  timeout: Maximum time to wait in seconds.
77
107
 
78
108
  Returns:
79
- True if sensor becomes active, False if timeout is reached.
109
+ True if the sensor becomes active within the timeout, False otherwise.
80
110
  """
81
- return self._subscriber.wait_for_active(timeout) if self._subscriber else False
111
+ if not self._subscriber:
112
+ logger.warning(f"'{self._name}': Cannot wait, no subscriber initialized.")
113
+ return False
114
+ return self._subscriber.wait_for_active(timeout)
82
115
 
83
116
  def get_obs(self) -> np.ndarray | None:
84
- """Get the latest RGB image data.
117
+ """Get the latest observation (RGB image) from the sensor.
85
118
 
86
119
  Returns:
87
- Latest RGB image as numpy array (HxWxC) if available, None otherwise.
120
+ The latest RGB image as a numpy array (HxWxC) if available, otherwise None.
88
121
  """
89
122
  return self._subscriber.get_latest_data() if self._subscriber else None
90
123