Repository navigation
A browser can fly the simulator, and it is a ground station - #67
Merged
Merged
Conversation
SIM-45 through SIM-49. Three site-survey failures in a row came down to where
the aircraft was put, and each was diagnosed by teleporting a disarmed vehicle
and photographing it through a throwaway script. This replaces that with a page.
Take off, land, translate, yaw. Chase, onboard and depth cameras, one large and
two beside it. GPS, local position, speed, attitude, IMU, battery -- read from
the same /fmu/out topics a real Pixhawk publishes.
The transport is rosbridge, wrapped rather than rewritten, and the work was
making a deliberately general bridge obey hard stop 1:
* topics_pub_glob allows exactly /mission/command. Enforced in publish.py
before the registration is created, so /fmu/in/vehicle_command is
unreachable. Measured: 0 bytes on that topic from a browser that tried.
* services and actions are closed outright, so no node's set_parameters is
reachable.
* a proof-of-sim interlock lives in the node that arms, not in the transport,
because ros2 topic pub walks straight past a transport.
* loopback by default; WEB_BIND names addresses deliberately.
The ground station is its own container. On real hardware the ROS 2 graph runs
on the companion computer and a web page that flies the aircraft runs on someone's
laptop, and sim-qgc has modelled that boundary since the Gazebo era. sim-webui
carries no AirSim client and none of our nodes. The chase camera pointedly did
not follow it: a real aircraft has no chase camera, so it stays with the simulator.
Movement is bounded by a leash in the node, never in the page: a per-step cap, a
radius around the take-off point, and an altitude band, each clamped and logged
rather than silently refused.
The depth camera is 32FC1 metres, which web_video_server cannot render, so it is
colourised beside the source and published as a JPEG -- with a fixed range, so a
colour means the same distance in every frame, and with no-return given its own
colour rather than the far end of the ramp.
Also here: the SIM-27 witness now brackets a flight instead of watching it, with
no vendor patch. Its worklog is rewritten, at the instruction in the handoff,
because the original described the fix that was thrown away.
Eleven review findings fixed, two of which were in the safety machinery itself: a
blocking RPC on the thread that keeps PX4 in offboard, and an altitude fence that
commanded a 40 m descent on a yaw keypress. Both flown at 80 m afterwards, which
is the configuration that made them reachable -- the sortie flown when the feature
was built was at 12 m and could never have exposed either.
332 off-target tests. Gate unchanged at 2/3; seed 2's parked-car landing fails
identically on the code from before this branch.
`tests/test_depth_view.py` imports numpy -- the depth colouriser's own dependency, where the fixed-range and no-return decisions live -- and the CI workflow installed only pytest and pyyaml. The full suite passed locally, because this machine has numpy, and the required check went red on the pull request with a collection error. That is the same shape as the rule already learned about vendor/: a test that needs something the runner does not have can only ever pass on the developer's machine. One was reading a vendored file, this one is importing a library, and both look green in exactly the place that does not matter. So the workflow installs numpy, and tests/test_ci_dependencies.py parses every top-level import in tests/ and fails if one is missing from the pip line. Guarded imports are exempt: pytest.importorskip degrades to a skip rather than taking collection down with it. Verified by removing numpy from the workflow and watching the new test go red.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three site-survey failures in a row — a tree over the takeoff point, a parked car under the
landing point, a takeoff corridor — came down to where the aircraft was put, and each was
diagnosed by teleporting a disarmed vehicle and photographing it through a throwaway script.
This replaces that with a page you can fly.
Take off, land, translate, yaw. Chase, onboard and depth cameras, one large and two beside
it. GPS, local position, speed, attitude, IMU and battery — read from the same
/fmu/outtopicsa real Pixhawk publishes.
Making a general bridge obey hard stop 1
rosbridge_suiteis wrapped rather than rewritten, per the reuse rule. It exists to expose awhole ROS graph to a browser, which is exactly the wrong default for a page with a button that
arms an aircraft — so most of the work here is narrowing it:
topics_pub_glob = ['/mission/command']/fmu/in/vehicle_commandunreachable — measured: 0 bytes on that topic from a browser that triedservices_glob/actions_glob=[]set_parametersis reachableros2 topic pubwalks straight past a transport, so the check cannot live in oneWEB_BINDnames addressesThree properties of rosbridge 2.7.0 that this depends on were read out of its source, not its
README — notably that an unset glob means no checking at all, so security is opt-in and a
missing line opens the bridge.
It is a ground station, in its own container
On real hardware the ROS 2 graph runs on the companion computer and a web page that flies the
aircraft runs on someone's laptop.
sim-qgchas modelled that boundary since the Gazebo era;sim-webuiis its sibling. It carries no AirSim client and none of our own nodes.The chase camera pointedly did not follow it — a real aircraft has no chase camera, so it
stays on the simulator side. Same for depth:
32FC1metres is 1.2 MB/frame, so it is colourisedbeside the source and shipped as JPEG.
sim_up.shnow holds its container list once, and a test asserts nothing else names containers— the two hand-written copies were one edit away from a container left running under a teardown
that printed "verified".
Review
/reviewat high effort: 11 findings, all legitimate, all fixed, with 10 new regressionguards. Two were in the safety machinery itself and both were mine:
setpoint stream past
COM_OF_LOSS_Tand drop the aircraft into failsafetarget, so a delta of
(0,0,0)at 100 m snapped the hold point to the 60 m ceilingBoth flown afterwards at 80 m, deliberately above the ceiling — the configuration that made
them reachable. The sortie flown when the feature was built was at 12 m and could never have
exposed either. A flight test that passes is not coverage.
Verification
went north
step 500.0 m -> 5.0 m,climb -500.0 m -> -5.0 m,altitude -2.0 m -> floor 2.0 m, every clamp loggedbefore this branch — established by reverting
offboard_control.pytoHEADand reflying it(z range 0.026 vs 0.023 m, descent +1.73 vs +1.76 m/s)
Also here
The
SIM-27witness now brackets a flight instead of watching it, with no vendor patch — thedirect fix worked, was measured working, and was deleted because patching an upstream so our
tool stops breaking it inverts the primary rule. Its worklog is rewritten, at the instruction in
docs/handoff-2026-09-01.md, because the original described the fix that was thrown away.Known limits
the interlock refuses take-off unless AirSim's RPC answers.
SIM-44's open half, and the reason seed 2 still fails — is nowpossible from a browser but has not been flown.