Skip to content

Deployment#

The deployment program runs a driver of the Field Scouting Task on the ROS 2 topics of the Polaris Ranger. You name a mission. The program plans the route from the position of the vehicle. In each control step (0.1 s) it builds the observation from the topics and runs the driver. Then it makes two ds_dbw commands: a SteeringCmd in curvature mode and a UlcCmd in velocity mode.

Status

The program, the drivers reference and ppo and the three modes exist. The driver residual with the gain \(\alpha\) and the shield of the Framework is specified, not built (Residual Driver).

Real Vehicle

Only the laboratory runs the program on the vehicle. The safety driver is the safety system.

Modes#

Mode Location Commands Sent Start
SIM The simulated Polaris: the game with acres_sim, or ACRES Core with acres_core_sim Yes, only after the program detects a simulator Learning/Scripts/deploy.sh sim
SHADOW The vehicle with a person who drives; or a recorded bag (offline) None. The node has no publisher 10_policy_run.py (field-day kit); deploy.sh offline <bag>
DRIVE The vehicle with the safety driver in the seat Steering (curvature) and ULC (velocity) 10_policy_run.py --drive

In DRIVE mode the program stops itself in three cases. It cannot drive (no odometry). The operator stops it (Enter, Ctrl-C).

The drive-by-wire disengages. Each other check is a warning. The program shows and logs each warning. The option --stop-on makes a check a stop.

Missions#

Mission Text Legs Complete When
F53 GoTo, Scout, Return The vehicle is stopped at H and the field is scouted
F53,F12 GoTo and Scout for each field, then Return The vehicle is stopped at H and each field is scouted
home Return The vehicle is stopped at H
goto:F53 GoTo The vehicle reaches the entry of the field

Plan. A single field uses its frozen optimal plan. More fields use the exact dynamic programme on the cached cost tables. The route starts at H when the vehicle is within 5 m and 45° of H. From a different pose, the route starts on the lane that the vehicle is on. The vehicle must be within 25 m of a lane.

GoTo mission. goto:F53 is the first leg of the scouting mission of F53: the same entry, direction and access point. The run ends at the entry with the vehicle stopped. The time \(T_\text{ref}\) is the time of the reference speed profile over the leg. The GoTo legs from H are long: 307 m (F57) to 2545 m, with a median of 1430 m.

After the time limit. Past \(1.5 \, T_\text{ref}\) the run continues. The mission can still become complete.

Program Structure#

The code is in Learning/acres_learn/deploy/. It needs only numpy. Thus it runs on the vehicle PC (Python 3.10) without torch. Each mode runs the same step (deploy/loop.py).

File Function
georef.py Converts /vehicle/odom (UTM 16N) to the map frame. It agrees with pyproj to \(10^{-5}\) mm
vehicle.py Converts the messages to the quantities of the policy, with the conventions of ACRES Core
mission.py Makes the plan and the reference path. reverse=False plans no three-point turns
drivers.py The drivers reference and ppo:<file.npz or run> behind one interface
loop.py One step: driver command, speed cap, gear logic, checks, one record
safety.py The checks
report.py Writes records.jsonl, summary.json, report.md, report.png
sim.py, offline.py The SIM front end and the offline SHADOW front end
bundle.py Makes the folder for the vehicle PC (acres_policy/)
Calibration/Polaris/FieldDay/10_policy_run.py The front end on the vehicle: SHADOW and DRIVE

Georeference. The datum chain is NAD83(2011) for an RTK solution, or WGS84 at the epoch for an uncorrected solution. Then follows the NADCON5 shift to NAD83(HARN) and EPSG:2968 (Indiana West) about the site origin. The two transverse Mercator projections use the Krüger series to sixth order (Karney 2011). The heading turns by the difference of the grid convergences: 0.054° at the garage.

Drivers#

Driver State Function
reference Exists The pure-pursuit reference driver. Its cruise speed is the speed cap
ppo:<file.npz or run> Exists The exported flat policy in numpy. The action is the mean
residual:<file.npz or run> with --alpha Specified, not built The reference driver plus the residual, then the shield

A policy file carries its training options. The program uses the same options: launch_hold, path_through and path_lookahead.

Step#

  1. The driver gives a curvature and a signed speed.
  2. The program clips the curvature to ±0.2 m⁻¹.
  3. The program clips the speed to the speed cap forwards. Backwards, the limit is the minimum of the cap and 1.5 m/s.
  4. Where gear shifts are permitted (the simulators), spec.GearManager makes the stop, the GearCmd and the hold.
  5. Where gear shifts are not permitted, a backward request becomes a stop and the finding reverse.
  6. For a policy with the option launch_hold, spec.LaunchHold holds the speed command while the vehicle stands.
  7. A complete mission holds the speed 0.
  8. The checks run. The program writes one record.

The launch hold runs after the gear logic in this program. The Framework specifies a different order for the driver residual: the launch hold runs before the shield.

Vehicle Conventions#

Recorded vehicle data or the simulators confirm each convention.

Input Source Convention
Position, heading /vehicle/odom The rear-axle ground point (base_footprint) in UTM. The yaw of the forward axis, turned to the map grid
Pitch, roll /vehicle/odom orientation Pitch nose up = asin(forward.z). Roll left side up = asin(left.z)
Speed, yaw rate /vehicle/odom twist Body frame, signed
Lateral acceleration Speed times yaw rate /oxts/imu is in the frame of the device, which is 2.7° off the body. Thus the program uses the centripetal value
Steering wheel /vehicle/steering/report Degrees, left is positive
Wheel speeds /vehicle/wheel_speeds if published; else the rigid-body speeds of the wheel centres m/s
Gear /vehicle/gear/report ds_dbw Gear (5 = L)
LiDAR /lidar/points Turned by the Helios mount (roll −1.5°, pitch 5.3°). The program drops returns nearer than 1.6 m. Beams within 1° of the horizontal go into 360 bins
RTK /oxts/fix Status 2 with σ < 5 cm: fixed. Status 2 otherwise: float. Else: none

On the RTK bag of 31 July, the twist speed is 1.003 times the speed from the positions. The slope of the yaw rate against the derivative of the heading is 0.96.

SIM Mode#

  1. Activate the ROS environment: source ROS/Env/setup_env.sh.
  2. Start a simulator: ros2 launch acres_core_sim core_sim.launch.py rate:=4 &. The game with the bridge of acres_sim is the alternative.
  3. Run the program:
Learning/Scripts/deploy.sh sim --mission F53 --driver reference --reset-home --out ~/runs/f53_ref
Learning/Scripts/deploy.sh sim --mission F53 --driver ppo:Acres/Saved/Training/scout1/policy.npz \
    --reset-home --out ~/runs/f53_ppo
Learning/Scripts/deploy.sh sim --mission goto:F53 --driver reference --reset-home --out ~/runs/goto_f53

Guard. The program makes no publisher until it sees a simulator. A simulator publishes /sim/episode with the source "unreal" or "core", and a fresh /clock. Each publish checks again that /clock is fresh. Without a simulator the program prints REFUSED and exits with code 2.

Role of the safety driver. In SIM mode the program does the work of the safety driver. --reset-home puts the vehicle at H (/sim/reset). The program puts the lever in L and enables the drive-by-wire.

Time. The program runs on simulation time: 10 Hz of /clock. A thread sends the commands again at 50 Hz. Thus the program follows the rate of a Core that runs faster than real time. The simulator must run free. A lockstep simulator that waits does not work.

Option Default Function
--mission F53 The mission text
--driver reference The driver
--out Required The output folder
--speed-cap 4.0 m/s The speed cap (the cruise speed of the task)
--forward-only Off No gear shifts, as in DRIVE mode
--reset-home Off Puts the vehicle at H first
--stop-on None Checks that stop the run
--max-run-s \(3 \, T_\text{ref}\) The run limit in simulation seconds
--wait-s 15 s The time to look for a simulator
--datum nad83_2011 The datum of the odometry
--odom-max-age-s 1.0 s The maximum age of the odometry in simulation seconds

SHADOW Mode#

On the vehicle PC, in the folder of the kit (~/fieldday_kit):

  1. Run the check: python3 10_policy_run.py --check --mission F53. Each line must show GO.
  2. Start the run: python3 10_policy_run.py --mission F53 for the bundled policy.
  3. Or start with the reference driver: python3 10_policy_run.py --mission F53 --driver reference.
  4. Drive the vehicle by hand, or with the path follower of the laboratory.
  5. Type q and Enter (or Ctrl-C) to end the run.

The node subscribes and has no publisher (tested: only /rosout and /parameter_events). The status line shows these items each 0.5 s:

  • The phase and the progress.
  • The speed against the command.
  • The commanded curvature as a steering-wheel angle, against the measured angle.
  • The cross-track error, the RTK state and the warnings.

The report goes to ~/fieldday/<date>/policy_runs/<time>_<mission>_shadow/.

Offline SHADOW on a Recorded Bag#

Learning/Scripts/deploy.sh offline <bag folder or .mcap> --mission home --driver reference --out <dir>

The messages of the bag go through the same code in their recorded order. The step is 0.1 s of bag time. The program copies a sqlite3 bag into the output folder before it opens the bag. It reads an MCAP file in place. The mission starts at the first pose of the bag.

Option Default Function
--mission home The mission text
--driver reference The driver
--speed-cap 2.0 m/s The speed cap
--stop-on None Checks that count as stops
--datum auto auto, nad83_2011 or wgs84
--scan-level body body, or ins to level the scan with gravity

DRIVE Mode#

python3 10_policy_run.py --mission F53 --drive                          # the bundled policy, cap 2.0 m/s
python3 10_policy_run.py --mission F53 --drive --driver reference       # the reference driver
python3 10_policy_run.py --mission F53 --drive --speed-cap 1.5 --stop-on off_path,obstacle

--drive is the one arming flag. There is no typed confirmation and no countdown. The program plans. Then it waits until the driver enables the drive-by-wire. It drives from the moment that /vehicle/dbw_enabled is true.

Event Reaction of the Program
/vehicle/dbw_enabled false, or no report for 0.5 s One release (enable = false), then no more commands. The driver has the vehicle
/vehicle/odom older than 0.25 s One release, then no more commands. The 0.1 s timeout of the drive-by-wire stops the vehicle
Enter or Ctrl-C The safe stop: ULC 0 m/s until the vehicle is stopped (0.5 s minimum, 8 s maximum), then the release. A second Ctrl-C stops the commands immediately
The mission is complete The safe stop
A check named in --stop-on The safe stop
Each other finding A warning on the status line and in the log

Commands. SteeringCmd with cmd_type 3 (curvature, \(\lvert \kappa \rvert \le 0.2\) m⁻¹, the rate limits of the laboratory follower). UlcCmd with cmd_type 1 (velocity, 0 to the speed cap). The program sends them at 50 Hz.

It never sends a GearCmd, /vehicle/enable, throttle or brake. The mission plan has no three-point turns. The program holds a backward request of the policy at 0 and reports it. The speed cap is 2.0 m/s by default and 4.0 m/s at most.

10_policy_run.py Option Default Function
--mission F53 The mission text
--driver ppo ppo (the bundled policy.npz or --policy) or reference
--policy acres_policy/policy.npz A different policy file
--drive Off Arms DRIVE mode
--speed-cap 2.0 m/s The speed cap, 4.0 m/s at most
--stop-on None Checks that stop the run
--off-path-m 3.0 m The distance of the off_path warning
--max-run-s 0 (none) The run time of the time warning
--datum nad83_2011 The datum of the odometry
--no-lidar Off No subscription to /lidar/points
--check Off The dependency check, then exit

Checks#

--stop-on accepts each of these names, separated by commas.

Check Condition
rtk The solution is not RTK fixed
off_path The rear axle is more than --off-path-m from the reference path of the phase
crop A part of the plan box is on crop outside the edge band
off_map A wheel is off the surveyed tile
obstacle LiDAR returns in the driving corridor within the stop distance
lidar_stale /lidar/points is older than 0.5 s
safety_stop /safety_stop of the laboratory is true
publisher A different node publishes drive-by-wire commands
overspeed The measured speed is 1 m/s above the cap
reverse The driver asked for reverse where gear shifts are not permitted
time The run is longer than --max-run-s

The obstacle check uses returns 0.35 to 2.3 m above the ground plane of the wheels. It needs at least 8 returns. The corridor is 1.6 m wide plus 0.3 m on each side. The stop distance is \(1.5 \text{ m} + v \cdot 0.5 \text{ s} + v^2 / (2 \cdot 0.8 \text{ m/s}^2)\). Stale odometry always stops the commands.

Checklist before DRIVE#

  1. Run python3 10_policy_run.py --check --mission F53. Each line must show GO.
  2. Make sure that the RTK solution is fixed (01_wait_rtk.py).
  3. Put the vehicle at H with its front to the north. A lane with the front along it is the alternative.
  4. Do a SHADOW run of the same mission. Drive the first few hundred metres by hand.
  5. Make sure that the commanded curvature follows the steering wheel.
  6. Make sure that the cross-track error stays below 1 m and that no off_path or crop warning shows.
  7. Stop the follower and NavRoute. The publisher warning shows if they run.
  8. Make sure that the safety driver is in the seat with the belt on.
  9. Make sure that the hands of the driver are at the wheel and a foot is above the brake.
  10. Put the gear in L. Make sure that the route is clear.
  11. Start python3 10_policy_run.py --mission F53 --drive.
  12. Enable the drive-by-wire when the program tells you.
  13. To stop: brake or steer, or press Enter on the laptop.

Report#

Each run writes these files.

File Content
records.jsonl Each step: inputs, commands, phase, cross-track error, findings
summary.json, report.md The summary
report.png The map with the planned path and the driven track; speed, steering and cross-track error against time
result.json, events.log Vehicle runs only
commands.csv DRIVE only: each command sent
inputs/ Vehicle runs only: a rosbag2 of the inputs (each tenth cloud) and of the records
Line of the Report Meaning
Mission Progress along the reference path (m and share), the last phase, success, distance driven
Cross-track \(\lvert e_y \rvert\) from the reference path of the phase: 50th percentile, 95th percentile, maximum
Command against vehicle Commanded against driven curvature; commanded against reported steering-wheel angle; commanded against measured speed
Check The share of steps on which each check was true, and the first time
Stop The place of the stop, and the place of a stop if each check were a stop
Georeference The ground class under the track, and its distance from the lane centre lines

Results#

These results are of 1 October 2026. The policy is the best checkpoint of a short test run (14 minutes of training). Its numbers show only that the chain works.

DRIVE stops against the fake vehicle (test_policy_run.py): 17 of 17 cases pass. The cases are:

  • The time stop, a driver override, the disable button and a drive-by-wire fault.
  • Lost reports and stale odometry.
  • The operator key, Ctrl-C, and Ctrl-C two times.
  • --stop-on with rtk, obstacle and publisher.
  • Warnings that do not stop the run, the speed cap, and a quit before the drive-by-wire is enabled.

In each case the program sent only curvature SteeringCmd and velocity UlcCmd inside their bounds. The release came last. No node published a gear, enable, throttle or brake command. The 1.0 m/s cap holds.

SIM mode on ACRES Core (policy_core_run.sh, Core at four times real time, cap 4 m/s):

Mission Driver Result Time Cross-Track p50 / p95 Commanded against Driven Curvature, p50
F53 (3193 m, with a three-point turn) Reference Complete 892 s (frozen \(T_\text{ref}\) 890 s) 0.26 / 1.04 m 0.007 m⁻¹
F33 (2819 m) Reference Complete 778 s (frozen \(T_\text{ref}\) 770 s) 0.25 / 1.05 m 0.007 m⁻¹
F53 PPO (test run) Not complete: field scouted, then stopped 4.1 m from H 1500 s (run limit) 1.54 / 4.33 m 0.031 m⁻¹
F33 PPO (test run) Not complete: field scouted, on the Return at 95 % of the path 1500 s (run limit) 3.85 / 20.6 m 0.022 m⁻¹

DRIVE mode on ACRES Core (policy_core_run.sh drive:reference:F53:2.0): the forward-only F53 mission (3220 m) is complete in 1541 s of simulation. The cross-track error is 0.14 / 0.60 m (p50 / p95). The commanded speed is never above 2.0 m/s. The program ended with the safe stop and the release at H.

SIM mode in the game (policy_game_run.sh <dir> F53 reference 420): in 420 s the reference driver drove 1113 m of the GoTo of F53. The cross-track error is 0.25 / 0.91 m. The LiDAR of the game sees trees and crop along the lanes. The obstacle warning was true on 29 % of the steps.

Offline SHADOW on recorded bags (deploy.sh offline, mission home):

Bag Result
human_20260813_172445 (parked, 4.5 s) The pose is on the lane west of the bins: 100 % lane, 1.0 m from the centre line
grass_diag_20260731_174757 (follower, 41.5 s, RTK fixed) The track is on lane (57 %) and edge band (42 %), 1.85 m (p50) from the lane centre line. The route of the follower is 3 m east of the centre line of the transit graph

Georeference (deploy/georef_check.py): the 37 closed-loop runs of the follower have 18,404 logged poses. The numpy chain puts 95.0 % on lane, 2.6 % on verge, 0.2 % on edge band and 2.1 % on crop. On the bag of 31 July, the heading and the course over the ground agree to 0.9° (median).

Bundle check. 10_policy_run.py --check on the bundle under Python 3.10 and numpy 1.26 passes each line but ROS. One step of the flat policy takes 4.5 ms.

Bundle for the Vehicle PC#

Learning/Scripts/ppo.sh export scout1 --checkpoint best --out Acres/Saved/Training/scout1/policy.npz
Learning/Scripts/deploy.sh bundle --policy Acres/Saved/Training/scout1/policy.npz --out ~/kit_out

The bundle is ~/kit_out/acres_policy/ (17 MB) and acres_policy.tar.gz. It holds:

  • The sources and configurations of acres_learn.
  • The map products and the other files of Acres/Content/Simulation that the program reads.
  • The cached cost tables.
  • policy.npz.
  • MANIFEST.json with the commit and the SHA-256 of each file.

The bundle goes into ~/fieldday_kit/acres_policy/ with the top-level files of the kit. No installation is necessary.

Demonstration Procedure#

The procedure has a simulator part and a vehicle part. Only the laboratory does the vehicle part. The program never connects to a different computer.

  1. Simulator. Make sure that no game and no Core run. Then run one of these commands.

    source ROS/Env/setup_env.sh
    Calibration/Polaris/FieldDay/Test/policy_game_run.sh ~/demo/game F53 reference 420     # the game, 7 minutes
    CORE_RATE=4 Calibration/Polaris/FieldDay/Test/policy_core_run.sh ~/demo/core sim:reference:F53
    

    The Polaris leaves the ICSC garage, drives the lanes to F53, scouts its loop and comes back. report.png shows the planned path, the driven track, the speed and the steering.

  2. Offline SHADOW. Run the program on a recorded drive.

    Learning/Scripts/deploy.sh offline <bag> --mission home --driver reference --out ~/demo/shadow
    
  3. SHADOW on the vehicle. Make sure that the RTK solution is fixed and that the vehicle is at H. Run python3 10_policy_run.py --check --mission F53. Then run python3 10_policy_run.py --mission F53 --driver reference. The driver drives out of the garage along the planned route by hand. Type q and Enter. Open report.png.

  4. DRIVE on the vehicle. Do this step only if step 3 was correct and the laboratory agrees. Use the reference driver at a low cap for the first few hundred metres.

    python3 10_policy_run.py --mission F53 --drive --driver reference --speed-cap 1.5
    

    The driver enables the drive-by-wire when the screen tells him or her. The driver takes control at any time by a brake or steering input, or with Enter on the laptop.

Residual Driver#

State: specified, not built.

Property Rule
Selection --driver residual:<file.npz or run> and --alpha <value>
Base command The reference driver on the mission path, with the cruise speed at the speed cap
Residual The exported actor in numpy, with the residual law
Order in a control step Reference driver, residual law, launch hold, shield, gear logic, actuator clip (Order in a Control Step)
Shield The same function as in training (Shield). The program calls shield_curvature, measures the obstacle distance along that arc, then calls shield
\(\alpha = 0\) The run is equal to a run of the driver reference. The exceptions are a shield action for an obstacle and a stop for an input that is not usable
Records Each record has the base command, the residual, the flag intervened, the rule codes violated and \(\alpha\)
SHADOW The log shows the residual command beside the base command. No command goes to the vehicle
DRIVE The status line shows \(\alpha\) and the state of the shield

The obstacle rule of the shield reads the planar scan. On the vehicle, the scan sees tall crop as a wall. Thus the rule needs an input that does not report crop before it can stop the vehicle beside crop.

The stop of the shield assumes a latency of at most 0.5 s and a deceleration of at least 0.8 m/s². No proof and no measurement gives these values for the vehicle (Verification). The safety driver stays the safety system.

Tests#

Command Content
python Learning/tests/test_deploy.py (in run_all.py) Georeference; vehicle conventions; missions from H and from a pose; the GoTo mission; speed cap; gear logic; checks; the SIM guard; the PPO driver
python3 Calibration/Polaris/FieldDay/Test/test_policy_run.py (ROS 2, DDS fence) Each DRIVE stop against the fake vehicle; warnings; no publisher in SHADOW; refusal in SIM without a simulator
Calibration/Polaris/FieldDay/Test/policy_core_run.sh <dir> [mode:driver:mission[:cap]] SIM and DRIVE end to end on ACRES Core

Limitations#

  • The LiDAR of the training has no crop. The scene of ACRES Core has terrain, buildings, bins and trees, but no crop. The real Helios sees corn at the edge of a lane as a wall some metres to the side.
  • The steering-wheel comparison uses the fitted steering of polaris.json (ratio 12.0, centre 12.5°). On the simulators the comparison reads approximately 13° off.
  • The datum. The program reads /vehicle/odom as NAD83(2011). Without corrections the OxTS solution drifts towards WGS84, which is 1.3 m away on the tile. The rtk warning shows this.
  • Missions start at H or on a lane. From a different place the planner refuses.
  • The obstacle check is simple. It uses a straight corridor on a flat ground plane. On a curve or a slope it can miss an obstacle or report a false one.
  • Few real recordings fit. The local bags with a converged INS are short. The first SHADOW run on the vehicle is the real test of the conventions.