What you're actually building
The Jetson is a small Linux computer that watches AprilTags and answers one question, many times a second: where is the robot on the field? Everything in this guide exists to make that answer correct and to get it across the network.
The analogyThink of a navigator on a ship. They cannot know where they are from nothing — but sight a lighthouse whose position is printed on the chart, measure its bearing and how far off it lies, and their own position falls out of the arithmetic. The tags are lighthouses, the field layout is the chart, and the Jetson does the arithmetic.
Before touching a cable, it helps to see the whole chain. Each stage takes the previous stage's output and adds one piece of information. If the final pose is wrong, exactly one of these stages is lying to you — and knowing which one is most of the debugging.
Two things are worth noticing in that chain. First, the Jetson does the hard math — the roboRIO only receives a finished answer, so the RIO stays free for driving. Second, two calibration files sit in the middle of the chain: intrinsics and extrinsics. They are the subject of Part 06, because a perfect detector with wrong calibration produces confident, precise, wrong poses.
The parts on the table
Lay everything out before you start. A vision coprocessor that browns out mid-match looks exactly like a software bug, and you will spend a week chasing it.
The analogyThink of a telescope on a tripod. Fine optics on a wobbly tripod give you nothing, and an instrument that loses power halfway through an observation looks exactly like one that was aimed wrong. Half of this job is mechanical and electrical, not visual.
| Part | What it does | What goes wrong |
|---|---|---|
| Jetson module + carrier | Runs Linux and the detector on its GPU/accelerators. | Undersized power supply → resets under load. |
| Storage (microSD or NVMe) | Holds the OS, your binary, and the calibration files. | Full disk makes deploys fail silently. Check df -h. |
| Global-shutter camera | Captures frames with no rolling-shutter skew. | A rolling-shutter webcam smears tags while the robot turns. |
| Ethernet to the radio | Carries NetworkTables traffic to the roboRIO. | A loose RJ45 in the pit is the #1 "vision is broken". |
| Regulated 12 V feed | Powers the Jetson off the PDH/PDP. | Unregulated 12 V sags on a hard shift and reboots it. |
| Rigid camera mount | Holds the camera at a known, unchanging angle. | Any flex invalidates your extrinsics. Part 06. |
The mount is a software component. If a rookie can twist the camera by hand, your pose estimate has a hardware bug that no amount of code will fix.
Networking from zero
The rest of this guide talks about addresses, ports, clients and servers as if you already know them. Here they are from scratch, using nothing but the robot. If you already know this part, skip to Part 04 — nothing here is FRC-specific.
The analogyThink of a street of houses, each with a number, and each house with several numbered doors. The house number gets your message to the right building; the door number gets it to the right room. Knock on a door nobody is standing behind and nothing happens, however correct the address.
An address says which machine
Every device on the robot network gets an IP address — four numbers like 10.16.78.101. Read it as a street address: the first three numbers are the neighbourhood, and the last number is the house.
FRC assigns each team its own neighbourhood from its team number: a team numbered 16 78 gets 10.16.78.x. Everything on the robot — radio, roboRIO, Jetson, your laptop — lives at some house on that one street.
But an address only gets a message to the right machine. A machine runs many programs at once, so it also needs to know which program. That's a port — a numbered door on the house. NetworkTables uses door 5810. SSH uses door 22. A message with the right address but the wrong door hits a wall.
Send a few messages below and watch what happens.
Someone has to knock first
This is the idea that everything else in this guide depends on, and it is genuinely simple: in every connection, one side waits and the other side starts it.
- The side that waits with a door open is the server. It is not a big computer in a rack — it is just a program that is listening.
- The side that knocks is the client. It has to know where to knock.
Two clients can never talk to each other, because neither one is listening. That single fact explains the most common vision complaint there is: the Jetson and your dashboard are both clients. The roboRIO is the only server. Try the three scenarios below.
A socket is one live connection — that's what ss -tnp lists, and why it proves things a log line can't. Loopback — 127.0.0.1 — always means "this same machine, talk to myself". It never travels over a cable, which is why it can't reach the robot.
NAT: the one-way door
Your home router does this trick, and so does WSL. NAT lets several machines hide behind one address.
When a hidden machine sends something out, the router rewrites the message to say "from me", writes down who really sent it, and un-rewrites the reply on the way back. That works perfectly — in one direction. A message arriving from outside unprompted has no note to match, so the router doesn't know who it was for and drops it.
Where addresses come from
A machine gets its address one of two ways. With DHCP it asks the network on startup and takes whatever it's given — convenient, but the address can change between boots. With a static address you write the address into the device's config so it is the same every time.
Anything other machines must find gets a static address. That is why the Jetson is pinned to 10.16.78.101 in Part 05, while your laptop can happily take whatever DHCP gives it.
ping asks "is this machine there at all?" — address only, no port. (A firewall can block ping on a machine that is otherwise fine; on a robot network that is rare, but it is why a failed ping is a strong hint rather than proof.) nc -vz host port asks "is something listening behind that specific door?". ss -tnp lists the live sockets on the machine you run it on — "who am I actually connected to right now?" (add sudo if you also want the name of the process that owns each one). They answer three different questions, in that order.
The network map
Almost every "the Jetson isn't publishing" report is actually a network misunderstanding. NetworkTables has exactly one server and many clients — and the server is the roboRIO, not the Jetson.
The analogyThink of a village noticeboard. There is one board, in one place, and everybody walks to it — some to pin things up, some to read what is there. Nobody pins a notice to another villager.
Click each device below to see its address and its role. Pay attention to the arrows: they show who initiates the connection, which is the thing that determines what address you type where.
10.16.78.x subnet. The roboRIO runs the NT4 server on port 5810; the Jetson and your dashboard are both clients of it.Three addresses people type, and why two of them can’t work
When the data doesn’t appear, the same three addresses get tried, and all three look equally reasonable from the outside. Each fails for a different, specific reason — reading them side by side is the fastest way to make the client/server split stick.
| Address tried | What is actually there | Why it showed nothing |
|---|---|---|
| 10.16.78.101 | The Jetson | The Jetson is an NT client. It has no server for AdvantageScope to connect to. Unless the Jetson is explicitly started in server mode, nothing is listening on 5810 there. |
| 10.16.78.2 | The roboRIO — correct target | Right address, but it only has vision topics if the Jetson successfully connected to it. Verbose logs showing detections prove the detector works, not that the NT client connected. |
| 127.0.0.1 | Your own laptop | Loopback means "this machine". Nothing runs an NT server there unless you are running a simulator. From WSL, 127.0.0.1 is a third, different machine again. |
By default WSL2 sits behind NAT with its own private subnet. Outbound connections work, so the Jetson code you run inside WSL can reach the RIO — but nothing on the robot network can reach back into WSL, and a dashboard running on Windows does not share WSL's loopback. On Windows 11 22H2+, put networkingMode=mirrored under [wsl2] in %UserProfile%\.wslconfig and restart with wsl --shutdown to give WSL the same interfaces Windows has.
The three checks that settle it
Run these in order. The first one that fails is your bug.
# 1. Is the Jetson reachable at all? ping 10.16.78.101 # 2. Is the roboRIO's NT4 server actually listening? # Expect an open connection; a refusal means the RIO has no robot code running. nc -vz 10.16.78.2 5810 # 3. On the Jetson: did the publisher establish a session with the RIO? ss -tnp | grep 5810
Check 3 is the one that settles the argument. A verbose log line saying "detected tag 7" is printed before any network code runs, so it proves the detector works and nothing else. The socket table is the only place that tells you the publisher truly connected.
First contact with the Jetson
Getting a shell on the Jetson and pinning down its address. Do this once per Jetson and write the result on masking tape stuck to the carrier board.
The analogyThink of giving a house a permanent number and leaving a key with someone you trust. A house that renumbers itself every morning cannot receive post, and a door that demands a password every time cannot be opened by a script.
Flash and first boot
How you get an OS onto the Jetson depends on the board. Developer kits that boot from microSD take a written image card and nothing else. Modules with onboard eMMC or an NVMe drive are flashed from a host Linux machine over USB, using NVIDIA's SDK Manager — check which kind you have before buying an SD card and expecting it to work.
Either way, boot the Jetson once with a monitor and keyboard attached. The first-boot wizard asks for a username, password, and hostname, and SSH does not work until it has been answered. Give it a hostname you can say out loud on a field: jetson-front beats ubuntu.
A static address that survives the pit
DHCP is fine at home and a liability at competition. Pin the Jetson to 10.16.78.101 so every config file, every dashboard bookmark, and every rookie's memory agrees. On Ubuntu with NetworkManager:
# List connections, find the wired one (usually "Wired connection 1") nmcli connection show # Pin it. Gateway is the radio; the RIO is .2 sudo nmcli connection modify "Wired connection 1" \ ipv4.method manual \ ipv4.addresses 10.16.78.101/24 \ ipv4.gateway 10.16.78.1 sudo nmcli connection up "Wired connection 1" ip addr show # confirm 10.16.78.101 appears
Keys, not passwords
You will deploy to this machine hundreds of times. Copy your key over once so nothing ever prompts you — this also matters in Part 07, because an automated deploy rule that stops to ask for a password looks exactly like a build failure.
ssh-copy-id your-user@10.16.78.101
ssh your-user@10.16.78.101 'uname -m' # expect: aarch64Confirm the camera before anything else
Plenty of hours have been lost debugging a detector whose camera was never visible to Linux in the first place. Check that first, and check what it can actually produce — resolution and frame rate are not free choices, they are a list the sensor offers you.
v4l2-ctl --list-devices # is the camera there at all? v4l2-ctl -d /dev/video0 --list-formats-ext # formats, resolutions, frame rates
If nothing is listed, stop: it is a cable, a driver, or a power problem, and no vision code will fix it. If the camera appears but only offers low frame rates at your chosen resolution, decide that trade-off now rather than after calibrating.
That aarch64 is worth pausing on. Your laptop is x86_64. The Jetson is ARM. Every binary you build has to be built for the other architecture, which is the whole story of Part 07.
Intrinsics and extrinsics
Two calibrations, two completely different jobs. Intrinsics describe the lens. Extrinsics describe where the camera is bolted. Getting them confused produces poses that are wrong in a way that looks like noise.
The analogyThink of a pair of glasses, and where you are standing. The prescription decides how big and how bent everything looks; where you stand decides where everything is. Wrong prescription and the world is the right shape at the wrong size. Wrong position and the world is perfectly sharp and completely misplaced.
Intrinsics: how the lens turns a 3D world into pixels
A camera squashes the world onto a sensor. Intrinsics are the numbers that describe that squashing: focal length in pixels (fx, fy), where the optical centre lands on the sensor (cx, cy), and distortion coefficients that describe how the lens bends straight lines. You measure them once per camera by photographing a calibration board from many angles.
Two things in the panels below never change: the real tag on the left, and the image the camera actually captured on the right, in amber. The dashed teal outline is different — it is what your calibration file claims a tag at 4.00 m should look like. Drag the sliders and watch the two come apart. The gap between them is your calibration error, and the number underneath is the distance the robot would believe.
fx = 720 px, k1 = 0. Amber is the photograph; teal is your model of the camera. When they sit on top of each other the range is right. On a real robot you never see the amber — only the number, which is why a bad calibration is so quiet.An intrinsics error scales your distance estimate. If your focal length is 10% low, a tag genuinely at 4.00 m is reported at 3.60 m — every single frame, in the same direction. The pose estimator cannot average that away, because it is bias, not noise.
Extrinsics: where the camera lives on the robot
The detector gives you the tag's pose relative to the camera. Robot code needs the robot's pose relative to the field. Bridging that gap takes two known transforms: the field layout (where each tag is bolted to the field — published by FIRST) and the extrinsics (where the camera is bolted to the robot — measured by you).
In the diagram below, the solid robot is where the robot really is. Drag the sliders to change what your config file claims about the camera mount, and watch the estimated pose — the outline — drift away from the truth.
Angle errors grow with distance; offset errors do not. Two degrees of yaw error is 3.5 cm at 1 m and 35 cm at 10 m. This is why measuring the mount angle carefully matters far more than measuring the mount position carefully.
A single flat tag has two mathematically valid poses — the real one, and a mirrored one flipped about the tag's plane. The solver separates them by a small difference in how well each reprojects, and when the tag is far away, small in frame, or viewed close to straight on, it can pick the wrong one. The pose then jumps between two positions frame to frame. Two defences: prefer a solution computed from several tags at once, which has no such ambiguity, and reject single-tag solutions whose ambiguity ratio is above a threshold — around 0.2 is a common starting point.
Getting the files onto the Jetson
Both calibrations end up as files the vision binary reads at startup. Keep them in version control, name them after the physical camera (serial number, not "camera 1"), and copy them to a fixed path on the Jetson:
scp calib/intrinsics_cam-A17.json your-user@10.16.78.101:/home/your-user/vision/
scp calib/extrinsics_front.json your-user@10.16.78.101:/home/your-user/vision/
# Verify they landed and are what you think they are
ssh your-user@10.16.78.101 'md5sum ~/vision/*.json'
md5sum calib/*.jsonCamera tuning is the step most worth wrapping in a script — exposure, gain, and calibration capture in one command rather than a wiki page of manual steps. Until you have one, write down every exposure value you try. A detector that works in the shop and fails under field lighting is almost always an exposure problem, and the notes are what let you find that out in minutes.
Build and deploy
Your laptop is x86_64; the Jetson is aarch64. Bazel has to build for a machine it is not running on, then copy the result across the network. Both halves can fail, and they fail differently.
The analogyThink of writing a letter in a language the recipient reads, and then actually posting it. Two separate jobs. A flawless letter that never leaves your desk arrives exactly as often as one you never wrote.
The animation below shows the full path from source to a running process. Notice that "build succeeded" and "deployed" are two separate events. A deploy that quietly does nothing looks identical, from your terminal, to one that worked.
Reading the deploy command
Two forms of the same command. The second is worth studying, because it cannot do what it looks like it does:
# (a) The form you want bazel run -c opt --config=arm64 //frc/vision:download_stripped -- 10.16.78.101 # (b) Adding --cpu=x86_64 fights the arm64 config for the same setting bazel run -c opt --cpu=x86_64 --config=arm64 //frc/vision:download_stripped -- 10.16.78.101
A --config is not a flag in its own right — it is a named bundle of flags stored in .bazelrc, expanded in place where you wrote it. So if --config=arm64 sets the target CPU and you also pass --cpu=x86_64, both are setting the same knob and the one that ends up last wins: in form (b) the config expands after --cpu and overrides it. Either way one of your two intentions is silently discarded. Form (b) is not "both architectures" — there is no such thing. Start from form (a).
--cpu
--cpu is the legacy way to select a target. Modern Bazel resolves toolchains through --platforms instead, so a repository may set the target platform there and ignore --cpu entirely. Check what arm64 actually expands to in .bazelrc before adding flags next to it.
Diagnosing a deploy that builds but doesn't land
Work down this list. Each step answers a different question, and together they cover every way a deploy rule can no-op.
# 1. Where did Bazel put the output? The path names the configuration. bazel cquery -c opt --config=arm64 //frc/vision:vision_publisher --output=files # e.g. bazel-out/aarch64-opt/bin/... — a host-shaped path here is the whole bug # 2. Look at the artefact itself. This is the ground truth. file bazel-bin/frc/vision/vision_publisher # want: ELF 64-bit LSB, ARM aarch64 — not: x86-64 # 3. Watch what the rule actually executes. bazel run -s --verbose_failures -c opt --config=arm64 \ //frc/vision:download_stripped -- 10.16.78.101 # 4. Is the destination writable and non-full? ssh your-user@10.16.78.101 'df -h /; mount | grep " / "' # 5. Does the rule assume a different SSH user than yours? # Many download rules hardcode a user; without a key for THAT user, # the copy silently does nothing. grep -rn "ssh\|scp\|rsync\|user" frc/vision/BUILD
After any deploy — automatic or manual — check that the binary on the Jetson is the one you just built. Timestamps lie across machines; hashes don't.
md5sum bazel-bin/frc/vision/vision_publisher ssh your-user@10.16.78.101 'md5sum ~/vision/vision_publisher'
The manual fallback
Until the rule is fixed, scp is slower but honest. Wrap it in a script so nobody has to remember the paths. Note the systemctl restart at the end: for that to run unattended the Jetson needs a sudoers rule allowing it without a password, or the script will stop and wait for input that never comes.
#!/usr/bin/env bash # tools/deploy_vision.sh — build, copy, restart, verify set -euo pipefail JETSON="${1:-10.16.78.101}" USER_="${JETSON_USER:-your-user}" bazel build -c opt --config=arm64 //frc/vision:vision_publisher BIN=bazel-bin/frc/vision/vision_publisher file "$BIN" | grep -q aarch64 \ || { echo "NOT an ARM binary — check --config"; exit 1; } scp "$BIN" "$USER_@$JETSON:~/vision/vision_publisher.new" ssh "$USER_@$JETSON" 'set -e mv ~/vision/vision_publisher.new ~/vision/vision_publisher chmod +x ~/vision/vision_publisher sudo systemctl restart vision.service md5sum ~/vision/vision_publisher' md5sum "$BIN" echo "Compare the two hashes above."
Seeing the data
The publisher writes topics into NetworkTables. Getting them onto a dashboard is a matter of connecting to the right machine and knowing the topic names — and being aware that AdvantageScope hides fields you haven't asked for.
The analogyThink of the noticeboard again, this time with notices going up many times a second. Reading it means standing in the right hallway, knowing which notice you came for, and checking the date on it before you act.
Turn on verbose logging first
Before opening any dashboard, watch the Jetson's own output. You are looking for two distinct kinds of line: detections, and a connection event. (The exact verbosity flags depend on which logging library the publisher uses — the ones below are the glog/gflags style; substitute yours.)
ssh your-user@10.16.78.101 ~/vision/vision_publisher --v=3 --logtostderr # You want to see BOTH of these: # "detected tag 7 corners=(...)" <- the detector works # "NT: connected to 10.16.78.2:5810" <- the network works # Detections without a connection line is the classic silent failure.
Connecting AdvantageScope
Point AdvantageScope at the roboRIO — the machine running the NT server. Set the roboRIO address in preferences (the standard form is 10.TE.AM.2, so 10.16.78.2 here), then use File → Connect to Robot with NetworkTables 4 as the live source.
By default AdvantageScope only requests data for fields that are actively being used, so values published before you selected a field will not be there. If the tree looks empty, expand it fully and look for your table by name before concluding nothing is being published.
The debug tree
When the data still isn't there, work this tree instead of guessing. Each answer eliminates a whole class of causes.
Reading the pose in robot code
On the robot side you subscribe to the same topics — but there are two different times involved, and mixing them up is the most common way a working vision system still makes the robot drive badly.
- The time the value was published. NetworkTables gives you this for free on every value.
- The time the frame was captured. This is earlier — by the exposure, the detection, and the trip across the network — and it is the moment the pose actually describes.
WPILib's pose estimator takes a timestamp in seconds on the same clock as Timer.getFPGATimestamp(), and uses it to rewind its history and insert the measurement where it belongs. Hand it the arrival time instead of the capture time and every vision update is quietly late — invisible when the robot sits still, and a fight with odometry the moment it turns quickly.
So publish the capture latency from the Jetson alongside the pose, and subtract it here:
/** Your own small type — a pose plus the time the frame was taken. */ public record VisionSample(Pose2d pose, double captureTimestampSeconds) {} public class JetsonVision { private final NetworkTable table = NetworkTableInstance.getDefault().getTable("vision/front"); private final DoubleArraySubscriber poseSub = table.getDoubleArrayTopic("robot_pose") // [x, y, theta] .subscribe(new double[] {}); private final DoubleSubscriber latencySub = table.getDoubleTopic("capture_latency_us") // published by the Jetson .subscribe(0.0); public Optional<VisionSample> getLatestSample() { TimestampedDoubleArray v = poseSub.getAtomic(); if (v.timestamp == 0 || v.value.length < 3) return Optional.empty(); // Robot code IS the NT server, so v.timestamp is already on the FPGA clock, // in microseconds. In a client you would add getServerTimeOffset() first. double publishedAt = v.timestamp / 1e6; double capturedAt = publishedAt - latencySub.get() / 1e6; if (Timer.getFPGATimestamp() - capturedAt > 0.25) return Optional.empty(); // stale return Optional.of(new VisionSample( new Pose2d(v.value[0], v.value[1], new Rotation2d(v.value[2])), capturedAt)); } } // Then, once per loop — note it is the CAPTURE time that goes in: // vision.getLatestSample().ifPresent(sm -> // poseEstimator.addVisionMeasurement(sm.pose(), sm.captureTimestampSeconds()));
If the Jetson dies mid-match, the last pose it published stays on the noticeboard forever, looking perfectly valid. Rejecting anything older than a couple of hundred milliseconds turns a silently wrong pose into no pose, which the estimator handles correctly by falling back on odometry.
Topic names and array layout are yours to choose, so choose them once and write them down before other code depends on them. Whatever you pick, publish a heartbeat counter alongside the pose: without it the robot cannot tell "no tags visible" apart from "the Jetson died", and those two situations call for opposite responses.
Starting on boot
Nobody should be SSHing into a robot on the field. systemd starts the publisher at boot and restarts it if it crashes — the state machine below is what you're signing up for.
The analogyThink of a night watchman rather than a light switch. A switch has to be flipped by hand, by someone who is present. A watchman turns the lights on when the building opens, and turns them back on when they trip — without being asked.
Restart=always a crash costs you RestartSec seconds, not a match. Without it, a single crash means vision is gone until someone notices.The unit file
# /etc/systemd/system/vision.service
[Unit]
Description=AprilTag vision publisher
After=network-online.target
Wants=network-online.target
[Service]
User=your-user
WorkingDirectory=/home/your-user/vision
ExecStart=/home/your-user/vision/vision_publisher \
--intrinsics=/home/your-user/vision/intrinsics_cam-A17.json \
--extrinsics=/home/your-user/vision/extrinsics_front.json \
--nt_server=10.16.78.2
Restart=always
RestartSec=2
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload sudo systemctl enable --now vision.service systemctl status vision.service # is it running? journalctl -u vision.service -f # live logs journalctl -u vision.service -b -1 # logs from the PREVIOUS boot
After=network-online.target matters: without it the service can start before the static IP is assigned, fail to reach the RIO, and — with Restart=always — thrash. That target only means anything if something is actually waiting on the network, so confirm NetworkManager-wait-online.service is enabled. Second, enable and start are different verbs: enable means "at boot", start means "now"; enable --now does both. Third, Restart=always is not unlimited — by default systemd gives up after 5 starts in 10 seconds and leaves the service failed. With RestartSec=2 a program that dies instantly can trip exactly that limit, so a service you expected to retry forever is simply dead. Set StartLimitIntervalSec=0 in the [Unit] section if you want it to keep trying no matter what.
Faster builds: a cache in the pit
Cross-compiling is slow, and by default every laptop recompiles the same objects from scratch. A shared Bazel cache turns the second person's build into a download.
The analogyThink of a shared pantry. The first person cooks the dish and leaves a portion in the fridge; everyone after takes the portion instead of cooking it again. Nothing is faster than work you don’t have to do.
Bazel is deterministic: the same inputs and the same flags produce the same output. That means one machine's compile result is valid for everyone. A remote cache stores those results, keyed by a hash of the inputs.
# On the pit computer — a plain HTTP cache is enough for a team docker run -d --restart=unless-stopped -p 8080:8080 \ -u 1000:1000 \ -v /srv/bazel-cache:/data \ buchgr/bazel-remote-cache --max_size 40 # GiB, and required # In the repo's .bazelrc — everyone gets it automatically build --remote_cache=http://10.16.78.50:8080 build --remote_upload_local_results=true build --remote_timeout=60 # Don't let a missing pit computer break builds at home: build --remote_local_fallback=true
A cache only helps if the keys are honest. If a build action reads something Bazel doesn't know about — a file outside the sandbox, a timestamp, the hostname — you can get a "hit" that isn't valid. If a build ever behaves differently on two laptops, test with --noremote_accept_cached before blaming your code.
Glossary
Every piece of jargon this guide uses, in plain words. Type to filter, or pick a group. Nothing here assumes you've read the rest.
- IP address
- Four numbers like
10.16.78.101that identify one machine on a network. Nothing can be sent to a machine that doesn't have one. - Subnet
- The group of addresses that can reach each other directly. On the robot network everything sharing
10.16.78.is on one subnet; the trailing/24you see in configs means "the first three numbers are the neighbourhood". - Port
- A number that picks which program on a machine receives a message — a numbered door. NetworkTables listens on
5810, SSH on22. - Server
- The program that waits with a port open. Not a special computer — just a program that is listening.
- Client
- The program that starts the connection. It must know the server's address and port. Nothing can connect to a client.
- Socket
- One live connection between two programs. If a socket exists, they are genuinely talking right now — which is why
ss -tnpproves things a log message can't. - Loopback · 127.0.0.1 · localhost
- An address that always means "the machine I am running on". It never travels over a cable, so it can never reach the robot.
- NAT
- Network Address Translation. A router lets several hidden machines share one address by rewriting outgoing messages and remembering who really sent them. Works outbound; blocks unprompted inbound.
- DHCP
- The device asks the network for an address at startup and takes whatever is free. Easy, but the address can change between boots.
- Static address
- An address written into the device's own config so it never changes. Anything other machines must find by address needs one.
- Gateway
- The device that forwards traffic destined for anywhere outside your subnet. On the robot that's the radio.
- ping
- Asks "is this machine reachable at all?" Address only — it says nothing about whether any program is running.
- NetworkTables · NT4
- WPILib's shared noticeboard. Programs publish named values (topics) and others subscribe. Version 4 is the current one; the roboRIO hosts the server.
- Topic
- One named value on the noticeboard, like
vision/front/robot_pose. Publishers write it; subscribers read it. - WSL
- Windows Subsystem for Linux — a real Linux system running inside Windows. By default it sits behind NAT with its own address, which is why it counts as a separate machine.
- Shell
- The text prompt where you type commands. The
$or#at the start of a line in this guide means "type this at the shell". - SSH
- Secure Shell — opens a shell on another machine over the network.
ssh user@10.16.78.101gets you a prompt on the Jetson. - SSH key
- A pair of files that logs you in without a password. Copy the public half to the other machine once with
ssh-copy-id; automated deploys need this or they stall waiting for a password. - scp
- Copies files over SSH.
scp local remote:/pathpushes; swap the arguments to pull. - sudo
- Run one command as the administrator (root). Needed to change system settings like the network config or a service file.
- Hostname
- A machine's human name, like
jetson-front. Easier to say on a field than four numbers. - Binary
- The compiled, runnable file your source code turns into. The Jetson runs a binary; it never sees your source.
- ELF
- The file format Linux binaries use.
file some_binaryprints it, along with the CPU the binary was built for — the fastest way to catch a wrong-architecture build. - aarch64 · ARM
- The Jetson's processor family. Different instruction set from your laptop, so binaries are not interchangeable.
- x86_64
- Your laptop's processor family. A binary built here will not run on the Jetson.
- systemd
- The program Linux starts first, which starts everything else. You hand it a unit file and it runs your program at boot and restarts it when it dies.
- Service · unit file
- The small config file telling systemd what to run, as whom, with which arguments, and what to do when it exits.
- enable vs start
startmeans "run it now".enablemeans "run it at every boot".enable --nowdoes both — forgetting this is why a service works today and is missing tomorrow.- journalctl
- Reads the logs systemd collected.
-ffollows live;-b -1shows the previous boot, which is where the reason for a mystery reboot lives. - Hash · md5sum
- A short fingerprint computed from a file's contents. Two files with the same hash are the same file — the only trustworthy way to confirm a deploy actually landed.
- Bazel
- The build tool this repo uses. You name a target and it works out everything that must be compiled first.
- Target · label
- A thing Bazel can build, written
//frc/vision:vision_publisher— the path before the colon, the name after. - Action
- One step of a build — compiling a single file, linking a binary. Caching happens per action, not per build.
- Cross-compile
- Building on one kind of machine for another. Your x86_64 laptop producing an aarch64 binary for the Jetson.
- Toolchain
- The compiler and libraries used for a build. Cross-compiling means selecting a toolchain that targets the Jetson instead of your laptop.
- .bazelrc · --config
- A file of saved flag bundles.
--config=arm64means "apply the flags stored under arm64" — which is why passing a conflicting flag alongside it silently overrides part of it. - Remote cache
- A shared store of finished build actions, keyed by a hash of their inputs. One person compiles; everyone else downloads.
- Cache hit / miss
- A hit means the result already existed and was downloaded. A miss means it had to be genuinely built.
- Sandbox
- The restricted directory a build action runs in, containing only its declared inputs. It's what makes caching trustworthy — an action that sneaks a file in from outside can produce a bad cache entry.
- Stripped binary
- A binary with its debug symbols removed. Smaller and faster to copy, harder to debug.
- AprilTag
- A printed black-and-white marker with an ID encoded in its pattern. Because its real size and position are known, seeing one tells you where you are.
- Fiducial
- The general word for a marker placed in a scene specifically so a camera can measure from it. An AprilTag is one.
- Pose
- A position and an orientation together — where something is and which way it faces. On the field: x, y, and an angle.
- Field-relative
- Measured from a fixed corner of the field rather than from the robot or camera. What the pose estimator wants.
- Intrinsics
- The numbers describing the lens and sensor: focal length, optical centre, distortion. One set per physical camera, measured once.
- Extrinsics
- Where the camera sits on the robot — offset and angle. Changes whenever the mount changes.
- Distortion
- The way a lens bends straight lines, strongest near the edges of the frame. Corrected using coefficients from calibration.
- solvePnP
- The algorithm that turns four known corner points plus the intrinsics into the tag's position and orientation relative to the camera.
- Global shutter
- A sensor that captures every pixel at the same instant. A rolling shutter captures row by row, which smears and skews tags while the robot moves.
- Exposure · gain
- How long the sensor collects light, and how much the signal is amplified afterwards. Wrong values make tags undetectable under field lighting even though everything worked in the shop.
- Pose estimator
- The robot-code component that blends wheel odometry, the gyro, and vision poses into one best guess of where the robot is.
- Stale data
- A value that arrived too long ago to still be true. Worse than no value, because the estimator will trust it.
- Capture latency
- The gap between the shutter opening and the answer arriving on the robot — exposure, detection, and network time added together. Subtract it from the publish time to get the moment the pose actually describes.
- Pose ambiguity
- A single flat tag has two mathematically valid poses, one a mirror of the other. The ambiguity ratio says how close the two are; a high value means the solver is guessing.
- Reprojection error
- Take a candidate pose, draw where the tag's corners should appear, and measure how far that is from where they actually appeared. Small error means a believable pose.
- FPGA timestamp
- The roboRIO's master clock, in seconds since the robot powered on. WPILib's pose estimator measures every measurement against it, which is why vision timestamps must be converted onto it.
- v4l2
- Video4Linux2, the Linux camera interface.
v4l2-ctllists which cameras exist and which resolutions and frame rates each one can actually deliver. - Heartbeat
- A counter published continuously so the receiver can tell "the sender is alive but sees nothing" apart from "the sender is dead".