Skip to content

Comments

Reboot vehicle only for ROVs if heartbeat lost #3790

Open
patrickelectric wants to merge 4 commits intobluerobotics:masterfrom
patrickelectric:reboot-only-rov
Open

Reboot vehicle only for ROVs if heartbeat lost #3790
patrickelectric wants to merge 4 commits intobluerobotics:masterfrom
patrickelectric:reboot-only-rov

Conversation

@patrickelectric
Copy link
Member

@patrickelectric patrickelectric commented Feb 11, 2026

Summary by Sourcery

Limit automatic Ardupilot restarts on heartbeat loss to ROV (ArduSub) vehicles and introduce a typed MAVLink firmware classification.

New Features:

  • Add a MavlinkFirmwareType enum to represent ArduPilot firmware families and wire it through vehicle type helpers.

Bug Fixes:

  • Prevent non-ROV vehicles from being automatically rebooted on heartbeat loss by checking the firmware vehicle type before restarting Ardupilot.

Enhancements:

  • Update vehicle type logic and helpers to use the new MavlinkFirmwareType enum instead of raw strings for firmware classification.

…reType

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
…wareType enum

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
…ly for ROV

We don't want to reboot flying vehicles

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
@sourcery-ai
Copy link

sourcery-ai bot commented Feb 11, 2026

Reviewer's Guide

Restricts automatic Ardupilot restarts on heartbeat loss to ROV/ArduSub vehicles by introducing a typed MAVLink firmware enum and using it to gate the heartbeat-based reboot logic, while updating VehicleManager and related helpers to use the new type-safe firmware type API.

Sequence diagram for conditional ArduSub-only heartbeat restart

sequenceDiagram
    participant AutopilotManager
    participant VehicleManager
    participant Mavlink2Rest

    AutopilotManager->>AutopilotManager: auto_restart_ardupilot loop
    AutopilotManager->>VehicleManager: is_heart_beating() async
    VehicleManager-->>AutopilotManager: alive or failure

    alt heartbeat failure
        AutopilotManager->>AutopilotManager: increment _heartbeat_fail_count
    else heartbeat ok
        AutopilotManager->>AutopilotManager: reset _heartbeat_fail_count
    end

    AutopilotManager->>AutopilotManager: check _heartbeat_fail_count >= _max_heartbeat_failures
    alt threshold reached
        AutopilotManager->>AutopilotManager: check _firmware_vehicle_type is None
        alt firmware type unknown
            AutopilotManager->>VehicleManager: get_vehicle_type() async
            VehicleManager->>Mavlink2Rest: get_updated_mavlink_message(HEARTBEAT) async
            Mavlink2Rest-->>VehicleManager: heartbeat_message
            VehicleManager-->>AutopilotManager: MavlinkVehicleType
            AutopilotManager->>AutopilotManager: _firmware_vehicle_type = vehicle_type.mavlink_firmware_type()
        end
        AutopilotManager->>AutopilotManager: check _firmware_vehicle_type == MavlinkFirmwareType.ArduSub
        alt firmware is ArduSub
            AutopilotManager->>AutopilotManager: restart_ardupilot() async
        else firmware is not ArduSub
            AutopilotManager->>AutopilotManager: skip restart
        end
        AutopilotManager->>AutopilotManager: _heartbeat_fail_count = 0
    end
Loading

Class diagram for MAVLink firmware typing and conditional heartbeat restart

classDiagram
    class AutopilotManager {
        - VehicleManager vehicle_manager
        - int _heartbeat_fail_count
        - int _max_heartbeat_failures
        - MavlinkFirmwareType _firmware_vehicle_type
        - bool should_be_running
        + setup() async
        + auto_restart_ardupilot() async
        + restart_ardupilot() async
        + is_running() bool
    }

    class VehicleManager {
        - Mavlink2Rest mavlink2rest
        + get_vehicle_type() async MavlinkVehicleType
        + get_firmware_vehicle_type() async MavlinkFirmwareType
    }

    class MavlinkVehicleType {
        <<enumeration>>
        MAV_TYPE_GENERIC
        MAV_TYPE_FIXED_WING
        MAV_TYPE_VTOL_DUOROTOR
        MAV_TYPE_VTOL_QUADROTOR
        MAV_TYPE_VTOL_TILTROTOR
        MAV_TYPE_VTOL_RESERVED2
        MAV_TYPE_VTOL_RESERVED3
        MAV_TYPE_VTOL_RESERVED4
        MAV_TYPE_VTOL_RESERVED5
        MAV_TYPE_QUADROTOR
        MAV_TYPE_COAXIAL
        MAV_TYPE_HELICOPTER
        MAV_TYPE_ANTENNA_TRACKER
        MAV_TYPE_GCS
        MAV_TYPE_AIRSHIP
        MAV_TYPE_FREE_BALLOON
        MAV_TYPE_ROCKET
        MAV_TYPE_GROUND_ROVER
        MAV_TYPE_SURFACE_BOAT
        MAV_TYPE_SUBMARINE
        MAV_TYPE_HEXAROTOR
        MAV_TYPE_OCTOROTOR
        MAV_TYPE_TRICOPTER
        MAV_TYPE_FLAPPING_WING
        MAV_TYPE_KITE
        MAV_TYPE_ONBOARD_CONTROLLER
        MAV_TYPE_VTOL_RESERVED6
        MAV_TYPE_VTOL_RESERVED7
        MAV_TYPE_VTOL_RESERVED8
        MAV_TYPE_VTOL_RESERVED9
        MAV_TYPE_GIMBAL
        MAV_TYPE_ADSB
        MAV_TYPE_PARAFOIL
        MAV_TYPE_DODECAROTOR
        MAV_TYPE_CAMERA
        MAV_TYPE_CHARGING_STATION
        MAV_TYPE_FLARM
        MAV_TYPE_SERVO
        MAV_TYPE_ODID
        MAV_TYPE_DECAROTOR
        MAV_TYPE_BATTERY
        MAV_TYPE_PARACHUTE
        MAV_TYPE_LOG
        MAV_TYPE_OSD
        MAV_TYPE_IMU
        MAV_TYPE_GPS
        MAV_TYPE_WINCH
        + mavlink_firmware_type() MavlinkFirmwareType
        + is_actually_a_vehicle() bool
    }

    class MavlinkFirmwareType {
        <<enumeration>>
        ArduPlane
        ArduCopter
        ArduRover
        ArduSub
        Unknown
    }

    class Mavlink2Rest {
        + get_updated_mavlink_message(message_name) async dict
    }

    AutopilotManager --> VehicleManager : uses
    VehicleManager --> Mavlink2Rest : uses
    VehicleManager --> MavlinkVehicleType : returns
    VehicleManager --> MavlinkFirmwareType : returns
    MavlinkVehicleType --> MavlinkFirmwareType : maps
    AutopilotManager --> MavlinkFirmwareType : caches
Loading

File-Level Changes

Change Details Files
Introduce a dedicated MavlinkFirmwareType enum and update vehicle-type helpers to be type-safe and more semantically clear.
  • Add MavlinkFirmwareType Enum with explicit ArduPlane/ArduCopter/ArduRover/ArduSub/Unknown members.
  • Change MavlinkVehicleType.mavlink_firmware_type return type from str to MavlinkFirmwareType and return enum values instead of raw strings.
  • Update MavlinkVehicleType.is_actually_a_vehicle to rely on MavlinkFirmwareType.Unknown instead of a hard-coded list of string firmware names.
core/libs/commonwealth/src/commonwealth/mavlink_comm/typedefs.py
Gate heartbeat-based Ardupilot auto-restart to only occur for ArduSub (ROV) firmware, caching the firmware type once retrieved.
  • Add _firmware_vehicle_type Optional[MavlinkFirmwareType] field to AutopilotManager to cache the firmware type.
  • Clarify heartbeat monitoring comment to indicate restart is only for ROVs (Sub).
  • On reaching the heartbeat failure threshold, lazily fetch the vehicle type via VehicleManager.get_vehicle_type, derive its firmware type, cache it, and conditionally call restart_ardupilot only if firmware type is ArduSub, with exceptions ignored when fetching type and errors logged on restart failure.
  • Reset heartbeat failure counter after handling the restart decision.
core/services/ardupilot_manager/autopilot_manager.py
Propagate the new MavlinkFirmwareType through VehicleManager’s firmware type accessor.
  • Change VehicleManager.get_firmware_vehicle_type to return MavlinkFirmwareType instead of str.
  • Delegate to MavlinkVehicleType.mavlink_firmware_type to obtain the enum value.
core/libs/commonwealth/src/commonwealth/mavlink_comm/VehicleManager.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The cached _firmware_vehicle_type is only populated lazily on heartbeat failure and never reset on (re)start, which could make the restart logic stale if the vehicle type changes; consider refreshing or clearing this cache whenever Ardupilot is (re)started.
  • In auto_restart_ardupilot, the broad except Exception: pass around get_vehicle_type() failures fully swallows errors and hides why a restart didn’t occur; consider at least debug-level logging or narrowing the exception to avoid silently ignoring unexpected issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The cached `_firmware_vehicle_type` is only populated lazily on heartbeat failure and never reset on (re)start, which could make the restart logic stale if the vehicle type changes; consider refreshing or clearing this cache whenever Ardupilot is (re)started.
- In `auto_restart_ardupilot`, the broad `except Exception: pass` around `get_vehicle_type()` failures fully swallows errors and hides why a restart didn’t occur; consider at least debug-level logging or narrowing the exception to avoid silently ignoring unexpected issues.

## Individual Comments

### Comment 1
<location> `core/services/ardupilot_manager/autopilot_manager.py:228-231` </location>
<code_context>
                     logger.warning("Consecutive heartbeat failures threshold reached — restarting Ardupilot.")
                     try:
-                        await self.restart_ardupilot()
+                        if self._firmware_vehicle_type is None:
+                            try:
+                                vehicle_type = await self.vehicle_manager.get_vehicle_type()
+                                self._firmware_vehicle_type = vehicle_type.mavlink_firmware_type()
+                            except Exception:
+                                pass
</code_context>

<issue_to_address>
**suggestion:** Reuse VehicleManager.get_firmware_vehicle_type instead of duplicating logic.

Calling `vehicle_manager.get_firmware_vehicle_type()` here would centralize firmware-type resolution in one place and avoid this mapping logic drifting from the shared implementation.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 228 to 231
if self._firmware_vehicle_type is None:
try:
vehicle_type = await self.vehicle_manager.get_vehicle_type()
self._firmware_vehicle_type = vehicle_type.mavlink_firmware_type()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Reuse VehicleManager.get_firmware_vehicle_type instead of duplicating logic.

Calling vehicle_manager.get_firmware_vehicle_type() here would centralize firmware-type resolution in one place and avoid this mapping logic drifting from the shared implementation.

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant