From 13618f1e7ae975c2e51fc3b18d60370e0fe584aa Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:28:06 +0300 Subject: [PATCH 01/13] refactor: drop unused FloatArray alias The FloatArray alias and its numpy.typing.NDArray import were never referenced. Remove both. --- dimos/hardware/manipulators/damiao/base_adapter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index 9528f05362..cfc56412e8 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -20,7 +20,6 @@ from typing import Any, cast import numpy as np -from numpy.typing import NDArray from dimos.hardware.manipulators.damiao.specs import DamiaoArmSpec from dimos.hardware.manipulators.spec import ControlMode, JointLimits, ManipulatorInfo @@ -28,8 +27,6 @@ logger = setup_logger() -FloatArray = NDArray[np.float64] - _can_motor_control: Any | None _damiao: Any | None From 8b9468f05b1a394006ca2ae1a1234bf62010993b Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:28:37 +0300 Subject: [PATCH 02/13] refactor: drop unused OpenArmRSMotorSpecConfig alias The OpenArmRSMotorSpecConfig alias for DamiaoMotorSpec was only referenced by its own __all__ entry; nothing imports it. Remove both. --- dimos/hardware/manipulators/openarm_rs/adapter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dimos/hardware/manipulators/openarm_rs/adapter.py b/dimos/hardware/manipulators/openarm_rs/adapter.py index 99d6f4337e..76225c05af 100644 --- a/dimos/hardware/manipulators/openarm_rs/adapter.py +++ b/dimos/hardware/manipulators/openarm_rs/adapter.py @@ -26,8 +26,6 @@ if TYPE_CHECKING: from dimos.hardware.manipulators.registry import AdapterRegistry -OpenArmRSMotorSpecConfig = DamiaoMotorSpec - class OpenArmRSBindingUnavailableError(DamiaoBindingUnavailableError): pass @@ -128,6 +126,5 @@ def register(registry: AdapterRegistry) -> None: __all__ = [ "OpenArmRSAdapter", "OpenArmRSBindingUnavailableError", - "OpenArmRSMotorSpecConfig", "register", ] From 872fc4395dc9daea888f103d7c6fa6c6d854916d Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:28:54 +0300 Subject: [PATCH 03/13] refactor: hoist time import in openarm adapter test Move 'import time' out of FakeState.__init__ to the module top, per the imports-at-top convention. FakeState.timestamp is still read by the adapter's staleness check, so it stays. --- dimos/hardware/manipulators/openarm/test_adapter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dimos/hardware/manipulators/openarm/test_adapter.py b/dimos/hardware/manipulators/openarm/test_adapter.py index 2f15ef1ee1..66eb87cb69 100644 --- a/dimos/hardware/manipulators/openarm/test_adapter.py +++ b/dimos/hardware/manipulators/openarm/test_adapter.py @@ -14,6 +14,7 @@ from __future__ import annotations +import time from unittest.mock import MagicMock import pytest @@ -24,8 +25,6 @@ class FakeState: def __init__(self, index: int) -> None: - import time - self.q = 0.1 * index self.dq = 0.2 * index self.tau = 0.3 * index From cc738dc68e7ca8e7696fa4126048195b24c796a5 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:29:13 +0300 Subject: [PATCH 04/13] refactor: stop restating openarm_rs adapter defaults in blueprint gravity_comp=True and canfd=True are already the OpenArmRSAdapter defaults. The blueprint should only specify what differs, so keep just gravity_model_path. --- dimos/robot/manipulators/openarm/blueprints.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dimos/robot/manipulators/openarm/blueprints.py b/dimos/robot/manipulators/openarm/blueprints.py index 90f20041c6..ecd60dd39d 100644 --- a/dimos/robot/manipulators/openarm/blueprints.py +++ b/dimos/robot/manipulators/openarm/blueprints.py @@ -59,12 +59,10 @@ # replaced / factory-reset). AUTO_SET_MIT_MODE = True +# gravity_comp and canfd are already True by default on OpenArmRSAdapter, so +# the blueprint only overrides the gravity model path. _ADAPTER_KWARGS = {"auto_set_mit_mode": AUTO_SET_MIT_MODE} -_OPENARM_RS_ADAPTER_KWARGS = { - "gravity_model_path": OPENARM_V10_RIGHT_MODEL, - "gravity_comp": True, - "canfd": True, -} +_OPENARM_RS_ADAPTER_KWARGS = {"gravity_model_path": OPENARM_V10_RIGHT_MODEL} _left_hw = _openarm( side="left", address=LEFT_CAN, From 40dc78e27ba1fe380667581843dff73775a24acb Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:29:32 +0300 Subject: [PATCH 05/13] docs: explain lazy pinocchio imports in damiao base adapter Per the imports convention, a lazy import of a heavy optional dep needs a comment saying why. Add it to both gravity-model import sites. --- dimos/hardware/manipulators/damiao/base_adapter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index cfc56412e8..701c098999 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -494,6 +494,8 @@ def write_clear_errors(self) -> bool: def _load_gravity_model(self) -> None: if not self._gravity_comp or self._gravity_model_path is None: return + # Lazy import: pinocchio is a heavy optional dep only needed when + # gravity compensation is enabled, so keep it out of module import. import pinocchio build_model_from_urdf = _dynamic_attr(pinocchio, "buildModelFromUrdf") @@ -504,6 +506,8 @@ def compute_gravity_torques(self, q: list[float]) -> list[float]: self._validate_length("q", q) if self._pin_model is None or self._pin_data is None: return [0.0] * self._dof + # Lazy import: pinocchio is a heavy optional dep only needed when + # gravity compensation is enabled, so keep it out of module import. import pinocchio compute_generalized_gravity = _dynamic_attr(pinocchio, "computeGeneralizedGravity") From 1f9078d51f6bf7b042d916446d10f1d67525c9b8 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:30:16 +0300 Subject: [PATCH 06/13] refactor: share tick/cache defaults via named constants tick_deadline_us=1_000 and state_cache_ttl_s=0.002 were unnamed magic numbers duplicated as defaults in both the base and the openarm_rs subclass. Hoist them to _DEFAULT_TICK_DEADLINE_US and _DEFAULT_STATE_CACHE_TTL_S so the two can't drift. --- dimos/hardware/manipulators/damiao/base_adapter.py | 9 +++++++-- dimos/hardware/manipulators/openarm_rs/adapter.py | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index 701c098999..6a58bb7e4f 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -27,6 +27,11 @@ logger = setup_logger() +# Shared adapter defaults. Subclasses reference these so the base and its +# subclasses can't drift apart. +_DEFAULT_TICK_DEADLINE_US = 1_000 +_DEFAULT_STATE_CACHE_TTL_S = 0.002 + _can_motor_control: Any | None _damiao: Any | None @@ -110,8 +115,8 @@ def __init__( address: str | Path | None = "can0", config_path: str | Path | None = None, use_mock_bus: bool = False, - tick_deadline_us: int = 1_000, - state_cache_ttl_s: float = 0.002, + tick_deadline_us: int = _DEFAULT_TICK_DEADLINE_US, + state_cache_ttl_s: float = _DEFAULT_STATE_CACHE_TTL_S, ) -> None: arm_spec.validate() if dof is not None and dof != arm_spec.dof: diff --git a/dimos/hardware/manipulators/openarm_rs/adapter.py b/dimos/hardware/manipulators/openarm_rs/adapter.py index 76225c05af..96e493ba61 100644 --- a/dimos/hardware/manipulators/openarm_rs/adapter.py +++ b/dimos/hardware/manipulators/openarm_rs/adapter.py @@ -18,6 +18,8 @@ from typing import TYPE_CHECKING from dimos.hardware.manipulators.damiao.base_adapter import ( + _DEFAULT_STATE_CACHE_TTL_S, + _DEFAULT_TICK_DEADLINE_US, DamiaoArmAdapterBase, DamiaoBindingUnavailableError, ) @@ -71,8 +73,8 @@ def __init__( kp: list[float] | None = None, kd: list[float] | None = None, gravity_comp: bool = True, - tick_deadline_us: int = 1_000, - state_cache_ttl_s: float = 0.002, + tick_deadline_us: int = _DEFAULT_TICK_DEADLINE_US, + state_cache_ttl_s: float = _DEFAULT_STATE_CACHE_TTL_S, gravity_model_path: str | Path | None = None, gravity_torque_limits: list[float] | None = None, **_: object, From c26874e287167a89dac7ced181b9600e0dc7f640 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:30:58 +0300 Subject: [PATCH 07/13] refactor: single source of truth for default CAN address The 'can0' interface name was hard-coded three times: the base constructor default, the base None-coalesce, and the openarm_rs subclass default. Collapse them onto one _DEFAULT_ADDRESS constant so the literal lives in exactly one place. The None-coalesce stays to handle a config that leaves address unset (the factory then passes address=None). --- dimos/hardware/manipulators/damiao/base_adapter.py | 6 ++++-- dimos/hardware/manipulators/openarm_rs/adapter.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index 6a58bb7e4f..c3730db5be 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -31,6 +31,8 @@ # subclasses can't drift apart. _DEFAULT_TICK_DEADLINE_US = 1_000 _DEFAULT_STATE_CACHE_TTL_S = 0.002 +# Conventional first SocketCAN interface; used when no address is configured. +_DEFAULT_ADDRESS = "can0" _can_motor_control: Any | None _damiao: Any | None @@ -112,7 +114,7 @@ def __init__( gravity_model_path: str | Path | None = None, gravity_torque_limits: list[float] | tuple[float, ...] | None = None, supported_control_modes: tuple[ControlMode, ...] | None = None, - address: str | Path | None = "can0", + address: str | Path | None = _DEFAULT_ADDRESS, config_path: str | Path | None = None, use_mock_bus: bool = False, tick_deadline_us: int = _DEFAULT_TICK_DEADLINE_US, @@ -159,7 +161,7 @@ def __init__( self._last_positions: list[float] | None = None self._pin_model = None self._pin_data: object | None = None - self._address = str(address) if address is not None else "can0" + self._address = str(address) if address is not None else _DEFAULT_ADDRESS self._config_path = str(config_path) if config_path is not None else None self._arm_name = arm_spec.arm_name self._bus_name = arm_spec.bus_name diff --git a/dimos/hardware/manipulators/openarm_rs/adapter.py b/dimos/hardware/manipulators/openarm_rs/adapter.py index 96e493ba61..75c99fb80c 100644 --- a/dimos/hardware/manipulators/openarm_rs/adapter.py +++ b/dimos/hardware/manipulators/openarm_rs/adapter.py @@ -18,6 +18,7 @@ from typing import TYPE_CHECKING from dimos.hardware.manipulators.damiao.base_adapter import ( + _DEFAULT_ADDRESS, _DEFAULT_STATE_CACHE_TTL_S, _DEFAULT_TICK_DEADLINE_US, DamiaoArmAdapterBase, @@ -55,7 +56,7 @@ class OpenArmRSAdapter(DamiaoArmAdapterBase): def __init__( self, - address: str | Path | None = "can0", + address: str | Path | None = _DEFAULT_ADDRESS, dof: int = 7, *, hardware_id: str = "arm", From f98dbc383d893a43f3d05316bf3d0856a202c76c Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:32:30 +0300 Subject: [PATCH 08/13] perf: vectorize damiao state and command paths - refresh_state: build the position/velocity/torque lists with ndarray.tolist() instead of per-element float() loops. - write_mit_commands: assemble the MIT matrix with np.column_stack instead of a Python comprehension over zipped rows; this removes the now-redundant _mit_command_rows helper (validation moves to a direct _validate_command_lengths call). - read_state: look the control-mode index up in a precomputed _CONTROL_MODE_INDEX map instead of rebuilding list(ControlMode) every call. --- .../manipulators/damiao/base_adapter.py | 29 ++++++------------- .../manipulators/damiao/test_base_adapter.py | 2 +- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index c3730db5be..7eef27af45 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -33,6 +33,8 @@ _DEFAULT_STATE_CACHE_TTL_S = 0.002 # Conventional first SocketCAN interface; used when no address is configured. _DEFAULT_ADDRESS = "can0" +# Position of each control mode in declaration order, reported by read_state(). +_CONTROL_MODE_INDEX = {mode: index for index, mode in enumerate(ControlMode)} _can_motor_control: Any | None _damiao: Any | None @@ -186,18 +188,6 @@ def _validate_command_lengths(self, **commands: list[float]) -> None: def _zero_vector(self) -> list[float]: return [0.0] * self._dof - def _mit_command_rows( - self, - *, - q: list[float], - dq: list[float], - kp: list[float], - kd: list[float], - tau: list[float], - ) -> list[tuple[float, float, float, float, float]]: - self._validate_command_lengths(q=q, dq=dq, kp=kp, kd=kd, tau=tau) - return list(zip(q, dq, kp, kd, tau, strict=True)) - def get_info(self) -> ManipulatorInfo: return ManipulatorInfo( vendor=self._arm_spec.vendor, @@ -334,9 +324,9 @@ def refresh_state(self, *, force: bool = False) -> tuple[list[float], list[float self._arm.refresh() self._robot.tick(self._tick_deadline_us) state = ( - [float(value) for value in self._arm.positions().astype(np.float64)], - [float(value) for value in self._arm.velocities().astype(np.float64)], - [float(value) for value in self._arm.torques().astype(np.float64)], + self._arm.positions().astype(np.float64).tolist(), + self._arm.velocities().astype(np.float64).tolist(), + self._arm.torques().astype(np.float64).tolist(), ) if any(len(values) != self._dof for values in state): raise RuntimeError("can_motor_control state length does not match configured DOF") @@ -357,7 +347,7 @@ def read_joint_efforts(self) -> list[float]: def read_state(self) -> dict[str, int]: return { "state": 1 if self._enabled else 0, - "mode": list(ControlMode).index(self._control_mode), + "mode": _CONTROL_MODE_INDEX[self._control_mode], } def read_error(self) -> tuple[int, str]: @@ -431,10 +421,9 @@ def write_mit_commands( ) -> bool: if self._arm is None or self._robot is None or not self._enabled: return False - rows = self._mit_command_rows(q=q, dq=dq, kp=kp, kd=kd, tau=tau) - self._arm.mit_control( - np.array([(row[2], row[3], row[0], row[1], row[4]) for row in rows], dtype=np.float64) - ) + self._validate_command_lengths(q=q, dq=dq, kp=kp, kd=kd, tau=tau) + # MIT command columns are (kp, kd, q, dq, tau) per joint. + self._arm.mit_control(np.column_stack([kp, kd, q, dq, tau]).astype(np.float64)) self._robot.tick(self._tick_deadline_us) self._state_cache = None self._last_positions = list(q) diff --git a/dimos/hardware/manipulators/damiao/test_base_adapter.py b/dimos/hardware/manipulators/damiao/test_base_adapter.py index 82357f94c4..400da6f7c5 100644 --- a/dimos/hardware/manipulators/damiao/test_base_adapter.py +++ b/dimos/hardware/manipulators/damiao/test_base_adapter.py @@ -101,7 +101,7 @@ def test_base_adapter_info_limits_and_modes() -> None: def test_base_adapter_validates_command_lengths() -> None: adapter = DamiaoArmAdapterBase(arm_spec=_arm_spec()) with pytest.raises(ValueError, match="q length 1 does not match dof 2"): - adapter._mit_command_rows( + adapter._validate_command_lengths( q=[0.0], dq=[0.0, 0.0], kp=[0.0, 0.0], From 227e84104149a626688f13b64b9e2ab9b76ef0c9 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:33:47 +0300 Subject: [PATCH 09/13] fix: use structured logging with tracebacks in damiao adapter The connect/disconnect/write_enable/write_clear_errors/write_stop paths stuffed context into f-string log lines and dropped the traceback on real failures. Switch to structlog key/value fields and use logger.exception (or warning with exc_info) so the traceback survives instead of being downgraded to an f-string error message. --- .../manipulators/damiao/base_adapter.py | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index 7eef27af45..d2c2c27afb 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -255,9 +255,12 @@ def connect(self) -> bool: self.refresh_state(force=True) except self._binding_error_type: raise - except Exception as exc: - logger.error( - f"{type(self).__name__} {self._hardware_id}@{self._address} connect failed: {exc}" + except Exception: + logger.exception( + "damiao adapter connect failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + address=self._address, ) self._robot = None self._arm = None @@ -298,9 +301,12 @@ def disconnect(self) -> None: if self._robot is not None: try: self._robot.disable() - except Exception as exc: + except Exception: logger.warning( - f"{type(self).__name__} {self._hardware_id} disable on disconnect failed: {exc}" + "damiao adapter disable on disconnect failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + exc_info=True, ) self._enabled = False self._connected = False @@ -449,8 +455,13 @@ def write_stop(self) -> bool: ) try: self._robot.disable() - except Exception as exc: - logger.warning(f"{type(self).__name__} {self._hardware_id} stop disable failed: {exc}") + except Exception: + logger.warning( + "damiao adapter stop disable failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + exc_info=True, + ) return False self._enabled = False return True @@ -460,14 +471,23 @@ def write_enable(self, enable: bool) -> bool: return False try: self._robot.enable() if enable else self._robot.disable() - except Exception as exc: - logger.error(f"{type(self).__name__} {self._hardware_id} enable={enable} failed: {exc}") + except Exception: + logger.exception( + "damiao adapter enable failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + enable=enable, + ) return False self._enabled = enable if enable: positions = self.read_joint_positions() if not self.write_joint_positions(positions): - logger.error(f"{type(self).__name__} {self._hardware_id} startup hold failed") + logger.error( + "damiao adapter startup hold failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + ) return False return True @@ -477,13 +497,21 @@ def write_clear_errors(self) -> bool: try: self._robot.disable() self._robot.enable() - except Exception as exc: - logger.error(f"{type(self).__name__} {self._hardware_id} clear errors failed: {exc}") + except Exception: + logger.exception( + "damiao adapter clear errors failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + ) return False self._enabled = True positions = self.read_joint_positions() if not self.write_joint_positions(positions): - logger.error(f"{type(self).__name__} {self._hardware_id} clear-error hold failed") + logger.error( + "damiao adapter clear-error hold failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + ) return False return True From a88758f7bb756cc87771c876a1b5ead0e977f492 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:34:10 +0300 Subject: [PATCH 10/13] fix: log dropped gravity feed-forward in write_joint_positions When the pre-command state read fails, the adapter substitutes zero feed-forward torque. Log a warning (with traceback) so the dropped gravity term is visible instead of being silently swallowed. --- dimos/hardware/manipulators/damiao/base_adapter.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index d2c2c27afb..a73d6c1c55 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -379,6 +379,14 @@ def write_joint_positions(self, positions: list[float], velocity: float = 1.0) - try: tau = self.compute_gravity_torques(self.read_joint_positions()) except RuntimeError: + # State read failed: fall back to zero feed-forward torque, but + # surface it so the dropped gravity term isn't silent. + logger.warning( + "damiao adapter dropping gravity feed-forward; state read failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + exc_info=True, + ) tau = self._zero_vector() else: tau = self._zero_vector() From 36940ea77be2ef056a1e41494464d238d01c417c Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:34:46 +0300 Subject: [PATCH 11/13] fix: narrow except in write_gravity_compensation The try wrapped both refresh_state and compute_gravity_torques under a broad except Exception, masking a real gravity-model error as 'invalid state'. Scope the try to refresh_state (the call that raises RuntimeError on a bad read) and let a genuine compute error propagate. Also switch the log line to structured fields with a traceback. --- dimos/hardware/manipulators/damiao/base_adapter.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dimos/hardware/manipulators/damiao/base_adapter.py b/dimos/hardware/manipulators/damiao/base_adapter.py index a73d6c1c55..ecd3ad823c 100644 --- a/dimos/hardware/manipulators/damiao/base_adapter.py +++ b/dimos/hardware/manipulators/damiao/base_adapter.py @@ -421,12 +421,15 @@ def write_joint_torques(self, efforts: list[float]) -> bool: def write_gravity_compensation(self, damping: float | list[float] = 0.0) -> bool: try: q, dq, _ = self.refresh_state(force=True) - tau = self.compute_gravity_torques(q) - except Exception as exc: + except RuntimeError: logger.warning( - f"Skipping {type(self).__name__} gravity compensation due to invalid state: {exc}" + "skipping damiao gravity compensation; state read failed", + adapter=type(self).__name__, + hardware_id=self._hardware_id, + exc_info=True, ) return False + tau = self.compute_gravity_torques(q) kd = [float(damping)] * self._dof if isinstance(damping, int | float) else list(damping) return self.write_mit_commands(q=q, dq=dq, kp=self._zero_vector(), kd=kd, tau=tau) From e22e724e797705571ce63d260cab158034267f06 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:35:30 +0300 Subject: [PATCH 12/13] fix: reject unknown kwargs in OpenArmRSAdapter The **_: object catch-all silently dropped misspelled adapter kwargs. The registry factory only passes declared parameters (dof, address, hardware_id plus the blueprint adapter_kwargs), so drop the catch-all to turn config typos into a clear TypeError. --- dimos/hardware/manipulators/openarm_rs/adapter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dimos/hardware/manipulators/openarm_rs/adapter.py b/dimos/hardware/manipulators/openarm_rs/adapter.py index 75c99fb80c..9a70c9c7dc 100644 --- a/dimos/hardware/manipulators/openarm_rs/adapter.py +++ b/dimos/hardware/manipulators/openarm_rs/adapter.py @@ -78,7 +78,6 @@ def __init__( state_cache_ttl_s: float = _DEFAULT_STATE_CACHE_TTL_S, gravity_model_path: str | Path | None = None, gravity_torque_limits: list[float] | None = None, - **_: object, ) -> None: if dof != len(self._DEFAULT_OPENARM_MOTORS): raise ValueError(f"OpenArmRSAdapter only supports 7 DOF (got {dof})") From 1b12e2d5cea9c7a006934a8c1d1a860cf7eb3327 Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Tue, 9 Jun 2026 01:35:54 +0300 Subject: [PATCH 13/13] docs: point to kp/kd source constants instead of restating them The OpenArm integration guide inlined the kp/kd preset numbers, which drift from the adapter code. Replace them with a pointer to OpenArmRSAdapter._DEFAULT_KP/_DEFAULT_KD (and the openarm adapter's own constants). --- docs/capabilities/manipulation/openarm_integration.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/capabilities/manipulation/openarm_integration.md b/docs/capabilities/manipulation/openarm_integration.md index d75f7ce0a0..8d1f676538 100644 --- a/docs/capabilities/manipulation/openarm_integration.md +++ b/docs/capabilities/manipulation/openarm_integration.md @@ -258,12 +258,7 @@ No other code changes are needed. The `coordinator-openarm-rs` and `openarm-rs-p ### Gain tuning (MIT kp/kd) -Defaults live in the adapter implementations. The binding-backed `openarm_rs` path uses the upstream OpenArm ROS2 hardware presets from `openarm_hardware/openarm_simple_hardware.hpp`; the in-tree `openarm` adapter keeps its existing gains. OpenArm RS/OpenArm ROS2 presets are: - -```python -_DEFAULT_KP = [70.0, 70.0, 70.0, 60.0, 10.0, 10.0, 10.0] -_DEFAULT_KD = [2.75, 2.5, 2.0, 2.0, 0.7, 0.6, 0.5] -``` +Defaults live in the adapter implementations — pointing here instead of restating the numbers so docs can't drift from code. The binding-backed `openarm_rs` path uses the upstream OpenArm ROS2 hardware presets from `openarm_hardware/openarm_simple_hardware.hpp`; see `OpenArmRSAdapter._DEFAULT_KP` / `_DEFAULT_KD` in [openarm_rs/adapter.py](/dimos/hardware/manipulators/openarm_rs/adapter.py). The in-tree `openarm` adapter keeps its own `_DEFAULT_KP` / `_DEFAULT_KD` in [openarm/adapter.py](/dimos/hardware/manipulators/openarm/adapter.py). Guidelines: - `kp ∈ [0, 500]` in MIT mode. Higher kp = stiffer position tracking; too high → oscillation.