A Flipper Zero app that plays "hot and cold" with wireless signals: pick a radio type, optionally pick a specific device/frequency/channel, then hit GO and the screen shows a big live number that climbs as you get closer and falls as you move away - like a metal detector, but for WiFi, Bluetooth, Sub-GHz, Infrared, RFID, and NFC.
WiFi Ch 6 <- header: type + config
------------------------------
-47 <- big number: dBm, or a 0-100% score
[######----] <- proximity bar
HOT! <- hot/cold word
The Flipper Zero has its own radios for Bluetooth, Sub-GHz, Infrared, 125kHz RFID, and NFC - those run entirely on-device using the Flipper's built-in hardware. It has no WiFi radio, though, so WiFi tracking needs an ESP32 Marauder board wired to the Flipper's GPIO header, running the stock, unmodified ESP32Marauder firmware
-
there's no companion sketch of ours to flash. The Flipper drives it entirely through Marauder's own serial CLI (
scanall,list -a,list -c,foxhunt -w,stopscan...) - seesrc/helpers/esp32_uart.cfor the client andesp32_firmware/README.mdfor exactly which commands are used and why, including a couple of real limitations that fall out of Marauder's own CLI: -
No BSSID over serial for access points, so no MAC/vendor lookup for them - APs are identified by SSID + Marauder's own list index instead.
-
Stations (clients) never get an RSSI from Marauder at all -
list -conly ever prints a MAC, no signal strength, for any station. That's a hard protocol limit, not a gap on our side. So WiFi discovery shows both access points and clients (their real MAC, and which AP they're talking to), but selecting a client for the GO screen tracks its parent access point as a proxy instead - there's nothing else to track it with. -
Channel locking is a real lock for tracking, not just a hint. The app sends
channel -s <n>itself beforefoxhunt -w, using the AP's actual discovered channel - it doesn't rely on Marauder's own fox-hunt channel lock, since that logic (WiFiScan::runFoxHunt()) only runs inside#ifdef HAS_SCREENcode on the ESP32 side and may be a no-op on headless board builds. It also doesn't trust Marauder's own(selected)marker inlist -ato find the tracked AP's row back - some boards never seem to actually set it - so the tracked AP is matched by list index instead, which only depends on Marauder's sniffer still updating that row's RSSI.
Bluetooth does not go through the ESP32 - most Marauder boards are
built on an ESP32-S2, which is WiFi-only silicon with no Bluetooth radio
at all, so there's no blescan to drive there. Instead,
src/helpers/ble_scan.c uses the Flipper's own
radio in raw RX test mode (furi_hal_bt_start_rx/furi_hal_bt_get_rssi,
the same primitive used for RF certification) to read undecoded BLE
channel activity - like the Sub-GHz mode, it tells you "how much signal is
on this channel", not which specific device it's from, so Bluetooth has
no device picker. This only works at all if the Flipper's BLE radio
coprocessor has the Full stack loaded
(furi_hal_bt_is_testing_supported()), since the Light stack most
Flippers ship with by default doesn't implement the HCI test commands
this needs; the app checks for this up front and says so plainly on the
GO screen rather than just sitting at a blank reading. Getting a real
BLE scan (attributing a reading to one specific
device) would need raw HCI access to the radio coprocessor, which stock
Flipper firmware doesn't expose to external apps at all - see the comment
at the top of ble_scan.c for what was actually checked before landing on
this raw-RX approach as the ceiling of what's reachable here.
| Type | Radio used | Extra hardware |
|---|---|---|
| WiFi | ESP32 Marauder's scanall/list -a/list -c/foxhunt -w |
ESP32 Marauder board, stock firmware |
| Bluetooth | Flipper's built-in radio, raw RX + RSSI (undecoded channel activity) | - |
| Sub-GHz | Flipper's built-in CC1101 | - |
| Infrared | Flipper's built-in IR receiver | - |
| RFID (125kHz) | Flipper's built-in LF coil | - |
| NFC (13.56MHz) | Flipper's built-in NFC front end | - |
-
Type - WiFi, Bluetooth, Sub-GHz, Infrared, RFID, or NFC.
-
Configure (rows shown depend on the type you picked):
- Device (WiFi only) - opens a live scan list mixing both access
points (
[AP] SSID, or "(hidden network)") and clients ([STA] MAC -> SSID, i.e. which network that device is talking to). Picking a client tracks its parent AP as a proxy - see the limitation above. Bluetooth has no device picker at all - see above. - Channel (WiFi and Bluetooth) - WiFi: a specific channel (1-14) or All to hop, best-effort (Marauder's own scan loop re-hops regardless). Bluetooth: one of the 3 BLE advertising channels (37/38/39) or All to hop - a real, hard restriction here, since the Flipper's own radio can only listen on one at a time.
- Frequency (Sub-GHz only) - a common ISM frequency, or All to sweep a preset list (300-915MHz) looking for activity.
- IR Mode (Infrared only) - Data (only count fully decoded remote-control protocol messages) or All (any raw IR pulses at all, decoded or not).
- NFC Mode (NFC only) - Reader (act as a reader and detect a passive tag/card held up to the Flipper - any protocol, ISO14443-3A/3B, ISO15693, FeliCa, ...) or Card (emulate a card so an active reader notices the Flipper - e.g. a phone sitting in a tap-to-pay UI). These detect genuinely different things and neither one covers the other: two readers pointed at each other don't see one another at all, so a phone will never show up in Reader mode.
- (RFID has no options here - it's inherently short range and just reports whether a tag is currently in range.)
- Min Signal - a threshold below which the reading is flagged as "too far" (dBm for WiFi/Bluetooth/Sub-GHz, a 0-100% closeness score for Infrared/RFID/NFC, since those don't have a real signal strength to read).
- Sound - On/Off. When on, the GO screen beeps once you're really close (not continuously - see below).
- GO - starts tracking with the above config.
- Device (WiFi only) - opens a live scan list mixing both access
points (
-
GO screen - continuously scans/sniffs and shows a big number (dBm or %), a proximity bar, a HOT/COLD word, an LED color tier (red/yellow/green), and - if Sound is on - a rising-pitch beep once you're in the top tier ("ON TOP OF IT!"), so a near-hit doesn't turn into a constant drone. WiFi re-requests a reading from the ESP32 once a second (faster floods Marauder's serial link) and displays anything the board pushes on its own the moment it arrives, so the number moves as fast as Marauder's fox-hunt reports it; everything else, including Bluetooth, updates every tick since it's all native radio reads. Press Back to stop and return to the config screen.
If Marauder's live fox-hunt stream goes quiet for 5 seconds, the app re-issues
foxhunt -wby itself - that stream is the only thing that makes an AP's RSSI actually change (seeesp32_firmware/README.md), and it can silently fail to start. After 5 seconds with no reading at all from any source the big number drops back to--rather than freezing on a stale value.
The app writes a running debug log to
/ext/apps_data/hot_cold/hot_cold.log on the SD card -
src/helpers/applog.c opens the file, writes one
line, and closes it again on every single call (slower than keeping a
handle open, but it means a crash a moment later doesn't lose whatever was
already logged - the point of the file is diagnosing crashes and stuck
states, so that trade-off is worth it). It's separate from the
system-wide FURI_LOG_* stream, which needs a serial terminal to capture;
this one you can just pull off the SD card.
It's truncated fresh on every app launch, and currently covers: every command sent to the ESP32, WiFi discovery/tracking decisions (including the fox-hunt retry logic), Bluetooth's raw RSSI reads for the first few ticks after each start, and every single call in and out of the NFC helper (the most crash-prone one) with its return value. If you hit a bug, reproduce it, then pull that file and it should make the cause obvious - or at least narrow it down to one specific call.
Note on WiFi tracking specifically. Individual received lines are
not logged any more. Writing to the SD card takes a file
open/write/close each time, and doing that per parsed line - on the UART
worker thread, while bytes are still streaming in at 115200 - was slow
enough to back up the receive buffer and drop the very lines it was
trying to capture. The GO screen emits one wifi poll: summary line per
second instead, built from counters kept on the UART side:
wifi poll: rssi=-62 has=1 age=180ms live=47(180ms ago) list=6 mismatch=0 unparsed=0 last=
liveclimbing - Marauder's fox-hunt push is working; the number on screen is genuinely live. This is the healthy case.live=0butlistclimbing -foxhunt -wnever took.list -ais answering, but an AP's RSSI there is write-once, so the displayed value will never change no matter how often it's polled. The app re-issuesfoxhunt -wevery 5s in this state; if it never recovers, suspect the board's Marauder build or the AP index.- both
0- nothing is coming back at all. Check wiring/baud, and thatesp32_uart_pingloggedOKat the top of the session. unparsedclimbing with alast=value - lines are arriving in a shape the parser doesn't recognize;last=is the most recent one.
Note on NFC, for the same reason and then some. The NFC callbacks are
hard real-time on the Card side - the listener has to answer a reader
inside the ISO14443-3A frame delay time, and an SD-card write there blows
straight through that budget, so the reader gives up and the Flipper is
never seen. Nothing is logged from those callbacks either; the GO screen
emits one nfc poll: line per second instead:
nfc poll: mode=0 detections=12 age=140ms field_off=0 rescans=11 last_event=0 last_proto=0
mode is 0 for Reader and 1 for Card. detections climbing means cards
(Reader) or readers (Card) are being seen. rescans is Reader-only and
counts scanner restarts, which only happen after a detection - so
detections=0 rescans=0 with a card physically on the antenna means the
scan is running but nothing is answering it.
application.fam Flipper app manifest (ufbt/fbt)
src/ Flipper app source (C)
hot_cold.c Entry point
hot_cold_i.h Shared app struct/types
scenes/ Scene manager: TypeSelect, Configure, DeviceScan, Go
views/go_view.c The big-number/proximity-bar canvas view
helpers/
wireless_type.h The 6 radio types + which fields each one needs
esp32_uart.c/.h Client for the ESP32 Marauder board's own serial CLI (WiFi only)
ble_scan.c/.h Native Bluetooth RSSI meter (furi_hal_bt raw RX)
subghz_scan.c/.h Native CC1101 RSSI sweep
infrared_scan.c/.h Native IR presence detector
rfid_scan.c/.h Native 125kHz RFID presence detector
nfc_scan.c/.h Native NFC presence detector
signal_source.c/.h Unifies all 6 into one poll() interface
applog.c/.h Crash-resilient debug log written to the SD card - see "Debugging" below
icons/hot_cold_10px.png App list icon
esp32_firmware/README.md Wiring + which Marauder CLI commands are used (no code - stock firmware)
scripts/install_to_sd.py Copies the built .fap onto the Flipper
Makefile Wires the above together
mac-address-vendor-database/, tools/build_vendor_db.py, and
vendor_db/ are leftovers from an earlier design that resolved MAC
addresses to vendor names for a device picker - WiFi APs never expose a
MAC over Marauder's serial protocol at all (see "How it works" above), so
nothing in the app calls into them anymore. Left in place in case WiFi
vendor lookup gets wired up later (Marauder's info -a <idx> does return
a BSSID, just not in the live discovery list this app polls), but safe to
delete if you don't need that.
You'll need:
- A Flipper Zero.
- An ESP32 Marauder board if you want WiFi tracking, flashed with the
stock ESP32Marauder
firmware (not something built from this repo) and wired to the Flipper's
GPIO header - see
esp32_firmware/README.mdfor pinout and flashing links. Not needed for Bluetooth/Sub-GHz/IR/RFID/NFC. - Python 3.9+.
make setup # pip install -r requirements.txt, then `ufbt update` to pull the Flipper SDK
make build # builds the .fapufbt build doesn't drop the .fap inside this project - it builds into
~/.ufbt/build/hot_cold.fap, a per-user cache shared across every ufbt app
you build. make install/make launch know where to find it; you only
need the path yourself if you're scripting around this repo.
The most reliable way to get the app onto the Flipper is to mount its SD card as a regular drive (pull the card and use a reader, or use qFlipper's file browser mount) and point the installer at it:
make install SD=E:\ # Windows drive letter
make install SD=/media/flipper-sd # Linux/macOS mount pointThat copies ~/.ufbt/build/hot_cold.fap -> <sd>/apps/GPIO/hot_cold.fap.
There's also an experimental USB-serial path (make install PORT=COM5,
via the optional pyflipper dependency) for when you don't want to pull
the SD card, and make launch to build-and-run directly over USB via
ufbt launch. Both need the app to already be built (make build); the
serial path is best-effort - if it fails, fall back to SD=.
There's nothing to build for it - flash the stock ESP32Marauder firmware
using its own releases/web flasher, then wire it up. See
esp32_firmware/README.md for links, pinout,
and exactly which Marauder CLI commands this app relies on.
Pushing to a branch named rel-* (e.g. rel-1.0.0) triggers
.github/workflows/release.yml, which
checks out the repo (submodules included - mac-address-vendor-database/
is a git submodule, just a reference to its own repo, not content copied
in here), runs make setup && make build on a clean Ubuntu runner, and
publishes/updates a GitHub Release named after the branch with
hot_cold.fap attached. Re-pushing to the same rel-* branch (or
re-running the workflow manually) replaces that release's asset rather
than failing on a duplicate tag.
This targets the standard ufbt/firmware SDK API (scene manager, view
dispatcher, furi_hal_serial, furi_hal_subghz, furi_hal_bt,
infrared_worker, lfrfid_worker, the low-level Nfc transport). It's
been built and verified clean (zero warnings, -Werror and all) against
SDK/API 87.1 (firmware 1.4.3) via ufbt build. Most of that surface is
stable across releases, but the Sub-GHz/Bluetooth/Infrared/RFID/NFC helper
files (src/helpers/subghz_scan.c, ble_scan.c, infrared_scan.c,
rfid_scan.c, nfc_scan.c) touch lower-level radio APIs that can
still shift between firmware versions and forks (official vs.
Momentum/Unleashed/etc) - if ufbt build reports an error there on your
firmware, check the exact symbol against your pinned SDK
(~/.ufbt/current/sdk_headers); the surrounding logic shouldn't need to
change, just the specific API names.
No license has been chosen yet for this project's own code. The bundled
mac-address-vendor-database/ is MIT-licensed (see its own LICENSE
file).