Thanks for helping build OpenRoadCode!
This project lives at the intersection of cars, radios, embedded Linux, hardware hacking, and software experimentation. You do not need to be an expert in all—or even most—of those things to contribute. Curiosity, care, and a willingness to test your work will get you a long way.
Whether you are fixing a typo, adding support for a sensor, improving the touchscreen UI, or arriving with an alarming collection of SDR equipment, you are welcome here.
Visit openroadcode.org for the project page, and use the GitHub repository for code, issues, and pull requests.
OpenRoadCode interacts with vehicles, electrical systems, radios, Linux services, and physical hardware. Please experiment thoughtfully.
Safety notes are not meant to drain the fun out of the project. They help make sure everyone gets to keep having fun afterward.
Contributions of all sizes are useful. Good places to start include:
For a substantial feature or architectural change, open an issue before investing heavily in an implementation. Explain the problem, the proposed direction, affected hardware or services, and how the result could be tested. That gives everyone a chance to compare maps before the convoy sets off.
Before introducing a new abstraction, subsystem, or integration, search the repository for prior art.
ORC's Law: OpenRoadCode has an unusual tendency to contain part of the thing you are about to build.
ORC's Law does not imply that the feature is complete, correct, documented, connected, or even remembered by its original author. Existing work may be an interface, an abandoned experiment, a test utility, an implementation that was never wired into the application, or simply enough code to change the right design direction.
Treat repository archaeology as part of design work. Search related interfaces, controllers, services, protocols, hardware adapters, tests, configuration, and documentation before creating a new boundary. Reuse or extend an existing abstraction when it already expresses the responsibility cleanly; do not preserve old code merely to satisfy the law.
In less formal terms:
Search first. Design second. Implement third. Discover Mark wrote half of it six months ago.
Copy the clone URL from the GitHub repository, then enter the project directory:
The installer supports Raspberry Pi 4, Raspberry Pi 5, and Debian/Ubuntu development hosts. Preview the development setup before changing your system:
Remove --show-plan when you are ready to install. Read the plan first: installers may add system packages, configure services, or create a Python environment.
Installation targets describe platforms, features describe optional capabilities, and concrete devices are configured separately. Avoid adding a specific sensor, adapter, radio, or service to every target merely because it is present in one reference vehicle.
Use --all-features when a development or reference system needs every capability compatible with its selected target. Optional services and concrete device configuration remain separate by design.
When an installer has created the project environment, activate it with:
Many parts of the project can be developed without vehicle hardware. Prefer mock, stub, simulation, or unconfigured implementations when working on application logic at a desk. Your laptop should not need to believe it is a Jeep.
OpenRoadCode separates reusable domain behavior, process ownership, messaging, and presentation.
Commands and requested behavior use controller or request-handler interfaces:
Continuously changing public telemetry is distributed through producer services and the message bus:
In practical terms:
apps/ owns user-facing applications, runtime assembly, and app-specific state.services/ owns long-running domain producers, process lifecycle, runtime composition, and acknowledged command endpoints.controllers/ exposes reusable domain behavior, policies, and hardware-independent processing.messaging/ owns public message contracts, encoding/decoding, dispatch, and transports.frontends/ owns toolkit-specific reusable presentation.ui/ owns toolkit-independent presentation contracts.common/ owns neutral cross-cutting helpers such as shared telemetry state and unit conversion.hardware_io/ isolates device-specific access.protocols/ handles communication formats, device protocols, and remote APIs.config/ holds runtime and hardware configuration.A producer service owns its physical device or simulation source, domain processing, and publication lifecycle. Applications should subscribe to public telemetry instead of creating their own competing GPS, IMU, or OBD-II hardware instances just to display state.
Do not confuse services/ with messaging/. A service owns and runs a domain capability. Messaging transports and defines public messages. A ZeroMQ socket does not become a service merely because it has ambitions.
For new public telemetry, use SI units and perform imperial/metric conversion only at the presentation boundary. Shared conversions live in common.units; do not duplicate conversion constants in each frontend. Existing internal APIs with explicitly named non-SI units must be converted at a documented boundary rather than silently reinterpreted.
Use PUB/SUB for continuously changing state that may have multiple consumers. Use request/reply when an operation needs acknowledgement or an error response; navigation calibration, heading reset, and route calculation are examples. Do not turn a command into telemetry merely because ZeroMQ happens to be nearby.
Simulation belongs at the producer-service input so simulated and physical sources exercise the same downstream contract and consumers. Runtime service composition belongs in runtime configuration, not in individual UI applications.
A useful rule when choosing a boundary is:
For navigation specifically, keep these responsibilities separate:
RouteResult;Architecture references:
Avoid abstractions that do not provide a useful boundary, test seam, or interchangeable implementation. Software already has enough ceremonial ribbon-cutting.
Define or extend toolkit-independent view/state and semantic request contracts under ui/ before wiring new UI behavior. Use immutable snapshots and normalized SI values; frontends own display units and formatting. Widgets implement view contracts and emit requests through handler contracts. They must not construct or call concrete controllers, providers, service clients, or transports, or inspect backend private state.
Controllers own workers, caching, retries, playback, and stale callback rejection. They must not import GUI frameworks or frontends. Composition roots construct dependencies, bind contracts, and own cleanup. Keep process and transport access inside injected adapters. Test hide/close, canceled work, and stale completions alongside normal behavior. UI contracts remain in this repository; do not add an independently installable UI distribution.
Run the complete repository gate before declaring a feature complete:
It runs Ruff, module size, project-wide and weather UI boundary checks, Doxygen contract documentation, Mermaid legends, and unit/integration tests. CI also checks documentation, browser audio, shell syntax, runtime state, whitespace, and logging. Record unavailable hardware/service validation separately from automated results; a portable subset is not a full-suite pass.
scripts/ui_boundary_exceptions.json lists exact legacy dependencies, not permission for new violations. Do not expand or regenerate it to pass a check. Remove entries when their dependencies are removed; stale entries fail the gate. See UI contracts and migration inventory and the repository's AGENTS.md instructions.
Update the affected READMEs and IDDs in the same change. Review renderer restart, theme changes, and subscription/worker cleanup on the target device when they affect native map lifecycle. Weather provider probes require live network access; mocked data does not establish live provider or GPU behavior.
Tests live beside the code they exercise. OpenRoadCode does not use one giant test attic where everything gets tossed and forgotten.
Each package may contain one or more of these directories:
unit_test/ contains fast, isolated, automated tests. Replace hardware, processes, clocks, and remote services with fakes or mocks.integration_test/ contains automated tests connecting real software components. These tests must run unattended and clean up their resources.component_test/ contains manual tools for hardware, interactive input, external applications, credentials, services, or end-to-end environment checks that CI cannot reliably provide.Use snake_case filenames:
Files discovered by the automated runner must start with test_. Manual component programs should normally end with _cli.py so they cannot be mistaken for unattended tests.
A component test should state its environment assumptions. If it requires Raspberry Pi hardware, gpsd, Valhalla, MapLibre, a display server, credentials, or another external dependency, document that rather than allowing an import failure to serve as the setup guide.
Run the same commands used by continuous integration:
Run both categories, in that order, with:
Component tests are run individually because their requirements vary. In a pull request, document the command, connected hardware or service, expected behavior, and result.
Public methods declared in *_if.py modules must document:
@paramNone result with @return or @retvalRun the contract check with:
Docstrings and comments should explain intent, constraints, or surprising behavior. They do not need to narrate obvious Python one line at a time.
Use Mermaid for architecture, topology, dependency, sequence, and data-flow diagrams in Markdown. Use the OpenRoadCode layer palette established in docs/architecture.md when the nodes map to those layers. Keep literal terminal output, directory trees, filesystem paths, equations/transforms, and protocol payload examples as normal fenced text or code rather than forcing them into diagrams.
When adding or changing a public message contract:
docs/idd/ or docs/messaging/;messaging/README.md and the owning service README when discovery or runtime behavior changes.New normalized public telemetry contracts use SI units unless the IDD explicitly documents a justified exception. Do not expose a presentation preference such as miles versus kilometers as two competing wire contracts.
Public request/reply commands are interfaces too. Document request fields, response fields, failure behavior, units, and ownership in an IDD when they cross a process boundary.
When adding or changing a fixed IP port, listener, or explicitly configured network endpoint, update the Ethernet IDD in the same change. Record the port owner, bind/connect address, transport, application protocol, and any platform or concurrency constraints. Do not introduce an undocumented default port and leave future contributors to discover it through ss, optimism, and increasingly creative language.
snake_case for modules, functions, methods, and variables.Match the surrounding code where the project does not yet enforce a rule automatically. Consistency beats introducing a second excellent convention.
Repository-owned source files use SPDX headers to record their license and copyright holders. New Python and shell files should begin with:
Use the comment syntax appropriate for other languages. Keep a shebang first, and keep a Python encoding declaration on its required first or second line.
When making a copyrightable contribution to an existing file, add your own SPDX-FileCopyrightText line without removing the existing notices. Trivial changes such as typo fixes or formatting do not normally require an additional copyright line. Preserve notices in third-party or adapted code, and identify its license accurately rather than applying the project's header automatically.
A useful hardware contribution usually includes:
hardware_io/.Include relevant device models, addresses, pins, connection types, Linux permissions, and service requirements. Never assume another contributor has your exact Raspberry Pi, adapter revision, wiring harness, or especially cooperative Bluetooth dongle.
Some areas are experimental or awaiting migration. Do not build new features on a legacy module merely because it happens to have the most adventurous filename.
When touching deprecated code, make the intended direction clear. A change may port it to current interfaces, isolate it under a clearly named legacy or deprecated area, or remove it when there is no remaining use. Call out that decision in the pull request.
Before opening a pull request:
python scripts/run_tests.py unit.python scripts/run_tests.py integration when the environment permits.python scripts/check_doxygen_contracts.py after changing interfaces.In the pull request description, tell us:
Screenshots are welcome for visual changes. Logs and command output are useful for hardware and protocol changes, provided secrets and private data have been removed.
Assume good intent, explain unfamiliar concepts, and leave room for people to learn. Helpful reviews discuss the code and its tradeoffs rather than the person who wrote it.
OpenRoadCode is an ambitious experiment assembled from many disciplines. No one arrives knowing all of them. Share what you know, ask when you do not, and help make the next contributor's first drive a little smoother.