Skip to content

Commit 5f9c8a8

Browse files
authored
Merge pull request #348 from pollen-robotics/347-log-dynamixel-hardware-error
Monitor hardware errors.
2 parents 84c40c3 + 6ba6d1c commit 5f9c8a8

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ requires-python = ">=3.10"
1212
dependencies = [
1313
"numpy>=2.2.5,<2.3.0",
1414
"scipy==1.15.3",
15-
"reachy_mini_motor_controller>=1.0.0",
15+
"reachy_mini_motor_controller>=1.2.0",
1616
"eclipse-zenoh>=1.4.0",
1717
"opencv-python<=5.0",
1818
"cv2_enumerate_cameras>=1.2.1",

src/reachy_mini/daemon/backend/robot/backend.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(
3030
log_level: str = "INFO",
3131
check_collision: bool = False,
3232
kinematics_engine: str = "AnalyticalKinematics",
33+
hardware_error_check_frequency: float = 1.0,
3334
):
3435
"""Initialize the RobotBackend.
3536
@@ -38,6 +39,7 @@ def __init__(
3839
log_level (str): The logging level for the backend. Default is "INFO".
3940
check_collision (bool): If True, enable collision checking. Default is False.
4041
kinematics_engine (str): Kinematics engine to use. Defaults to "AnalyticalKinematics".
42+
hardware_error_check_frequency (float): Frequency in seconds to check for hardware errors. Default is 1.0.
4143
4244
Tries to connect to the Reachy Mini motor controller and initializes the control loop.
4345
@@ -82,6 +84,8 @@ def __init__(
8284
self.target_antenna_joint_current = None # Placeholder for antenna joint torque
8385
self.target_head_joint_current = None # Placeholder for head joint torque
8486

87+
self.hardware_error_check_frequency = hardware_error_check_frequency # seconds
88+
8589
def run(self) -> None:
8690
"""Run the control loop for the robot backend.
8791
@@ -96,6 +100,8 @@ def run(self) -> None:
96100
self.retries = 5
97101
self.stats_record_t0 = time.time()
98102

103+
self.last_hardware_error_check_time = time.time()
104+
99105
next_call_event = Event()
100106

101107
# Compute the forward kinematics to get the initial head pose
@@ -240,6 +246,18 @@ def _update(self) -> None:
240246
self._stats["nb_error"] = 0
241247
self.stats_record_t0 = time.time()
242248

249+
if (
250+
time.time() - self.last_hardware_error_check_time
251+
> self.hardware_error_check_frequency
252+
):
253+
hardware_errors = self.read_hardware_errors()
254+
if hardware_errors:
255+
for motor_name, errors in hardware_errors.items():
256+
self.logger.error(
257+
f"Motor '{motor_name}' hardware errors: {errors}"
258+
)
259+
self.last_hardware_error_check_time = time.time()
260+
243261
def close(self) -> None:
244262
"""Close the motor controller connection."""
245263
if self.c is not None:
@@ -478,6 +496,33 @@ def _infer_control_mode(self) -> MotorControlMode:
478496
else:
479497
raise ValueError(f"Unknown motor control mode: {mode}")
480498

499+
def read_hardware_errors(self) -> dict[str, list[str]]:
500+
"""Read hardware errors from the motor controller."""
501+
if self.c is None:
502+
return {}
503+
504+
def decode_hardware_error_byte(err_byte: int) -> list[str]:
505+
# https://emanual.robotis.com/docs/en/dxl/x/xl330-m288/#hardware-error-status
506+
bits_to_error = {
507+
0: "Input Voltage Error",
508+
2: "Overheating Error",
509+
4: "Electrical Shock Error",
510+
5: "Overload Error",
511+
}
512+
err_bits = [i for i in range(8) if (err_byte & (1 << i)) != 0]
513+
return [bits_to_error[b] for b in err_bits if b in bits_to_error]
514+
515+
errors = {}
516+
for name, id in self.c.get_motor_name_id().items():
517+
# https://emanual.robotis.com/docs/en/dxl/x/xl330-m288/#hardware-error-status
518+
err_byte = self.c.async_read_raw_bytes(id, 70, 1)
519+
assert len(err_byte) == 1
520+
err = decode_hardware_error_byte(err_byte[0])
521+
if err:
522+
errors[name] = err
523+
524+
return errors
525+
481526

482527
@dataclass
483528
class RobotBackendStatus:

0 commit comments

Comments
 (0)