This document covers running AOG-TaskController as a headless Linux service. It is the natural home for the TC when paired with AgValoniaGPS or any cross-platform AgOpenGPS variant, and it is the recommended deployment when the TC lives on a single-board computer (Raspberry Pi, NVIDIA Jetson, etc.) in the tractor cab.
For the UDP wire protocol used by AgIO / AgValonia / third-party clients, see PROTOCOL.md.
The Linux build produces a single statically-configured binary that runs in console mode (no GUI, no tray). It is built and released as a tarball by GitHub Actions for two architectures:
| Tarball | Target |
|---|---|
AOG-TaskController-linux-x86_64.tar.gz |
64-bit Intel/AMD Linux (most desktops, NUCs, x86 fanless boxes) |
AOG-TaskController-linux-aarch64.tar.gz |
64-bit ARM Linux (Raspberry Pi 3/4/5/Zero 2 W under 64-bit Raspberry Pi OS, Jetson, generic ARM SBCs) |
The TC reaches the ISOBUS via Linux SocketCAN (--can_adapter=socketcan --can_channel=<iface>). Linux has no built-in CAN controller on most SBCs, so you need one of:
- SPI CAN HAT based on MCP2515 (e.g. Waveshare RS485-CAN-HAT, PiCAN2). Driver is in mainline kernel, no userspace install. Comes up as
can0after a small config tweak. - USB CAN adapter (CANable v2, Innomaker USB2CAN, Geschwister Schneider gs_usb). Driver is in mainline. Comes up as
can0when plugged in.
Either works. The TC does not care which — it only opens the can0 (or whatever you pass via --can_channel) network interface.
Measured on Ubuntu 24.04 aarch64 (Parallels VM, smoke-tested against vcan0):
| Resource | Steady state |
|---|---|
| CPU | <5% of one core. Main loop sleeps 1 ms between ticks; CAN I/O runs on its own thread. |
| RAM (RSS) | ~10–20 MB. |
| Disk | A few MB for the binary; optional log files under the config directory. |
| Network | <10 kbps UDP to AgIO/AgValonia. CAN bus usage depends on the implement. |
Pi Zero 2 W (4× Cortex-A53 @ 1 GHz, 512 MB RAM) handles this trivially. The bottleneck on a Pi deployment is the CAN adapter setup, not the TC itself.
ARCH=$(uname -m) # "x86_64" or "aarch64"
TARBALL=AOG-TaskController-linux-${ARCH}.tar.gz
# Download the artifact attached to the GitHub Release you want
curl -LO https://github.com/AgOpenGPS-Official/AOG-TaskController/releases/latest/download/${TARBALL}
tar -xzf "${TARBALL}"
cd AOG-TaskController-linux-${ARCH}
# Install (as root)
sudo install -m 0755 bin/AOG-TaskController /usr/local/bin/AOG-TaskController
sudo install -m 0644 lib/systemd/system/aog-taskcontroller.service /etc/systemd/system/aog-taskcontroller.service
sudo install -m 0644 -D share/AOG-TaskController/settings.sample.json /etc/aog-taskcontroller/settings.sample.jsonsudo apt-get install -y build-essential cmake ninja-build
git clone https://github.com/AgOpenGPS-Official/AOG-TaskController.git
cd AOG-TaskController
cmake -S . -B build -G Ninja -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -Wno-dev
cmake --build build
sudo install -m 0755 build/AOG-TaskController /usr/local/bin/AOG-TaskControllerBoost and AgIsoStack are fetched automatically via CMake FetchContent — no system Boost required.
Add to /boot/firmware/config.txt (Pi 5/Bookworm) or /boot/config.txt (older):
dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25
Reboot, then bring the interface up:
sudo ip link set can0 up type can bitrate 250000
ip -details link show can0ISOBUS bitrate is 250 kbps. To make it survive reboots, add this to /etc/systemd/network/80-can0.network or call ip link set from a unit file that runs before aog-taskcontroller.service.
Most USB adapters using the gs_usb kernel driver appear as can0 automatically. Bring it up the same way:
sudo ip link set can0 up type can bitrate 250000sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0Run the TC with --can_channel=vcan0. Useful for protocol-level testing without an implement.
The TC reads settings.json from $XDG_CONFIG_HOME/AOG-TaskController/ (typically ~/.config/AOG-TaskController/ for an interactive user, or /var/lib/aog/.config/AOG-TaskController/ when running under the aog system user).
{
"subnet": [192, 168, 5],
"tecuEnabled": true,
"nmeaSendEnabled": true,
"aogHeartbeatEnabled": true,
"vtEnabled": true,
"tcVersion": 3,
"languageCode": "en",
"countryCode": "US"
}| Key | Type | Default | Meaning |
|---|---|---|---|
subnet |
int[3] |
[192, 168, 5] |
First three octets of the LAN the AgIO/AgValonia host lives on. TC enumerates NICs (via getifaddrs(3)) and binds the one whose IP starts with these octets. AgIO can override at runtime via the subnet-detection PGN. |
tecuEnabled |
bool |
true |
Whether the TC also impersonates a Tractor ECU on the bus (Speed Messages, NMEA2000 COG/SOG, BasicTractorECUServer announce). Set false if the tractor already has a TECU on the harness. |
nmeaSendEnabled |
bool |
true |
Whether the TECU sends cyclic NMEA2000 COG/SOG messages. Has no effect when tecuEnabled is false. |
aogHeartbeatEnabled |
bool |
true |
Whether the TC sends a 100 ms heartbeat UDP packet to AgIO/AgValonia even when no implement is connected. Set to false if pairing with AgOpenGPS pre-v6.8.2 beta 5 — older AOG versions choke on the heartbeat. |
vtEnabled |
bool |
true |
Whether the TC registers its Virtual Terminal UI. |
tcVersion |
integer |
3 |
Task Controller version code (0–4); 3 selects Second Edition Draft. |
languageCode |
string |
"en" |
Two-character ISOBUS language code. |
countryCode |
string |
"US" |
Two-character ISOBUS country code. |
See PROTOCOL.md for the full reference.
The release tarball ships lib/systemd/system/aog-taskcontroller.service:
[Unit]
Description=AOG-TaskController (ISOBUS TC for AgOpenGPS)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aog
ExecStart=/usr/local/bin/AOG-TaskController --can_adapter=socketcan --can_channel=can0 --log_level=info --log2file
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetTweak User=, --can_channel=, and --log_level= for your deployment. Then:
sudo useradd --system --home-dir /var/lib/aog --create-home aog
sudo systemctl daemon-reload
sudo systemctl enable --now aog-taskcontroller.service
sudo systemctl status aog-taskcontroller.service
journalctl -u aog-taskcontroller -fWhen the unit is active, --log2file writes to /var/lib/aog/.config/AOG-TaskController/logs/.
ip link set requires CAP_NET_ADMIN. The two clean options:
- Bring
can0up at boot from a separate root-owned unit, beforeaog-taskcontroller.servicestarts. Recommended. - Give the
aoguser permission viaAmbientCapabilities=CAP_NET_ADMINin the unit (less clean; the TC binary doesn't need to configure CAN itself — it just opens the interface).
- TC binds UDP 8888 on the LAN interface that matches
subnet, and on0.0.0.0:8888for subnet detection. - TC sends to UDP 9999 on the broadcast address of the matched subnet (e.g.
192.168.5.255:9999). - Firewalls: open inbound UDP 8888 (for AgIO/AgValonia → TC) and allow outbound UDP 9999 broadcast.
- Wi-Fi on Pi Zero 2 W is 2.4 GHz single-antenna. In a metal cab with electrical noise, you may want a Pi 4/5 or a wired link for production. Pi Zero 2 W is fine for development and bench testing.
TC starts but doesn't see AgIO/AgValonia
Check subnet in settings.json matches the LAN. The TC logs Available IP addresses (getifaddrs): at startup — verify your LAN NIC appears there and is in subnet.
bind: Address already in use
Something else is already on UDP 8888. The TC sets SO_REUSEADDR so two TCs on the same port won't fight, but a different daemon (or a stale TC) will conflict.
No CAN traffic
ip -details link show can0should showUP. If it'sDOWN, runsudo ip link set can0 up type can bitrate 250000.candump -t a can0should print frames if anything is on the bus.dmesg | grep -i canshows controller errors (bus-off, overruns) — most often a wiring or termination problem.
TC claims address 247 instead of the preferred 233
The address-claim arbitration walked up because either (a) the bus is empty and AgIsoStack's fallback range was used, or (b) something else has the preferred address. Look for an address-claim NACK in --log_level=debug output.
TECU collisions
If the tractor's factory TECU is on the bus, set tecuEnabled: false to avoid duplicate Speed Messages and NMEA2000 broadcasts. The TC will log [Info] Tractor ECU disabled in settings... at startup.
VT is detected but the UI never appears
The VT pool is embedded in the executable, so no external .iop file or service working directory is required. The pool scales from a 480-pixel data mask and 80-pixel softkey designator, and it uses five virtual navigation softkeys. Check the logged screen/softkey dimensions and softkey counts; a terminal with fewer than five physical keys must support paging. If the client remains disconnected for 30 seconds, the TC emits a detailed warning. Clear the VT's stored/cached object pools, then restart the TC and VT before retrying.
| Scenario | Location |
|---|---|
| Interactive run | stdout of the terminal. Add --log2file to also write to ~/.config/AOG-TaskController/logs/AOG-TaskController_YYYY-M-D_H-M.log. |
| systemd unit | journalctl -u aog-taskcontroller. With --log2file, also /var/lib/aog/.config/AOG-TaskController/logs/. |
Log levels (lowest to highest): debug, info, warning, error, critical. Default is whatever AgIsoStack defaults to; set explicitly with --log_level=.