Back in the early 2010s, when Bluetooth meant classic and Python 2 was still the norm, I built a little experiment: a local Bluetooth scanner that picked up nearby devices and sent their data to a web server.

The idea was simple: like a digital radar for your surroundings. Each time the scanner found a phone, speaker, or headset, it would POST that device’s address and name to a web endpoint. The server would respond:

That response decided whether to log it, greet the user, or just nod silently in the background.

The original project was barely over a hundred lines and relied solely on PyBluez. Simple, a bit clunky, but it worked. It even printed each new device in the terminal, a small victory in the early days of DIY IoT.

👉 Original project

Fast-Forward 12 Years

Now, over a decade later, I decided to bring it back, this time in modern Python, with full BLE support, a web dashboard, and proper persistence.

👉 New version

This rewrite isn’t just a touch-up. It’s a complete re-architecture designed to feel like a small but capable IoT edge service, the kind of thing that could live quietly on a Raspberry Pi, monitoring its environment and reporting back when something new enters range.

⚙️

What’s New

🌀 Dual Scanning
The scanner now uses Bleak for Bluetooth Low Energy and PyBluez for classic devices, both running in parallel. Each cycle merges the results into a unified stream with deduplication and metadata tagging.

🌐 Live Web UI
Instead of console text, there’s now a small Flask + SSE dashboard at http://localhost:8080.
You get a sortable device table, live status indicators, and a purge button to clear results. It’s surprisingly satisfying watching new devices pop into view in real time.

💾 Persistent Device Store
Each discovery is logged in a local SQLite database, meaning your data survives restarts and can be analyzed later.

📡 Smart Reporting
Point the app to any webhook via REPORT_URL, and it will send device batches with retry logic, deduplication, and back-off timing.
No webhook? No problem! The queue simply idles until you’re ready to connect it to something.

📊 Eddystone Support
If a BLE beacon advertises a URL frame, the app automatically decodes it, letting you see what local beacons around you are broadcasting.

🔧 Cleaner Management
Everything, adapters, queues, and configurations, is viewable right from the UI. You can even see when Bluetooth is disabled or when an adapter is missing.

🪛

Under the Hood

The project is now organized like a full Python service:

src/
 └── combined_scanner.py     # Unified BLE + Classic scanner
templates/
 └── index.html              # Flask web interface
tests/
 ├── test_db_and_helpers.py
 ├── test_endpoints.py
 └── test_reporting.py
run_scanner.py               # Cross-platform launcher
run_scanner.bat / .sh        # Startup scripts

With linting, pre-commit hooks, and automated testing, it’s a proper little modern app, not the hacked-together script of old.

Technical Notes

Dual-Mode Scanning:
The core loop spawns two asynchronous coroutines:

Device Persistence:
Each device entry contains its MAC address, name, RSSI (for BLE), and timestamps. If the same address reappears, it’s updated rather than duplicated.

Web Interface:
Flask serves the HTML dashboard, and Server-Sent Events (SSE) stream live updates to the browser — no refreshes required.
A small REST API powers the “Purge” and “Config” buttons.

Reporting Logic:
When REPORT_URL is defined, the scanner batches up new discoveries and posts them as JSON. Each batch includes the local sensor ID (THIS_SENSOR) so multiple scanners can feed the same backend.
Retries and exponential delays make the system resilient against temporary network drops.

Eddystone Decoding:
BLE packets that start with the Eddystone prefix are parsed for their embedded URLs. It’s a neat way to find tiny “broadcast web pages” from nearby beacons.

Testing & CI:
Everything is covered by a pytest suite and validated through GitHub Actions on multiple OS environments.
Linting, formatting, and import sorting are handled automatically via pre-commit.

Looking Back

There’s something satisfying about modernizing old code, seeing the evolution not just of Python and Bluetooth APIs, but of your own coding philosophy.

In 2013, this was a curious little script running on a laptop.
In 2025, it’s a self-contained IoT service with proper architecture, tests, and live reporting, and it’s still doing the same thing: quietly listening to the invisible world around it.

Sometimes, that’s what progress looks like. The same idea, refined and rebuilt with a decade of experience behind it.