software architecture for movement based hardware systems - Uvic-Robotics-Club/uvic-rover-2027 GitHub Wiki

This guide covers the architectural patterns that matter most when your code is going to move something in the real world. Getting these right early prevents a class of bugs that are difficult to debug and, in worst case, dangerous.


Why Movement Systems are Different

Software that moves physical hardware has a property that most software does not: failure has real-world consequences. A bug in a motor controller can damage hardware, break a mechanism, or injure someone nearby.

This changes how you should think about the architecture:

  • Safety must be a first-class concern, not an afterthought.
  • The system must degrade gracefully. If a node crashes, the robot should stop.
  • Simulation is crucial for testing.

Hardware Abstraction Layer (HAL)

What is it?

A Hardware Abstraction Layer is an interface that sits between the robot's logic (motion planning, inverse kinematics, high-level commands) and the physical hardware (motor drivers, CAN buses, serial ports, etc).

Why is it important?

  • Testability: You can swap between different backends (simulation and hardware) easily which makes testing the robot's logic simple.
  • Hardware Independence: If you ever change motor drivers, serial communication, or upgrade hardware, only the backend changes.
  • Contract Creation: Designing the HAL interface forces you to be explicit about what operations your hardware needs to support.
  • Abstract Interface: A HAL interface is abstract with defined functions. Every backend must implement this interface completely.

Rules for a well-designed HAL

  1. The interface must be minimal AND include only what is genuinely needed.
  2. No leaking abstractions, meaning words like "CAN", "serial", "PWM" stay in their respective backend file.
  3. estop() must always be callable even BEFORE initialize(), even if called multiple times, even from a signal handler.
  4. The sim backend should be strict to catch bad inputs before testing on real hardware.
  5. Backends own their lifecycle such as initialize() and shutdown().

Watchdogs and Heartbeats

The problem

Imagine the arm controller node crashes mid movement. Without any safety mechanism, the last command sent to the motors remain active. The arm keeps moving until it hits a mechanical stop, resulting in risky situations.

A watchdog is a mechanism that detects when a system has gone silent and takes a safe action in response.

How a watchdog works

A watchdog timer is reset periodically by the component it monitors. If the timer expires without being reset due to the component crashing, hanging, or lost communication, then the watchdog fires and triggers a safe action (usually estop).

For more information, refer to Watchdog.

Heartbeat Topics

For distributed systems with multiple nodes, a dedicated heartbeat topic is common. A "health publisher" node publishes to /system/heartbeat at a fixed rate; any node that depends on it monitors that topic and estops if the heartbeat goes silent.

Watchdog Best Practices

  • Set your timeout conservatively (3-5x your expected message period).
  • Estop must be idempotent.
  • Log everything.
  • Test watchdog explicitly.
  • Disarm on clean shutdown (to not trigger false alarms).

State Machines for Safe Motion

Why states matter

Motors and actuators are not stateless. You cannot send a velocity command to an arm that has not been initialized, calibrated and enabled. Trying to do so either silently fails or causes unexpected behaviour.

A state machine makes the allowed transitions explicit and enforces them in code.

The key benefit: if your watchdog fires, it calls a method (e.g., fault()) which would likely call estop() and then transitions the state to FAULT. And subsequent command attempts will raise an error immediately as the state is set to FAULT.

Command vs. Feedback Separation

Keep your topics clean

Use separate ROS2 topics for commands (what you want the hardware to do) and feedback (what the hardware is actually doing). Never conflate these.

e.g.

Direction Topic Pattern Message Content
Command /arm/joint_command Target angles, velocities, torques
Feedback /arm/joint_state Actual angles, velocities, current
Status /arm/status State machine state, fault codes

This separation makes it easy to:

  • Log: Record the feedback topic to understand what actually happened during failure.
  • Monitor: A safety monitor node can subscribe to feedback and command topics simultaneously and raise an alarm if the two diverge beyond a threshold.
  • Decouple senders: High-level planners publish commands.

Feedback loops are hardware-side. Meaning that ideally, low-level position/velocity/torque control loop should run as close to the hardware as possible.

⚠️ **GitHub.com Fallback** ⚠️