Week 7 · Coding Period

A planning week — deciding where perception runs

The board was blocked on hardware in transit, so this week went to repo cleanup and to the architectural question the project had been quietly stepping around since Week 1: the three blockers that turned out to be one blocker, and the plan that comes out of answering it honestly.

Week 6 ended at a login prompt on real silicon. The obvious next move was to install ROS2 on the board and finish the job. I started down that road, hit a wall almost immediately, and the wall turned out to be a storage problem — one I can't solve until the NVMe drive arrives.

Rather than burn the week half-fixing something that a hardware change would make moot, I used it deliberately: tidied up the repository, and spent the rest of it on a question I should have asked in Week 2. This is a planning post. I'd rather write one of those honestly than dress up a week of reading as a week of shipping.

1. The wall: 47.9 MB

The Jetson's root filesystem, as built:

$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/mmcblk0p1  257M  189M  47.9M  80% /

Forty-eight megabytes free. ROS2 — even a trimmed base install — does not fit in that, and neither does anything else I need to add. The board boots, but it has nowhere to grow.

That is a boring problem with a boring fix, except that trying to fix it surfaced two more, and all three turned out to be the same problem wearing different hats.

2. The question underneath: where does perception run?

Right now the split is:

SERVER (Hetzner, no GPU) JETSON (AGL 21) CARLA → LiDAR → ROS2 → perception a WebSocket client │ printing to a console ▼ rosbridge ─────────────────►

Every interesting thing happens on the server. The Jetson receives finished results and displays them. That works — it's what Weeks 1 through 6 proved — but it means AGL is a thin client, and the word "ROS-Embedded" in this project's title isn't yet true of the embedded target. There is no ROS2 on the board at all.

So there are three honest options, and picking one decides the rest of the summer:

OptionWhat it meansCost
A — server-side perceptionAGL stays a visualization client. Works today.The title needs rewording. "Embedded" would be a stretch.
B — perception on AGLThe detector runs on the Jetson. What the title promises.Needs meta-ros, needs disk, needs a reflash.
C — CARLA moved localSimulator on a desk machine, short LAN hop.Requires a GPU workstation I don't have.

I'm going with B. Not only because it's what the project claims, but because it fixes a transport problem I'd been treating as acceptable — and shouldn't have.

3. The transport argument for moving perception on-board

rosbridge is the piece that makes the current setup possible: it re-publishes ROS2 topics as WebSocket JSON, which is why a 73-line standard-library client on AGL can subscribe without ROS2 installed anywhere near it. That was the right call for proving the link.

It is the wrong call for shipping point clouds. Here's the arithmetic on the current stream:

StageSize per frame
Raw LiDAR — ~23,000 points × 16 bytes≈ 368 KB binary
Base64-encoded into JSON by rosbridge≈ 500 KB text
At 10 Hz, over an SSH tunnel from Finland≈ 5 MB/s

Five megabytes a second of JSON parsing, to move data that was already binary, so that the receiving end can compute something small. If perception runs on the Jetson instead, the same link carries 3D bounding boxes — a few hundred bytes a frame. The point cloud never leaves the machine that generated it.

That's a three-orders-of-magnitude difference, and it's the actual engineering reason to move the detector, independent of what the project title says.

4. Three blockers that are one blocker

Chasing option B turned up three separate-looking obstacles:

Each of those on its own is a reflash. Together they're the same reflash — one recovery-mode cycle that lands NVMe boot, a graphical AGL image, meta-ros, and makes the Week 6 device-tree fix permanent instead of living on one card's boot partition.

That's the week's real output: not a fix, but the recognition that three things I was about to do separately are one thing, and doing them separately would mean three flash cycles and three chances to brick the board.

5. The nice surprise: NVMe deletes the Week 6 problem

Week 6 was almost entirely spent on a failing 3.3 V→1.8 V UHS voltage switch on the SD bus, fixed by patching no-1-8-v and nvidia,vmmc-always-on into the mmc@3400000 device-tree node.

NVMe is not an SD card. It's a PCIe device. There is no mmc@3400000 in that path, no SDMMC controller, and no voltage negotiation to fail. The entire saga that consumed Week 6 becomes irrelevant the moment the root filesystem lives on NVMe.

Worth being clear  That does not make Week 6 wasted. The device-tree fix still matters for anyone booting this board from SD, it's still worth upstreaming to meta-tegra, and the debugging discipline is the transferable part. But it's a good reminder that "solve the problem" and "remove the problem" are different moves, and the second one is often cheaper. I spent a week on the first without checking whether the second was available.

The SD card also survives the migration — flashing NVMe doesn't erase it. The two coexist, and the working SD image stays as a fallback if NVMe boot goes badly. That's what makes this reflash a reasonable risk rather than a gamble.

6. Looking ahead: what actually gets upstreamed

With the board sorted, the deliverable is an AGL application, and I spent part of this week reading the code I'd be joining rather than guessing at it.

AGL's reference instrument cluster is two repositories: meta-agl-cluster-demo (the Yocto layer — around 450 lines across two dozen recipe and config files) and agl-cluster-demo-dashboard (the application itself — roughly 1,900 lines of QML and C++). The layer is mostly thin glue: about thirty lines of recipe per application. The interesting part is the app.

And there's a detail in it worth stating plainly:

The largest single file in the dashboard is ValueSource.qml — 519 lines, inherited from a Qt Company example. It generates fake animated values. The speedometer, the tachometer, the fuel gauge: all driven by a sine-wave simulator, because the demo needs to look alive without a car attached.

That is a very clear place for this project to land. Replacing a fake data source with a live one from a real sensor pipeline is a small, well-scoped, obviously-useful contribution — and the dashboard already depends on qtwebsockets, which is exactly the transport rosbridge speaks. The seam is already there.

One open question I'll be putting to my mentors: the cluster demo is Qt/QML, but newer AGL HMI work has been moving toward Flutter. Building on the wrong one costs weeks, so that's a question to ask before writing code, not after.

7. Where it stands, and what's next

Status: the board boots. The full CARLA → LiDAR → ROS2 → perception → rosbridge pipeline works. The gap is that perception is on the wrong machine, and the board can't yet hold the software that would fix that.

As soon as the NVMe drive is in hand, in order:

The honest scoreboard  Six weeks of this project have been plumbing — transport, bring-up, bridges. That work was necessary and some of it was genuinely hard. But the HMI application, which is the part anyone looking at this project will actually see, is currently at zero lines. Naming that now, while there's still time to act on it, seems better than discovering it later.

The lesson from this week is less satisfying than a root cause but probably more useful: I spent six weeks executing without re-examining whether the architecture still matched the goal. Stopping to ask "where is this supposed to run?" cost a few days and changed the plan. It should have happened in Week 2.

← Week 6
Week 8 →