Skip to content

Repository files navigation

WiFi Mapping

Measures WiFi signal strength at chosen spots and renders one heatmap per network on top of a floor plan or aerial view.

Two front-ends share the same core (src/):

  • web (server.py + web/): Flask plus a plain HTML/CSS/JS page. This is the main interface; the survey works by clicking on the plan.
  • command line (main.py): the same features in a text menu, with the survey running in a matplotlib window.

The interface ships in English and French, and any other language can be added without touching the code — see Translations.

Author: Jakub WERLINSKI.

One map per tracked network, each labelled with the model that produced it and its cross-validation error:

Grid of rendered heatmaps, one per network, over the sample floor plan

Requirements

Surveying requires Linux. Readings come from sudo iw, which has no equivalent on Windows or macOS. Everything else — rendering maps, the web interface, statistics, exports — is plain Python and runs anywhere; opening an existing survey and generating its heatmaps works on any platform. Off Linux the scanner says so plainly instead of failing in a confusing way.

On Windows, WSL is the usual way round it, provided the distribution can reach the wireless adapter — WSL2 does not pass physical network devices through by default.

  • Python 3 with numpy, matplotlib, scipy, flask, pillow

  • iw for scanning (sudo apt install iw)

  • passwordless sudo for iw, otherwise every scan fails:

    # in sudo visudo
    youruser ALL=(ALL) NOPASSWD: /usr/sbin/iw
    

    Scoping the rule to iw alone is enough; the readiness check probes that exact command rather than a placeholder.

On Kali/Debian the Python packages come from apt:

sudo apt install python3-numpy python3-matplotlib python3-scipy \
                 python3-flask python3-pil

Usage

./run_web.sh        # then open http://127.0.0.1:5000

Pick the WiFi interface under Settings, choose the networks to follow under Networks, then click your position on the plan under Survey — each click triggers a scan and records a point. Heatmaps renders the maps.

Survey numbers every point as it lands on the plan, and the side panel breaks down what the last scan picked up:

Survey view: numbered points on the floor plan, with the last scan listed beside it

Collected points stay in the browser until you press Save. Undo last point and Clear all work on that pending set, so a mistaken click costs nothing.

Overview sums up the survey once it is saved — how many points, which network comes out strongest, and the spread of each one:

Overview: stat panels, per-network gauges and a coverage table

Command line version: ./run.sh.

Translations

The active language is stored in wifi_config.json and can be changed from the language selector in the top bar, or from entry 8 of the command line menu. Both front-ends and the generated map labels follow the setting.

Catalogues live in locales/<code>.json as flat key/value pairs. Adding a language means copying locales/en.json, translating the values, and setting language.name to the label shown in the picker — the file is picked up automatically. Missing keys fall back to English, so a partial translation degrades gracefully instead of breaking a screen.

Method

Each click runs iw dev <interface> scan and keeps the strongest RSSI seen for every tracked network. Positions are stored as relative coordinates in [0,1], which keeps the survey independent of the image size.

The heatmap interpolates those readings with radial basis functions (scipy.interpolate.RBFInterpolator), with two safeguards:

Automatic calibration. Kernel and smoothing are chosen per network by leave-one-out cross-validation over two kernels and nine smoothing levels. Too little smoothing makes the surface oscillate between readings that are noisy by nature; too much flattens it onto the mean. Each map's title reports the chosen model and its RMSE, so the result can be judged rather than trusted blindly.

Faded extrapolation. Opacity falls off with distance to the nearest reading, scaled from the median spacing between neighbouring points. Without it the map would paint solid colour over areas that were never visited.

A network needs at least 3 points to be mapped, and at least 5 for cross-validation to mean anything. Since the signal varies by several dB at a fixed spot, spreading points across the whole area beats walking a single line.

Layout

server.py        JSON API and static file serving
web/             page, styles and browser logic
main.py          command line interface
locales/         translation catalogues
src/config.py    settings, persisted as JSON
src/i18n.py      catalogue loading and lookup
src/scanner.py   network scanning through iw
src/collector.py interactive survey (matplotlib)
src/heatmap_generator.py  interpolation and rendering
src/utils.py     reading, writing and exporting measurements
data/            plan.png and mesures.json
docs/            screenshots used by this file
tools/           sample floor plan generator
output/          rendered heatmaps, thumbnails under .thumbs/

Settings

wifi_config.json is created on first save and can be edited by hand. It is not tracked by git, being specific to each machine; copy wifi_config.example.json over it to start from a documented set of keys.

The keys that matter day to day are exposed in the Settings view; the rest of the table below is only reachable by editing the file:

Settings: interface picker, file paths and rendering sliders

The survey shipped in data/ uses neutral network names, and data/plan.png is a drawn floor plan rather than a real building: network names and a picture of the place together would pinpoint a home and name its neighbours. Rebuild it with python3 tools/make_sample_plan.py.

Working with real data

Anything matching data/*.local.* is ignored by git, so real material can sit next to the sample without ever being committed:

data/plan.local.png      your own floor plan or aerial view
data/mesures.local.json  a survey holding real network names

Point both settings at them to switch over, and back to the defaults to share the project:

{
  "plan_path": "data/plan.local.png",
  "measurements_path": "data/mesures.local.json"
}

Change both together. Surveying against a real plan while measurements_path still points at data/mesures.json would write real SSIDs straight into the tracked sample.

Rendered maps land in output/, which is ignored as well, so they never reach the repository whichever data you survey.

Key Default Purpose
ssids [] networks followed during a survey
wifi_interface wlan0 interface used for scanning
language en interface language, matching a file in locales/
plan_path data/plan.png background image
measurements_path data/mesures.json survey file
output_dir output where maps are written
scan_timeout 15 longest a scan may take, in seconds
heatmap_dpi 150 resolution of the rendered PNG
heatmap_resolution 400 interpolation grid size
heatmap_alpha 0.55 colour opacity over measured areas
rbf_auto_tune true calibrate by cross-validation
rbf_smoothing 1.0 fixed smoothing, used when rbf_auto_tune is false
fade_extrapolation true fade out unmeasured areas
fade_factor 2.5 how far the fade reaches; lower tightens the colour

API

Method Route Purpose
GET /api/config current settings and available languages
POST /api/config interface, language, timeout, DPI, grid size
GET /api/interfaces detected WiFi interfaces
POST /api/ssids add one or more networks
DELETE /api/ssids/<ssid> stop following a network
POST /api/scan run a scan and return the RSSI values
GET/POST /api/measurements read / save the survey
GET/POST /api/heatmaps list / render the maps
GET /heatmap/<name> one map; ?w=560 returns a thumbnail
GET /locales/<code>.json a translation catalogue
GET /api/export/*.csv CSV export

Measurement format

data/mesures.json holds positions normalised to [0,1] and RSSI in dBm.

{
  "ssids": ["Network-07"],
  "plan": "plan.png",
  "plan_w": 489,
  "plan_h": 449,
  "mesures": [
    {"x": 0.55, "y": 0.34, "signaux": {"Network-07": -74.0}}
  ]
}

Known limits

The Flask development server is meant for local use on 127.0.0.1; it is not built to be exposed on a network.

The interpolation knows nothing about the geometry of the place — neither walls nor the logarithmic fall-off with distance. On a sparse survey it barely beats predicting the network average, which the RMSE printed on each map makes plain.

Surveying a network means listening to it. Scan the places you are entitled to scan, and keep in mind that the network names a survey records are those of your neighbours as much as your own — which is why the sample here carries none of them.

License

MIT — do what you like with it, keep the copyright notice.

About

Measures WiFi signal strength at chosen spots and renders one heatmap per network on top of a floor plan or aerial view.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages