Controlling a vehicle motion axis with controller triggers

Datalake Params:

Z-Axis. I did create a Axis z- also, but that seems to have been removed.

S Axis:

Joystick Inputs:

As said above, this seems to be working on the bench, but is untested in the wet stuff. I hope to do full in-water tests tomorrow.

I’d recommend turning off the “Save variable value between boots” option, because otherwise if you turn off Cockpit while a trigger or axis is deflected, the vehicle could start moving next time you open Cockpit if you arm it before connecting the controller.

Also, if you want to control Axis S with a joystick axis then you don’t need to split it into + and - or change the Axis S Output definition, you can just use the original Axis S variable :slight_smile:

I tried that, and it didn’t work

Was that before or after you changed the output definition to stop using that variable?

In-water testing
alas this did not completely go as had hoped.

The following video answers this, showing all data lake data during joystick movement, with Axis S only:

We then have a video showing ‘splitting’ the Axis S, but with only an Axis S+ (no Axis S- as can only choose single parameter for stick movement). This shows the data lake and the thrusters reacting to the stick movement for pitch, however, the pitch is limited to around 10 degrees only in ‘depth hold mode’, and it brings back to centre when the stick is released. I also try manual and aero modes. At the end I show the movement from the trim pitch set point. The actions of the trim pitch is far more like what I am looking for, however still not quite. I am looking to pitch the ROV, in a controllable way (as in the rate of pitch changes along with amount of stick travel) with the stick, and the ROV remain in said pitch when the stick is released.

Your first video has the Axis S Output defined using the inputs/mavlink/axis-s-plus variable ID, which is for a variable that doesn’t exist at that point (it should be the inputs/mavlink/axis-s variable instead (no -plus), since that’s the ID of Axis S).

There is a limit, but it’s dependent on the ATC_ANGLE_MAX parameter[1], and (in the latest Dev firmware, since the pull request I linked to earlier has been merged in) also scaled by the pilot gain.

If you’re on 4.7 stable then your ±100 range for Axis S should instead be ±400 if you want control over the autopilot’s full angle range. If you’re on the latest Dev firmware (which includes the scaling fix) you’ll need to use the normal ±1000 range and 100% gain.

Indeed - stabilise and depth hold control the pitch and roll angles, so if you return to 0 deflection then you’re commanding a level attitude. If you don’t want that then you’ll want a slider/knob control instead of a spring-loaded joystick axis, noting that you can have that as a mini-widget in Cockpit if you don’t have such an input on your controller.

Acro mode should be able to do that, but you’ll need to disable ACRO_TRAINER so it doesn’t self-level, and you then don’t have automatic maintaining of the depth (commanding zero vertical velocity can still allow the depth to drift over time).

Alternatively you could potentially use guided mode, though then I believe you’ll need to set up alternative control inputs via MAVLink commands to set your attitude, depth, and velocity targets, instead of the default joystick pipeline.


  1. In addition to the user-set angle max there is seemingly also a 4500 value in the code which may be limiting the roll/pitch combination to 45 degrees of normalised deflection as well. ↩

Ah, ok - that works :+1:

I have always had this set to its maximum - I see it has now changed from rad to deg - so is on 80deg. ATC_ANGLE_MAX also limits the trim pitch maximum, so with that in mind, the trim pitch will reach around 45deg, and not the mere 10deg of the S-axis.

I am running the UDP Fix, which displays as 4.7 stable. So you suggest I put ±400 in the cockpit joystick min/max?

Alas sliders/knobs will not work for the application. The intended application is for using the ROV effectively as a camera gimbal, so the left sick is pan/tilt for offboard camera. Going back to the idea of the pitch trim, is it possible that it could be routed through a stick, and the adjustment change more/less rapidly as the stick is deflected more/less?

Yep - the axis range firmware fix only got merged a few days ago, so it’s not included in the one you’re running. Try 400 and see if that indeed provides a better range :slight_smile:

In the meantime we’ll try to look into whether that 45 degree limit can be readily increased to 80 degrees instead, as one of our various ongoing steps to enable properly stabilised arbitrary attitudes for ArduSub.

Out of interest, is there an angle range that’s critical for your application? While it would be nice to be able to repeatedly flip and stabilise at any point of release (and we’re working towards that), it may be quicker to achieve a defined angle range if that meaningfully exists.

As mentioned, that is how Acro mode (a to some extent Manual mode) works. If you want attitude rate control in a different mode then it’s possible, but different extents would require different approaches and amounts of effort to implement (i.e. it’s not just enabling a setting).

As a couple of ideas, a custom Cockpit Action could do the integration of the axis input, repeatedly incrementing the output while the joystick is deflected (keeping the autopilot’s current limitations), or the autopilot firmware could be modified (possibly with a new mode, or input handling variant based on a parameter) to treat those attitude inputs as rates instead of angles.

45degrees off horizontal is more than enough TBH - anything more would cause instability issues, as the cameras are a change to CoG. 45 degrees works well with the pitch trim, but the limitation is that this must be fixed prior to the shot, and subject tracking in pitch axis is impossible.

What would be truly grand, would be a mode that is basically depth hold, so stabilised and constant depth held (unless s-axis input modified on controller), then pitch control via stick input, that acts like the elevators of an aeroplane, so more stick deflection gives faster pitch change, but when stuck is released, the pitch remains until more stick inputs change pitch.

@EliotBR the eventual dream is similar to this that Qysea did

Though the BR2 is too small for a full sized Alexa, it easily handles the likes of Komodo, and does it very well, it just missed that one controlled axis movement

As a (currently untested) minimal approach to this:

  1. Create a new “number” data-lake variable called “Pitch Rate”, and with the initial value 0, persisting the variable, but not saving its value between boots
  2. Create a new JavaScript Action called “Integrate Pitch”, with the definition:
    const pitchLimit = 400; // should be 1000 on firmware >= 4.7.3
    const pitchRate = window.cockpit.getDataLakeVariableData('user/custom/pitch-rate');
    const currentPitch = window.cockpit.getDataLakeVariableData('outputs/mavlink/axis-s')
    const newPitch = min(max(currentPitch + pitchRate, -pitchLimit), pitchLimit);
    window.cockpit.setDataLakeVariableData('inputs/mavlink/axis-s', round(newPitch))
    
  3. Click the ‘link’ icon on the Action, and set it to run “repeatedly” every 0.04s
    Screenshot 2026-10-04 at 9.32.14 am
    • Slower but Cockpit sends MANUAL_CONTROL messages at 25Hz, so updating any faster than that just wastes resources
  4. Change your joystick configuration to use “Pitch Rate” instead of “Axis S”, and set the range to whatever maximum rate feels good
    • With 25Hz updates, starting from centred, a rate of 1 would reach the +400 maximum commanded pitch value after 16 seconds
    • A rate of 100 would command maximum pitch after 0.16s, which is way faster than the vehicle can actually achieve
    • The ‘maximum rate’ here also determines your number of ‘speed steps’, so if that’s too limited you can increase the Action repetition duration (e.g. to 0.08 or 0.12), at the cost of some additional latency in your pitch control, and potentially a bit less smoothness in the vehicle movement

Noting that this is by no means an ideal solution, and is less smooth and higher latency than an implementation in the autopilot would achieve, but it may still be sufficient for what you’re after (so seems worth testing).

In terms of caveats, this would run automatically so would always be applied, regardless of which flight mode you’re in, and can’t be easily turned off. That’s of course possible to adjust, at the cost of a more complex implementation.

Utmost thanks @EliotBR :folded_hands: I will set this up today and hopefully test tomorrow.

@EliotBR I have followed everything, and set the joystick range -5 - +5, to give theoretical 3.2 for full range.

On the bench, this does not seem to do anything to the thrusters. Video below:

Ok, I’ve actually tried running it now, and found a couple of issues with the Action I defined, as well as a bug with Cockpit’s automatic time-based triggering, which we can work around by re-implementing that functionality within the Action

  1. Create a new “number” DataLake variable called “Pitch Integrator ID”
  2. Replace the existing “Integrate Pitch” Action definition with:
    // Clear any existing repetition
    clearInterval(cockpit.getDataLakeVariableData('user/custom/pitch-integrator-id'))
    
    const pitchLimit = 400; // should be 1000 on firmware >= 4.7.3
    cockpit.setDataLakeVariableData('user/custom/pitch-integrator-id',
      cockpit.setInterval(
        () => {
          const pitchRate = cockpit.getDataLakeVariableData('user/custom/pitch-rate');
          const currentPitch = cockpit.getDataLakeVariableData('outputs/mavlink/axis-s')
          const newPitch = Math.min(Math.max(currentPitch + pitchRate, -pitchLimit), pitchLimit);
          cockpit.setDataLakeVariableData('inputs/mavlink/axis-s', newPitch);
        }, 40
      )
    )
    
  3. Turn off the time-based triggering, and add a trigger based on the Pitch Rate data-lake variable, at No faster than 0.01 changes/s (this is just to get the automatic loop running when you first push the stick)
    • If you’d prefer to manage this mode manually you can turn off automatic triggering entirely, and start it by pressing the play button on the Action, or creating a button mini-widget that’s tied to the Action (e.g. called “Pitch rate control”)
      • If you do this, you can stop the loop (i.e. return to non-rate-based pitch control) by either editing and saving the Action, or reloading Cockpit
  4. It may be helpful to set up a very generic indicator mini-widget for your active pitch rate, if you want precise control over that
    • If you set up a plotter widget to display the autopilot’s reported pitch angle you should be able to see how quickly and smoothly (or jerkily) the pitch is actually changing, though that may already be quite visible from the video stream + existing attitude HUD/indicator
  5. In a similar vein, if you tend to disarm and re-arm during missions it may be helpful to set up an indicator for the currently commanded pitch angle, just so you know which angle it’s going to try to shift to as soon as you arm (in case you didn’t move pitch back to zero/level before disarming)

Good news is I was wrong about the speed steps part, so you should still have fine-grained speed control regardless of the range you use, but understandably there is still some more latency involved in this approach than if you could directly request the autopilot to maintain a rotation rate. As mentioned though, it’s possible this approach will already be workable for your application :slight_smile:

On the bench this seems to work perfectly - thank you :folded_hands:

I will test in-water later in the week.