Documentation
OpenRoadCode Termux Target
Source:
development/termux/README.md
This directory contains the native Termux build workflow used to exercise OpenRoadCode directly on Android hardware.
The current target combines:
- the OpenRoadCode Android sensor bridge on localhost port
8766; - native Python controllers and services running in Termux;
- the OpenRoadCode ZeroMQ broker, navigation service, and simulated ADS-B web presentation under runit supervision;
- Android-backed geographic position through the sensor bridge;
- simulated IMU input in the current Termux navigation profile;
- native Valhalla and MapLibre builds;
- Termux:X11 for graphical execution;
- optional native Linux games, including Debian packages hosted through
proot-distro; - optional hardware-accelerated graphics when a compatible Mesa backend is available; and
- offline navigation data stored under
~/.local/share/openroadcode.
CarUi keeps the shared runtime composition in config/runtime.toml and selects config/applications.termux.toml for Termux-specific application behavior. config/runtime.termux.toml is the explicit Android/Termux navigation and sensor-service profile through OPENROAD_RUNTIME_CONFIG.
Graphics acceleration
Android devices do not share one GPU architecture, so OpenRoadCode does not assume a particular video driver. development/termux/configure_graphics.sh inspects the device and selects only a graphics backend that the installed Termux repositories can support.
Run detection without changing packages:
./development/termux/configure_graphics.sh
Install the packages selected for the detected device:
./development/termux/configure_graphics.sh --install
The first validated native Termux hardware path is Qualcomm/Adreno. On a device exposing the KGSL interface, when the Termux repository supplies mesa-vulkan-icd-freedreno, the selected stack is:
flowchart LR
adreno["Adreno"] --> kgsl["KGSL"] --> turnip["Turnip / Freedreno"] --> vulkan["Vulkan"] --> zink["Zink"] --> opengl["OpenGL"] --> x11["Termux:X11"]
classDef orcApp fill:#dbeafe,stroke:#2563eb,color:#172554;
classDef orcService fill:#ede9fe,stroke:#7c3aed,color:#2e1065;
classDef orcController fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef orcMessage fill:#ffedd5,stroke:#ea580c,color:#7c2d12;
classDef orcAdapter fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
classDef orcExternal fill:#f3f4f6,stroke:#6b7280,color:#1f2937;
class adreno,kgsl,turnip,vulkan,zink,opengl,x11 orcExternal;
For that native Termux backend, OpenGL applications should be launched with:
export MESA_LOADER_DRIVER_OVERRIDE=zink
Validate each layer independently when bringing up a new device. When the corresponding diagnostic packages are installed, useful probes are:
vulkaninfo --summary
MESA_LOADER_DRIVER_OVERRIDE=zink glxinfo -B
MESA_LOADER_DRIVER_OVERRIDE=zink glxgears
Do not install every Mesa Vulkan ICD indiscriminately and do not assume Freedreno on Mali, PowerVR, or other GPU families. Unknown devices retain the basic/software graphics path until a hardware backend has been validated. Application launchers should consume the selected graphics environment rather than hard-coding a GPU vendor.
Chromium launched under Termux:X11 should use --password-store=basic so it does not depend on a desktop password-keyring service. GPU-specific Chromium flags should remain platform/runtime configuration rather than UI code.
Debian/proot OpenGL for games
For the native Chromium/WebGL alternative-map experiment, see Google Earth navigation.
Debian applications running under proot-distro use glibc and cannot safely load Termux/Bionic Turnip libraries directly. For graphical Debian games, OpenRoadCode instead supports Mesa virpipe with Termux virglrenderer-android:
flowchart TD
game["Debian game / OpenGL"] --> mesa["Debian Mesa virpipe"] --> socket["Shared /tmp/.virgl_test socket"]
socket --> server["Termux virgl_test_server_android"] --> gpu["Android graphics stack / GPU"]
classDef orcApp fill:#dbeafe,stroke:#2563eb,color:#172554;
classDef orcService fill:#ede9fe,stroke:#7c3aed,color:#2e1065;
classDef orcController fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef orcMessage fill:#ffedd5,stroke:#ea580c,color:#7c2d12;
classDef orcAdapter fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
classDef orcExternal fill:#f3f4f6,stroke:#6b7280,color:#1f2937;
class game orcApp;
class mesa,socket,server orcAdapter;
class gpu orcExternal;
Install the Android VirGL server package when using this path:
pkg install virglrenderer-android
The Debian command runner starts virgl_test_server_android on demand when it is installed and exposes the shared socket to Debian with proot-distro --shared-tmp. It sets GALLIUM_DRIVER=virpipe for that Debian process only. This is intentionally separate from the native Termux Zink/Turnip environment.
SuperTuxKart is configured to use its OpenGL renderer rather than Vulkan on this path. Debian Vulkan may otherwise select a software renderer even when OpenGL through VirGL is accelerated.
Experimental GNOME game trial
GNOME 2048, GNOME Nibbles, and GNOME Sudoku are enabled for a Termux/X11
trial. Their proot configuration uses rendering = "auto" and
GSK_RENDERER=gl to select GTK OpenGL through the same VirGL bridge. Live
device rendering, input, embedding, and exit behavior remain unverified.
GTK versions that do not use GSK may ignore this renderer setting.
On the device, switch to the branch containing this trial before pulling or
testing. Close ORC, run ./development/termux/install_games.sh, then restart
ORC in the usual Termux/X11 session. Install each game through the Games panel
and test one at a time:
- Start a game and check that its board renders inside ORC without a blank window or rendering errors.
- Play a few moves (2048/Sudoku) or a level (Nibbles), then resize the ORC window and check drawing and input again.
- Use EXIT GAME, relaunch, and also test closing through the game’s own menu. Check that ORC returns to the game browser each time.
To inspect the Debian OpenGL renderer after launching a game has started the
bridge, install mesa-utils inside Debian if needed and run in Termux:
proot-distro login debian --shared-tmp -- env DISPLAY="$DISPLAY" \
XDG_RUNTIME_DIR=/tmp LIBGL_ALWAYS_SOFTWARE=true GALLIUM_DRIVER=virpipe glxinfo -B
Record the renderer string and any game errors. virgl indicates the bridge;
llvmpipe or softpipe indicates software rendering. This probe alone does not
prove that an individual game uses GPU rendering or performs well.
Compare failures with the previous software configuration by setting that game’s
rendering = "software" and GSK_RENDERER = "cairo" in config/games.toml,
then restarting ORC. Set enabled = false to hide a failing game again.
Native games
The ORC UI Games panel reads config/games.toml, discovers installed/available packages asynchronously, and supports both Termux packages and Debian packages. Install the Termux-side game prerequisites with:
./development/termux/install_games.sh
The Games frontend requires xdotool for X11 embedding. When a Debian game is selected, the controller chooses the Debian backend without exposing whether Debian is native or hosted through proot-distro to the UI.
The game browser adapts its columns to the window size and font metrics. Narrow Termux/X11 windows show one column. Each full page keeps six games; short windows scroll vertically instead of hiding games or stretching cards. Use PREV/NEXT to navigate between pages. Titles and descriptions wrap above the action button, and installation status appears below the wrapping filters.
While a game is active, ORC replaces the category browser with an EXIT GAME control and reparents the game’s X11 window into the Games content area. Closing a game through its own menu is also detected and returns the panel to the game browser. Window embedding is best effort because third-party games can create helper processes or reposition their own top-level windows; the X11 frontend searches the launched process tree and reasserts the ORC host geometry during startup.
Navigation contracts
Navigation data is intentionally separated by responsibility:
- Position contains geographic fix information such as latitude, longitude, altitude, fix mode, satellite counts, and accuracy.
- Ground motion contains speed over ground, course over ground, vertical speed, and turn rate.
- Attitude contains heading, pitch, and roll.
- IMU contains acceleration and angular-velocity measurements.
- Route guidance contains progress and maneuver state for an active route.
Position does not conceptually own speed or course. A physical provider may deliver those values with a location sample, but normalized OpenRoadCode consumers remain insulated from the provider-specific transport.
The current Termux navigation profile uses the Android bridge as its physical position source and keeps IMU simulation available so navigation remains usable when Android motion integration is unavailable. The map consumes the normalized navigation position contract rather than talking directly to Android.
Simulate on an active route now generates positions inside ORC’s navigation
service. The Android bridge stays on real GPS. Stop simulation, End route,
arrival, or a service restart restores normal position input. This Python change
requires restarting openroadcode-navigation, but no renderer rebuild. After
updating, test a short route through playback, stop, arrival, and restart; verify
that live position returns. The full native renderer/theme-toggle test and live
weather-provider probes still need device verification; CI does not prove GPU
rendering. See Weather component probes.
Automotive transport on Termux
PySerial is intentionally not a Termux dependency. Android/Termux automotive hardware uses the Android bridge and TCP transport rather than opening a serial device directly from Termux.
The ELM327 stack keeps transport selection behind the common stream-transport interface. Physical Linux targets may select the serial backend when PySerial is installed, while Android/Termux injects the TCP transport exposed by the bridge. Importing the OBD-II controller stack or running transport-injected/simulated tests must not require PySerial merely to collect or import the modules.
For the validated KONNWEI/ELM327 Android path, the bridge exposes the Bluetooth SPP connection through localhost TCP. This keeps Bluetooth ownership in Android while OpenRoadCode consumes the same ELM327 protocol through a platform-neutral byte stream.
Build the native navigation stack
cd ~/src/OpenRoadCode
./development/termux/build_navigation_stack.sh
The script installs/builds native dependencies and prints the resulting paths. Termux:X11 normally uses display :1; override it with X11_DISPLAY when needed.
Test the Android sensor bridge
With the openroadcode-android-bridge application running:
curl http://127.0.0.1:8766/health
curl http://127.0.0.1:8766/location
curl http://127.0.0.1:8766/imu
A healthy /location response is consumed by AndroidPositionSource and published by the normal navigation service. This keeps map-follow and other consumers independent of the Android bridge API.
Run supervised Termux services
Install Termux service supervision once:
pkg install termux-services
Restart the Termux shell after first installing termux-services, then from the repository root run:
chmod +x scripts/runit/install_termux_services.sh
./scripts/runit/install_termux_services.sh
Start and inspect the supervised services with:
./scripts/runit/manage_core.sh start
sv up openroadcode-adsb
./scripts/runit/manage_core.sh status
sv status openroadcode-adsb
Stop them with:
sv down openroadcode-adsb
./scripts/runit/manage_core.sh stop
The runit definitions call the same runtime wrappers used by the Linux service installation where applicable. Termux-specific service definitions live under scripts/runit/. Runtime-generated supervise/ directories are state, not source, and must never be committed to the repository.
Valhalla runs as the supervised openroadcode-valhalla runit service when the
core stack is requested; it remains down after installation. The navigation
build installs its service definition along with the core services. The Termux
build installs it under $PREFIX/opt/openroadcode/navigation/valhalla/bin/valhalla_service, while deployed routing data lives under ~/.local/share/openroadcode/valhalla.
For an existing installation, register the new service once (stop any manually launched Valhalla first):
cd ~/src/OpenRoadCode
./scripts/runit/install_termux_services.sh
sv up openroadcode-valhalla
sv status openroadcode-valhalla
The wrapper detects Termux, reads the installed valhalla.json, and writes a
prepared copy under $PREFIX/tmp/openroadcode-valhalla. It relocates IPC sockets,
standard routing data, optional data paths, and Linux log paths into writable
Termux locations. The downloaded source configuration stays unchanged. Run the
wrapper again after pulling new routing data; no manual JSON edits are needed.
VALHALLA_CONFIG, VALHALLA_BIN, VALHALLA_DATA_ROOT, and
VALHALLA_RUNTIME_ROOT can override the defaults. Runit owns startup, crash restarts, and shutdown; no dedicated terminal is needed.
Rotating logs for every supervised service are available under
~/.local/state/openroadcode/log/<service>/current.
The service-manager core start/stop operations include Valhalla.
For foreground debugging only, stop the supervised service before running
./scripts/runtime/start_valhalla.sh.
Verify the service with:
curl http://127.0.0.1:8002/status
ADS-B / tar1090 simulation
The Termux application profile uses the ADS-B producer source simulation. This keeps presentation testing independent of RTL-SDR hardware and Linux readsb/systemd service management.
Install the tar1090 presentation files once:
cd ~/src/OpenRoadCode
./development/termux/setup_tar1090.sh
The setup uses the tar1090 revision pinned in
scripts/installers/toolchain.lock; set TAR1090_REF only for an intentional
test of another revision.
After scripts/runit/install_termux_services.sh has installed the service, openroadcode-adsb owns the local tar1090 web server on port 8081.
Native map and route presentation
The native MapLibre renderer uses offline vector tiles under ~/.local/share/openroadcode/maps/vector/openroadcode.mbtiles. Map commands are published on the OpenRoadCode message bus using the map.command topic. Position telemetry and camera control remain separate so a manual pan does not move the vehicle marker.
The active navigation/map path is:
flowchart TD
location["Android location"] --> bridge["Android sensor bridge :8766"] --> source["AndroidPositionSource"]
source --> nav["Navigation service"] --> telemetry["Normalized position telemetry"] --> broker["OpenRoadCode ZeroMQ broker"]
broker --> ui["ORC UI map-follow camera"]
broker --> client["MapRendererClient"] --> command["map.command"] --> renderer["Native MapLibre renderer<br/>offline map + vehicle marker / route"]
classDef orcApp fill:#dbeafe,stroke:#2563eb,color:#172554;
classDef orcService fill:#ede9fe,stroke:#7c3aed,color:#2e1065;
classDef orcController fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef orcMessage fill:#ffedd5,stroke:#ea580c,color:#7c2d12;
classDef orcAdapter fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
classDef orcExternal fill:#f3f4f6,stroke:#6b7280,color:#1f2937;
class location,bridge,renderer orcExternal;
class source,client orcAdapter;
class nav orcService;
class telemetry,broker,command orcMessage;
class ui orcApp;
Route planning uses the local Valhalla HTTP service on port 8002. The route-to-map component test can use the real Valhalla service while recording renderer commands:
python -m services.navigation.component_test.route_to_map_e2e_cli \
--external-valhalla
To publish the resulting route to a running renderer through the normal broker path, add --external-renderer.
Run ORC UI
Start Termux:X11/XFCE first. When X11 is managed by runit, do not start a duplicate server on the same display.
Then launch the ORC UI from a Termux/X11 shell with the broker and navigation service already running:
cd ~/src/OpenRoadCode
export DISPLAY=:1
export CARUI_FULLSCREEN=0
export CARUI_GEOMETRY=1024x600
python -m apps.orcUi
The current ORC UI navigation map supports shared camera state between Home and Navigation views, follow/recenter behavior, screen-relative panning, route overlays, live vehicle position, and focused POI categories. The Games panel can launch installed native/hosted Linux games into its X11 content host. Browser-backed launchers use the selected X11 display and Chromium is started with --password-store=basic.
Navigation data
The runtime target pulls validated map/routing data from a map-build machine. The map-build machine publishes a validated dataset; the target decides when to update itself. The Termux-native target stores deployed navigation data under ~/.local/share/openroadcode. Use development/termux/pull_navigation_data.sh for the Android/Termux path where applicable.
Vector-map content and routing data are generated from source datasets and should not be assumed to contain every real-world business. UI POI highlighting operates on features present in the deployed vector tiles.
Test notes
The broad Python suite runs under Termux with platform-specific hardware tests skipped when their Linux-only dependencies are unavailable. Component tests supplement automated tests where real hardware, native services, X11, Android integration, or game packages are required.
Updating an existing map renderer
Close ORC, then update the native renderer without rebuilding MapLibre or Valhalla:
cd ~/src/OpenRoadCode
git switch android-linux-food-apps
git pull --ff-only origin android-linux-food-apps
./development/termux/build_navigation_stack.sh --renderer-only
./runOrcUi
The renderer-only option installs renderer build dependencies, including libspdlog for
structured logging, before compiling. It requires the existing
apps/map_renderer/build-termux directory configured by
build_navigation_stack.sh. Compilation must succeed before the installed
executable is replaced. No map-data download is needed.