Skip to main content

A walker on a Montevideo street, carrying a laptop in a backpack, pressing a headset button and holding a notebook of street crossings.

Enodia

Wardriving on foot, without GPS. A talking Wi-Fi scanner, a paper notebook, and a map built from your own walks.

tests PyPI version Python versions License: MIT DeepWiki

Why Enodia?

The city is full of radios. Enodia uses them as landmarks.

Walk down a street and networks appear, signals grow stronger, fade and disappear. A Linux laptop in your backpack records that changing landscape and speaks through your headphones.

At each crossing, press the headset button: "Mark 1." "Mark 2." Write the crossing beside the mark number in a paper notebook. Without a button, write the crossing and the time you hear instead. The log keeps the moment. The notebook gives it a place.

Back home, Enodia joins the two: scans placed along your route, estimates of where access points stand, and fingerprints you can keep. Walk there again and a new scan can tell you where you are: between these crossings, this far along.

No GNSS receiver and no external geolocation database: position comes from a radio map you walked yourself. Everything else follows, and runs offline.

Enodia: a walked route, numbered street crossings and radio observations across four city blocks.

Get started

You need Linux, Python 3.10+, and a Wi-Fi interface managed by iwd, NetworkManager or wpa_supplicant, with permission to scan through D-Bus. For speech, install espeak-ng or SVOX Pico. Without a voice engine, Enodia prints its announcements.

Install it as a command with uv:

uv tool install enodia

uv tool upgrade enodia follows new releases, and uvx enodia --help runs it once without installing anything. --geocode --proxy needs the socks extra: uv tool install "enodia[socks]". From a clone of the repository, uv sync and then uv run enodia do the same as enodia below.

For a guided terminal menu covering the whole workflow:

enodia --assistant

Before walking, follow machine setup to keep the laptop awake with its lid closed and enable the headset button.

See it work

Try the bundled example, from a clone of the repository, without scanning any radios:

enodia --locate samples/rivera-query.jsonl \
  --map samples/rivera-map.jsonl --voice none
You are between "Rivera y Brito del Pino" and "Rivera y Simón Bolívar", 53% of the way
  around [-34.90311, -56.15836]
  2 walks agree, best similarity 89%, spread 0% of the stretch (1 m)
  from evidence last gathered 2026-09-17 17:02

The street names, coordinates and geometry are real OpenStreetMap data. The radio observations are synthetic. This demonstrates the workflow, not measured accuracy on a real walk. Inputs and reproduction commands.

Your first walk

1. Check the machine, then start a short outing.

enodia --preflight --log walk.jsonl
enodia --say-status --log walk.jsonl

Resolve preflight failures before leaving. Check that you hear announcements with the lid closed. Use a new log filename for each outing.

2. Mark the crossings.

Mark the first crossing, each crossing along the way, and the last one. Write each name beside its announced mark number, or beside the spoken time if you have no button. Two or three blocks are enough to try the whole process. Press Ctrl+C when you finish.

3. Transcribe the notebook into notebook.txt.

#1 Rivera y Avenida Doctor Francisco Soca
#2 Rivera y Brito del Pino

#1 means button mark 1. For timed notes, replace it with a time such as 17:45:00. Keep crossing names consistent between outings.

Coordinates are optional: append @ latitude, longitude after a crossing name. Without them, positions are fractions of the stretch between two crossings.

4. Reconcile the walk and add it to your map.

enodia --reconcile walk.jsonl notebook.txt
enodia --map-add walk.jsonl notebook.txt

Reconciliation places scans between crossings and estimates access-point positions. Adding the walk to the map keeps its scans as fingerprints.

On a later visit, ask where you are, once or as you walk:

enodia --locate
enodia --locate --watch

Go further

  • Draw the route. Add coordinates by hand or look up crossings with --geocode. Use --streets for street geometry and export with --csv, --geojson or --svg. Geocoding is the only command that accesses the internet, and it supports an explicit proxy.
  • Check the estimates. --check-pace compares inferred movement with the clock, --check-passes compares repeated passes, and --check-map holds out each outing in turn to test localisation.
  • Share an outing. --export-public produces a pseudonymised log and notebook. Review both before publishing. Stable fingerprints and route geometry can still identify a place. Publishing a walk says what the export promises and what it cannot.

See the CLI reference for command syntax and options.

Limits

Enodia is experimental. Field use so far covers one real outing. Its pace, access-point and localisation estimates are tested on synthetic observations, and their accuracy on real streets remains to be established.

Enodia has no GPS on purpose. The question is whether a self-built Wi-Fi map can give a position on its own, and a receiver in the loop answers it by making the radio a second opinion. Where GPS does belong is as ground truth: coordinates can be written into the notebook beside any crossing (@ -34.9066, -56.2001) before reconciling, and --check-pace then measures the estimates against them in metres.

The map covers places you have already walked and ages as routers move or disappear. A map recognising the same outing it was built from proves little. Access-point positions are estimates, and scans follow straight lines between crossings unless you supply street geometry.

Fresh Wi-Fi scans send probe requests. --preflight checks scan-address randomisation settings but does not change them. Logs and maps contain network identifiers and location information.

Documentation

  • Machine setup: the lid, the Wi-Fi daemon, the voice and headset permissions.
  • CLI reference: every flag, by the command you reach for it with.
  • File formats: the notebook you write and the log Enodia writes.
  • Methodology: positioning, fingerprints and what the self-checks measure.
  • Design notes: engineering decisions and the failure behind each one.
  • Publishing a walk: what --export-public promises, and the three things it cannot.
  • From Python: the same operations as function calls.

License

MIT.

Release files for enodia 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for enodia 0.2.0
File Size Uploaded
enodia-0.2.0.tar.gz 332.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for enodia 0.2.0
File Interpreter ABI Platform
enodia-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 480.1 kB

Release files / enodia-0.2.0.tar.gz

Download URL enodia-0.2.0.tar.gz
Size 332.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0e8ca5dfcf96eb7522e13288f38dbb0a331c6f6513ee400787cfeed526e2af8a
BLAKE2b-256 checksum
How to use checksums
8e39ea60249c46fdea0594861abda47072f39c6917c661535a5ae997db6fd481
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / enodia-0.2.0-py3-none-any.whl

Download URL enodia-0.2.0-py3-none-any.whl
Size 147.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c96902bffef4b7c30c60e8937f8f2c5f71c42cbd38f0a253305065a85a224360
BLAKE2b-256 checksum
How to use checksums
7fbd15c7d0f4f8c3b7d3c2e2b703c8d1c44c22286a1d1152c7f2bc3c1acc54b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page