Terminal tool that monitors internet reachability and WiFi signal strength once per second, with a live UI, JSON logging, and matplotlib graphs.
Project description
๐ถ WiFi Observer
A terminal tool that tells you โ every second โ whether your problem is the internet or your WiFi.
WiFi Observer continuously monitors two independent things โ internet reachability (via ping) and WiFi signal strength (in dBm) โ so you can tell why your connection is bad, not just that it is. It runs entirely in your terminal, keeps a live history in memory, logs every sample to JSON, and draws a graph of the session. Built with the Python standard library + Bash; the only optional dependency is matplotlib (for graphs), installed automatically on first run.
๐ Table of contents
- Why
- Demo
- Features
- Installation
- Usage
- Configuration
- How it works
- Output & file formats
- Reading the numbers
- Project structure
- Requirements
- Roadmap
- Contributing
- License
Why
A plain ping test lumps two different failures together. WiFi Observer separates them:
| WiFi signal | Internet | Likely culprit |
|---|---|---|
| Strong & stable | Down / lossy | ISP / router โ not your WiFi |
| Weak / unstable | Down / lossy | Your WiFi โ distance, walls, interference |
| Strong & stable | Fine | All good โ |
So instead of "the internet is slow," you get an answer you can act on.
Demo
โโ WiFi Observer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SSID: HomeNet-5G iface: wlan0
elapsed: 00:04:12 remaining: 00:05:48
log โ logs/2026-06-28/wifi-215205.jsonl
INTERNET โ UP target 8.8.8.8
latency : last 23.4 ms avg 26.1 min 18.0 max 142.0 ms
quality : packet loss 0.8% (2/250) jitter 5.2 ms
outages : 2 downtime 00:04 longest 00:03
WIFI SIGNAL -47 dBm (Excellent)
stability : STABLE (ยฑ1.8 dBm) min -52 max -44 avg -47.3 dBm
latency โโโโโโโโโยทโโโโ (ยท = a probe with no reply / outage)
signal โ
โ
โโโ
โ
โโ
โ
โโโ
โโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[g] save graph [q] quit (Ctrl+C also quits)
The exported graph stacks internet latency (outages marked in red) over WiFi signal strength (with quality reference lines), sharing a time axis.
๐ก Tip: drop a real screenshot/GIF here (e.g.
docs/demo.png) before publishing.
Features
- ๐ก Two metrics at once โ internet reachability and WiFi signal, sampled every second.
- ๐ฅ๏ธ Live terminal UI โ single self-refreshing screen with two labelled sections and sparklines.
- โฑ๏ธ Pick a duration on start โ 1 min / 10 min / 1 hr / until you close it, with a live countdown.
- โจ๏ธ Interactive keys โ
gsaves a graph instantly,qquits. - ๐ JSON logging โ every sample appended (JSON-lines), crash-safe.
- ๐ Graphs ("maps") โ matplotlib charts generated from inside the app and on exit.
- ๐๏ธ Date-organised output โ logs, summary, and graph grouped under
logs/YYYY-MM-DD/. - ๐ง Near-zero setup โ monitor is stdlib-only; matplotlib is auto-installed into a local
.venv. - ๐ Degrades gracefully โ no colours? no matplotlib? not a TTY? It still runs and tells you.
Installation
# 1. Clone
git clone https://github.com/biswanathamz/wifi-observer.git
cd wifi-observer
# 2. Run โ that's it.
./run.sh
There is no build step. The monitor uses only the Python 3 standard library
and the system ping. The first run bootstraps a local .venv and installs
matplotlib so graphs work (skip with ./run.sh --no-graph-setup).
Prefer to manage deps yourself?
pip install matplotliband runpython3 wifi_observer.pydirectly.
Install as a command (optional)
Install it as a proper CLI so wifi-observer is on your PATH:
pip install . # core only (stdlib monitor)
pip install '.[graph]' # + matplotlib, for the `g` graph feature
wifi-observer --help
Without the graph extra the monitor still runs fully; graphs are simply
skipped until matplotlib is present.
Usage
./run.sh # ping 8.8.8.8 every 1s
./run.sh -H 1.1.1.1 -i 2 # ping Cloudflare, every 2 seconds
./run.sh -H 192.168.1.1 # ping your router (tests the LAN link only)
./run.sh --no-color # plain text
./run.sh --help # full option list
On start, choose how long to run:
WiFi Observer โ how long should it run?
[1] 1 minute
[2] 10 minutes
[3] 1 hour
[4] Until I close it
Select 1-4 (default 4):
While running, the UI is interactive:
| Key | Action |
|---|---|
g |
Generate & save a graph (PNG) right now |
q |
Quit and print the session summary |
Ctrl+C |
Also quits cleanly |
A graph is also saved automatically on exit, and everything lands under
logs/<today>/.
Configuration
| Flag | Default | Description |
|---|---|---|
-H, --host |
8.8.8.8 |
Host/IP to ping (an IP avoids DNS; a hostname is resolved by ping). |
-i, --interval |
1.0 |
Seconds between samples. |
-t, --timeout |
1.0 |
Per-ping timeout (s) โ a slow/no reply counts as DOWN. |
-n, --history |
3600 |
Samples kept in memory for the live UI / sparklines. |
--log PATH |
auto | Custom JSON-lines log path (default logs/<date>/wifi-<time>.jsonl). |
--no-log |
off | Disable JSON logging. |
--no-color |
off | Plain text, no ANSI colours. |
--no-graph-setup |
off | (run.sh) Skip the matplotlib .venv bootstrap. |
How it works
Architecture
| File | Language | Role |
|---|---|---|
| run.sh | Bash | Checks deps, bootstraps matplotlib into .venv, launches the app. |
| wifi_observer.py | Python | Monitor loop, measurements, statistics, interactive UI, JSON logging. |
| wifi_observer_plot.py | Python | Builds the matplotlib figure; used by the app and standalone. |
The per-second cycle
- Ping the host once โ reachable? + latency.
- Read WiFi signal from
/proc/net/wirelessโ dBm + link quality. - Build a sample record and append it to the in-memory history and the JSON log.
- Update running statistics incrementally (counts, sums, sums-of-squares).
- Redraw the UI.
- Wait out the interval while polling the keyboard for
g/q.
How the ping works
No hand-rolled ICMP โ it runs the system ping:
ping -c 1 -W <timeout> <host>
-c 1 sends one echo request; -W is the reply timeout. UP/DOWN is decided from
ping's exit code (0 = reply); latency is parsed from the output (time=23.4 ms)
with a regex. Because the OS ping already holds the ICMP privilege, no sudo
is needed. The call is wrapped so a hang or failure can never crash the loop โ it
just records a DOWN sample.
How the WiFi signal is read
The kernel exposes live wireless stats as text in /proc/net/wireless. The program
parses the active interface's row for link quality (โ out of 70 โ a 0โ100 %
figure) and signal level in dBm. No external command, no privileges.
Statistics
Aggregates are maintained incrementally so each tick is O(1) no matter how long you run: uptime %, packet loss %, latency avg/min/max, jitter (latency std-dev), outage count/total/longest, and signal avg/min/max with std-dev (which drives the stability verdict).
History is a bounded deque (default last 3600 samples โ 1 h), so memory stays
capped โ while the JSON log keeps the full session on disk, and graphs are
built from that log.
Output & file formats
Each run writes into a date folder, all artifacts sharing one basename:
logs/
โโโ 2026-06-28/
โโโ wifi-215205.jsonl # one JSON object per sample
โโโ wifi-215205.summary.json # session aggregates
โโโ wifi-215205.png # the graph ("map")
Per-sample log line (.jsonl):
{"ts": 1782662252.80, "time": "2026-06-28T21:27:32.802", "internet_up": true,
"latency_ms": 47.8, "signal_dbm": -63.0, "signal_quality": 67.1, "ssid": "HomeNet-5G"}
Summary (.summary.json) โ start/end/duration, config, plus internet and
wifi_signal blocks (uptime %, packet loss, latency stats, outages, signal
avg/min/max/stddev/stability).
Re-plot any old log standalone:
python3 wifi_observer_plot.py # newest log โ <log>.png
python3 wifi_observer_plot.py logs/2026-06-28/wifi-215205.jsonl -o report.png --show
Reading the numbers
WiFi signal (dBm) โ higher (closer to 0) is better:
| dBm | Label | Meaning |
|---|---|---|
| โฅ -50 | Excellent | Right next to the router |
| -50 to -60 | Good | Solid, reliable |
| -60 to -67 | Fair | Usable; HD video fine |
| -67 to -75 | Weak | Drops/slowdowns likely |
| < -75 | Very weak | Often unusable |
Stability (from signal std-dev): STABLE < 3 dBm, FLUCTUATING < 6 dBm,
UNSTABLE โฅ 6 dBm. Latency / jitter / packet loss โ lower is better; high
jitter hurts calls/gaming even when average latency looks fine.
Project structure
wifi-observer/
โโโ doc/
โ โโโ SPEC.md # full specification
โโโ run.sh # Bash launcher (+ matplotlib venv bootstrap)
โโโ wifi_observer.py # Python monitor, UI, logging
โโโ wifi_observer_plot.py # matplotlib figure builder (UI + standalone)
โโโ LICENSE # MIT
โโโ README.md
โโโ .venv/ # auto-created on first run [git-ignored]
โโโ logs/ # date folders of output [git-ignored]
Requirements
- Linux with
python3(3.8+) andpingonPATH. The monitor itself is standard-library only. - WiFi signal needs
/proc/net/wireless(standard on Linux WiFi). SSID/ interface detection usesiwgetid/nmcliwhen present; otherwise it shows?. - matplotlib โ only for graphs;
run.shinstalls it into a local.venvon first run (needspython3-venv+ network once). Skip with--no-graph-setup.
Platform note: Linux-focused. The
pingflags and signal reading target Linux; macOS/Windows support would need contributions (see the roadmap).
Roadmap
Ideas and good first contributions:
- macOS / Windows support (
pingflags + signal reading) - Optional gateway probe to pinpoint LAN-vs-WAN faults
- DNS-resolution check (distinguish "internet down" from "DNS down")
- Desktop notification on outage start / recovery
- CSV export and a
--summary-onlyheadless mode - Configurable thresholds (signal labels, stability bands)
Found a bug or want a feature? Please open an issue.
Contributing
Contributions are welcome! ๐
- Fork the repo and create a branch:
git checkout -b feature/my-change. - Make your change. Keep the monitor standard-library only (matplotlib is
fine to import lazily inside
wifi_observer_plot.py). Match the existing style. - Test it โ run
./run.shand verify the live UI, logging, and a generated graph. For unreachable-host behaviour, try./run.sh -H 192.0.2.1(TEST-NET). - Commit with a clear message and open a Pull Request describing what and why.
Please keep changes focused, document new flags in the README, and be kind in code review. By contributing, you agree your work is licensed under the project's MIT license.
License
Released under the MIT License โ free to use, modify, and distribute with attribution. ยฉ 2026 biswanathamz.
โญ If this saved you a frustrating "is it me or the internet?" moment, consider starring the repo.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wifi_observer-0.1.0.tar.gz.
File metadata
- Download URL: wifi_observer-0.1.0.tar.gz
- Upload date:
- Size: 26.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bff924af99ec3a6556ea62552b101fb7fcb441aea3b2dd019a8d80a0557ae4d5
|
|
| MD5 |
ef93ce4deec5d865c82eaa9cd0d5ec50
|
|
| BLAKE2b-256 |
05180b4a62a65ed1f67b31d5a9c6c0cce63c850346e79f998ac22df5d9653bec
|
Provenance
The following attestation bundles were made for wifi_observer-0.1.0.tar.gz:
Publisher:
release.yml on biswanathamz/wifi-observer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wifi_observer-0.1.0.tar.gz -
Subject digest:
bff924af99ec3a6556ea62552b101fb7fcb441aea3b2dd019a8d80a0557ae4d5 - Sigstore transparency entry: 2017446992
- Sigstore integration time:
-
Permalink:
biswanathamz/wifi-observer@2513f829bae89fde07e15bccf8a605940b8bdb58 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/biswanathamz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2513f829bae89fde07e15bccf8a605940b8bdb58 -
Trigger Event:
release
-
Statement type:
File details
Details for the file wifi_observer-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wifi_observer-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f2835d00701203b4de756c3f10256b5578534d7fcbfc169b521cbbcb1c48b79
|
|
| MD5 |
fb2531b08ccfdbf2ef42bd00895e7422
|
|
| BLAKE2b-256 |
ec8f630b3bb7f51b651e642473b65e7ecf2c359df22413f33c0e3bf6889c2fd1
|
Provenance
The following attestation bundles were made for wifi_observer-0.1.0-py3-none-any.whl:
Publisher:
release.yml on biswanathamz/wifi-observer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wifi_observer-0.1.0-py3-none-any.whl -
Subject digest:
2f2835d00701203b4de756c3f10256b5578534d7fcbfc169b521cbbcb1c48b79 - Sigstore transparency entry: 2017447084
- Sigstore integration time:
-
Permalink:
biswanathamz/wifi-observer@2513f829bae89fde07e15bccf8a605940b8bdb58 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/biswanathamz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2513f829bae89fde07e15bccf8a605940b8bdb58 -
Trigger Event:
release
-
Statement type: