Reflects the changes in nite-oui-collection commit 1ed6333 (his
"Update MAC status and remove unnecessary sections" push earlier
today):
- 08:3a:88 picks up a "BLE Ring conflict" flag. The firmware
array is unchanged — promiscuous WiFi detection isn't affected
— but a header comment in main.cpp now calls out the caveat,
and the dataset markdown grows a new "Flagged but still tracked"
section so BLE consumers (oui-spy-unified-blue's flockyou mode)
know to expect occasional Ring-doorbell false positives on this
prefix.
- The "Demoted / low confidence" section in the dataset markdown
now also catalogues cc:cc:cc and 00:0c:e7 — both Removed in his
notes, but only f8:a2:d6 was documented here previously.
OUI count is unchanged (42); the firmware array doesn't need to
move. This is documentation-tracking so the next sync against
nite-oui-collection has clean priors to diff against.
flockyou.py: bind host and listen port both read from env
(FLOCKYOU_HOST / FLOCKYOU_PORT, defaulting to 0.0.0.0:5000). Was a
hardcoded :5000 before, which collides with other Flask projects on
the same dev box — now you can run side-by-side instances or bind
to localhost only.
index.html: ?demo=1 query param seeds the dashboard with eight
synthetic detections covering every visual state (live wildcard-probe,
addr2, addr1 with/without GPS, replay/FLASH with/without GPS, replay/
RAM × 2) and reveals the device command toolbar so the polished layout
is browseable without flashing real hardware. Purely a front-end mock
— the command buttons still hit the real /api/flock/* endpoints, so
clicking them in demo mode produces the actual "device not connected"
error toast (that error path is itself part of the UI worth seeing).
Self-contained: a single initDemoMode() function gated on the query
param, called once at the end of DOMContentLoaded.
api/README.md: both features documented under Quick start so they're
discoverable without reading the source.
The first cut wedged the five host-command buttons into the existing
Sniffer device-control-group on the same line as the Connect /
Disconnect buttons. On a real-width header that crammed nine controls
into one row and the visual hierarchy collapsed.
Lift them out into a dedicated sticky toolbar that lives between the
header and the main content:
- Sticky top:0 with a frosted dark gradient, a soft shadow, and the
same #8b5cf6 accent the header already uses
- Sections separated by gradient dividers: DEVICE label + live
"Online" pulse dot, then [Pull Prev] [Pull Live] [Status], a
flexible spacer, divider, [Clear Prev] [Clear Live]
- SVG-iconed buttons (no emojis): download glyph on Pull, refresh
glyph on Pull Live, info-circle on Status, trash on Clear. 14px
stroked, currentColor so they pick up each button's accent
- Colour-keyed by action class: purple/indigo on Pull Prev (matches
the FLASH replay badge), cyan on Pull Live (matches the RAM
replay badge), slate on Status, red on the destructive Clear pair
- Slides in / out via transform + opacity transition when
setFlockExtraControls() toggles .show — no more raw display flip
Replay badges and the cards they live on got the same treatment so the
toolbar and the data it pulls back are visually linked:
- Inline SVG icons on the badge itself (a chip-stack glyph for
FLASH, a waveform for RAM) with a tooltip naming the source
CMD: that produced them
- Gradient backgrounds + subtle outer glow keyed to the same
indigo / cyan as the toolbar buttons
- Cards carry .replay (and .replay.live-source for RAM dumps) so
they get a coloured left border and a faint horizontal gradient
wash instead of a flat tint
Live progress counter while a Pull is in flight. Every replay_detection
socket event bumps a small pill on the in-flight button — "Pulling 17"
becomes "Pulling 18" in real time as the device streams the array.
Reset on the POST response or an error. Single global state since the
firmware protocol serializes one CMD:* at a time, so at most one Pull
is ever live.
Toast got a refresh too: bigger padding, frosted blur, a soft outer
glow matching the variant colour (green / amber / red / indigo), and
a smoother translate-in animation. The Status button now joins fields
with " · " instead of spaces so the one-line status line reads cleanly
at any width.
Buttons in the toolbar use .fcb-btn-label spans for their text so the
busy/done state updates ("Pulling…", "Querying…", "Clearing…") swap
just the label, not the SVG icon. Mobile breakpoint at 800px hides the
dividers and the Online dot, shrinks the label font, and lets the bar
wrap.
api/README.md was significantly out of date — referenced a non-existent
`app.py`, predated the entire `/api/flock/*` command-protocol surface,
and described the dashboard as if it only ingested live JSON.
Full rewrite:
- Quick start with the actual entrypoint (python flockyou.py)
- Sniffer command bar — table of all five buttons, what each one
sends to the device, what comes back, and the canonical
post-wardrive workflow (Pull Prev → Clear Prev) both as button
clicks and as curl invocations
- How replay detections are handled — no GPS temporal matching,
no overwrite of fresher live data, FLASH / RAM badges, the
flock_replay_complete socket event with its ok / count / reason
payload
- Toast colour semantics (green / blue / yellow / red)
- Endpoint reference table covering Sniffer, GPS, detections,
export/import, OUI lookup
- Socket.IO event table — both directions, with the new
flock_replay_complete / flock_status / flock_clear / flock_error
events from this branch
- JSON wire-format samples for live, replay, status, version,
replay_complete, clear, error
- Troubleshooting section keyed on the new failure modes (no_file /
crc_mismatch / device timeout / two-tabs-pulling-at-once)
The root README's "Running Flask" subsection gains a "Dashboard
command bar" block describing the same five buttons + replay visual
treatment, with a cross-reference to api/README.md for the full
detail.
Wires the host command endpoints added in the previous commit
(/api/flock/{status,dump_prev,dump_live,clear_prev,clear_live}) into
index.html as a five-button row under the Sniffer connect controls.
Buttons stay hidden until the device is connected and are auto-revealed
on the loadStatus() poll, the connect-success callback, and any
flock_reconnected socket event; hidden again on flock_disconnected.
- Pull Prev / Pull Live → POST dump_{prev,live}, show "Pulling…"
busy state, toast the final count
- Status → GET /api/flock/status, toast a compact
line: det=N ouis=N prev=yes ch=N heap=KKB
up=Ns
- Clear Prev / Clear Live → POST clear_{prev,live} after a confirm()
dialog (destructive)
A top-right toast element (#flockToast) handles all command feedback
with success/warning/error/info colour bands; auto-dismisses after 4s
(6s for status, so the user has time to read).
Replay detections are visually distinguished in the detection cards:
a new "FLASH" or "RAM" badge (purple for SPIFFS, blue for live RAM)
appears next to the GPS tag, and the card itself gets a subtle left
border + tint via .detection-item.replay.
Socket events also wired: replay_detection pushes into both
detections[] and cumulativeDetections[]; flock_replay_complete /
flock_error trigger their own toasts so other browser tabs see the
result of a pull triggered elsewhere; flock_status / flock_clear are
logged only (the REST caller already gets toast feedback).
All button click handlers disable the other command buttons during a
request so a user can't fire two dumps in parallel against the same
serial port (which would interleave at the firmware end — the protocol
serializes one CMD: at a time).
Firmware (main.cpp): adds a line-based CMD:* protocol on the same
USB-CDC port that already streams live detection JSON, so Flask can pull
state without re-flashing:
- CMD:STATUS emits {"event":"status",...} with det count,
SPIFFS state, free heap, uptime, channel
- CMD:VERSION emits firmware identifier + compile-time constants
- CMD:DUMP_LIVE streams the in-RAM detection table as replay
JSON lines, then a replay_complete sentinel
- CMD:DUMP_PREV same, but reads /prev_session.json from SPIFFS
(parses the CRC envelope and the embedded array)
- CMD:CLEAR_LIVE wipes fyDet[] and dirties the autosave
- CMD:CLEAR_PREV deletes /prev_session.json and any /session.tmp
Implementation:
- Minimal string-aware JSON object reader (string-aware brace counter,
backslash handling) lifts entries from the SPIFFS array one at a
time without slurping the whole file
- jsonGetString / jsonGetInt field extractors over flat objects
- emitReplayDetection() reuses the existing Flask schema and adds
replay / replay_source / detection_count / device_first_ms /
device_last_ms so the host can tell historical from live
- serialCmdTick() runs once per loop() and only acts on completed
lines — non-blocking and safe alongside the live detection path
- dualPrintf buffer bumped 384 → 1024 B to fit the longer replay line
(and to remove a latent truncation risk on a long-SSID live line)
Flask (api/flockyou.py): turns the protocol into REST endpoints and
ingests replayed detections without confusing them with live ones:
- flock_reader now dispatches {"event":"status"|"version"|"clear"|
"replay_complete"|"error"} responses to threading.Event slots, and
routes {"replay":true,"detection_method":...} lines through a new
add_replay_detection_from_serial() that skips GPS temporal matching,
flags timestamp_source="device_replay", and merges into an existing
fresher live entry instead of overwriting it
- send_command(cmd, response_event_name, timeout) serializes one
command at a time and blocks until the matching event arrives
- new endpoints: /api/flock/{status,version,dump_prev,dump_live,
clear_prev,clear_live}
Verified: pio run completes clean (RAM 19.1%, flash 12.0%); flockyou.py
passes py_compile. README documents the protocol, the per-event shape,
and the canonical post-wardrive dump_prev → clear_prev workflow.
Brings the target OUI array up to parity with @NitekryDPaul's upstream
nite-oui-collection (April 2026):
- Adds 12 prefixes: 04:0d:84, f0:82:c0, 1c:34:f1, 38:5b:44, 94:34:69,
b4:e3:f9, b4:1e:52, 14:b5:cd, 94:2a:6f, f4:e2:c6, d4:11:d6, e0:0a:f6
- Demotes f8:a2:d6 — flagged as a Sony Media Player false positive
in his my_tested_flock.md notes, retained only as documentation in
the dataset's "Demoted / low confidence" section.
Active firmware count is now 42 (29 from @NitekryDPaul's original set,
12 April 2026 additions, 1 from Michael / DeFlockJoplin).
Also: replaces the stylised cyrillic researcher name with its decoded
form OrdoOuroborous and links his GitHub @nitekry, since the unicode
glyphs don't render reliably and made the credit hard to follow.
Adds Michael / DeFlockJoplin's high-precision detection method on top of
the NitekryDPaul baseline: a Flock camera is flagged when it transmits a
Probe Request (type=0 subtype=4) with a wildcard SSID IE (tag 0 len 0)
AND its addr2 matches the OUI list. Drive-test in Joplin: 11/12 cameras
caught with only 2 false positives.
- New AlertType ALERT_WILDCARD_PROBE, emitted as detection_method
'wifi_wildcard_probe' (high-precision class)
- Wildcard-probe hits suppress the addr2 broad alert for the same frame
to prevent double counting; non-probe OUI matches still emit as
'wifi_oui_addr2'
- IE parser returns tri-state (1=wildcard / 0=directed / -1=no SSID IE),
with FCS-trailer retry only on the -1 no-IE case
- addr1 receiver-side sleeper-catch and the optional addr3 + SSID paths
are unchanged — wildcard is purely additive
- 31st OUI 82:6b:f2 added to target_ouis[] and to the dataset doc; it's
the OUI of the 12th camera in Michael's drive-test that the original
30 didn't catch
- README explains the wildcard-probe method, credits Michael with a link
to github.com/DeflockJoplin/flock-you, and bumps Acknowledgments
Source: https://github.com/DeflockJoplin/flock-you
Known MACs that haven't been heard from in >30 s now refire the
ascending new-discovery chirp when they reappear. Shorter gaps (like
Flock burst-sleep cycles) still just resume the heartbeat beep-pair
without a chirp.
fyAddDetection now returns an 'outChirpWorthy' bool: true for
brand-new MACs and for rediscoveries past the threshold. Replaces
the old count==1 check, which only ever fired once per MAC per
session.
Only consider a target 'still around' if it was seen within the last
3 seconds (was 20 s). Heartbeat interval stays 10 s. Net effect: the
monotone beep-pair only fires while the device is actively in RF
range, stops almost immediately on departure.
Replaces the single beep-per-detection with two distinct patterns:
- New MAC (first sighting): two fast ascending beeps, 2000 -> 2800 Hz,
55 ms each with 25 ms gap
- Same MAC still active (last seen within 20 s): two monotone 1500 Hz
heartbeat beeps, 70 ms each, every 10 s
- Silent once nothing has been seen for 20 s, until the next new MAC
Global "last seen" timer refreshes on every inbound hit, including ones
suppressed by the serial rate limit, so quieter repeats still count as
"still around" for the heartbeat. LED still flashes on every emitted
detection.
- Moved promiscuis-flock-you/{main.cpp,partitions.csv,platformio.ini} to root
- Removed BLE firmware (src/main.cpp, src/CMakeLists.txt) — the 'main' branch
still has it
- Removed the subfolder README (root README has the full walkthrough)
- Updated README paths and build commands for the flat layout
- Retagged the BLE companion section as a pointer to the 'main' branch
The 'promiscious' branch is now purely the WiFi promiscuous firmware plus
the shared Flask app (api/), datasets, and branding. Builds green with
the standard 'pio run' from the repo root.
The 'promiscious' branch is about the WiFi promiscuous detector, not BLE.
Reworked the root README to lead with:
- @NitekryDPaul credit front-and-center (30-OUI list + addr1 technique)
- What the WiFi firmware does and why promiscuous mode is the right tool
- Detection pipeline, OUI list, SPIFFS envelope format
- Flask dashboard integration with the wifi_oui_addr1/addr2 methods
- Hardware, build, config cheatsheet
- SMB 1-2 underground boot melody
The BLE firmware is now a short pointer to main at the bottom.
Explains how the new WiFi promiscuous firmware in promiscuis-flock-you/
complements the existing BLE detector — same hardware class, same Flask
dashboard schema, complementary RF coverage.
Full research credit to ØяĐöØцяöЪöяцฐ / @NitekryDPaul for the 30-OUI
target list and the addr1-receiver detection technique. Added to
Acknowledgments.
Modded from @NitekryDPaul's promiscuous-mode firmware. Adds:
- SPIFFS session persistence with atomic CRC-envelope writes
- Flask-compatible JSON emission over USB CDC (ingested by api/flockyou.py)
- Onboard LED flash and buzzer beep per detection
- USB-optional operation (non-blocking Serial for standalone runs)
- First 6 notes of SMB World 1-2 as boot melody
- Prior-session promotion to /prev_session.json on boot
30-OUI target list and the addr1-receiver detection technique are
@NitekryDPaul's research (see datasets/NitekryDPaul_wifi_ouis.md).
30 Flock Safety infrastructure OUIs identified by @NitekryDPaul via
2.4 GHz promiscuous-mode analysis, including the addr1-receiver
detection technique that catches Flock STAs during burst-sleep
duty cycles. Full credit and methodology in the new file.
Fix GPS not tagging all detections:
- Add dedicated fyGPSMutex guarding fyGPSLat/Lon/Acc/Valid/LastUpdate
- fyGPSSnapshot() + fyGPSUpdate() replace direct global reads/writes,
eliminating the race between BLE callback (reader) and /api/gps
HTTP handler (writer)
- fyBackfillGPS() runs every 2s in loop(), stamping any detection that
was recorded before the first phone GPS fix became available
- BLE callback JSON emitter now uses snapshot instead of raw globals
Make data saving/transfer bulletproof:
- New envelope format: header line {v,count,bytes,crc32} + payload array
- CRC32 verification catches any truncation or corruption on read
- Atomic write: compute CRC pass 1, write tmp + verify, then rename to
final (SPIFFS.rename with copy+delete fallback)
- Boot-time recovery: if session.json is missing/corrupt, recover from
session.tmp; legacy raw-array files still load for back-compat
- Save cadence tightened: within 5s of first detection, after any new
unique detection (3s throttle), and periodic safety net every 15s
- Export mutex timeouts raised 200->500ms to prevent empty CSV/JSON
exports under heavy BLE traffic
- /api/history and /api/history/kml strip envelope header before
returning body so downstream tools keep working unchanged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Low-confidence mac_prefix_mfr hits no longer update fyLastDetTime/fyLastHB,
preventing them from keeping the heartbeat alive after a high-confidence
device leaves range. Bump FYDetection::method from 24 to 32 bytes so
"mac_prefix_soundthinking" (23 chars) has headroom.
- fyPromotePrevSession now reads session.json, writes to prev_session.json,
then deletes the original — SPIFFS.rename() silently fails on ESP32
- prior session KML export: added style defs, raven metadata, error handling,
moved file validation before response stream init
- reduced auto-save interval 60s → 15s, added 5s quick-save on first detection
Address Copilot review: contract manufacturer OUIs (Liteon/USI) are now
in a separate flock_mfr_mac_prefixes[] array emitting "mac_prefix_mfr"
as the detection method. SoundThinking/ShotSpotter gets its own array
and "mac_prefix_soundthinking" method. Low-confidence detections (mfr
OUIs) suppress buzzer/heartbeat but still emit JSON events so consumers
can apply their own thresholds.
Collapse the two-branch snprintf into a single call so every
detection message includes is_raven (true/false) and raven_fw,
making the format self-describing regardless of device type.
BLE server callbacks run on the NimBLE host task, not the Arduino
loop task. Calling WiFi state changes and delay() from that context
can stall BLE processing or trip watchdogs, and mutating scan
duration creates a cross-task data race.
Fix: callbacks now just set a volatile pending flag. The actual
WiFi/scan changes are applied in loop() where they're safe.
Expand MAC prefix detection with entries sourced from:
- Flock WiFi (Liteon/USI): f4:6a:dd, f8:a2:d6, e0:0a:f6, 00:f4:8d,
d0:39:57, e8:d0:fc — contract manufacturer OUIs (Liteon Technology and
USI/Universal Scientific Industrial) identified via the OUI-SPY firmware
ecosystem table and cross-referenced against the IEEE OUI registry.
These manufacturers produce Flock Safety's WiFi-enabled camera hardware.
- Flock Safety direct: b4:1e:52 — registered directly to "Flock Safety"
in the IEEE OUI database (MA-L assignment). This is their own prefix
rather than a contract manufacturer's.
- SoundThinking/ShotSpotter: d4:11:d6 — registered to "SoundThinking Inc"
(formerly ShotSpotter) in the IEEE OUI database. Their acoustic gunshot
detection sensors use BLE for local diagnostics and provisioning.
Enable DeFlock mobile app connectivity via BLE GATT notifications,
and desktop host detection via USB serial heartbeat. When a companion
is connected, WiFi AP is disabled to free radio bandwidth and BLE
scan duty cycle is increased for better detection performance.
- BLE GATT server advertising service UUID a1b2c3d4-e5f6-7890-abcd-ef0123456789
with TX characteristic (NOTIFY) for streaming detection JSON
- Chunked BLE notification sender respecting negotiated MTU
- "event":"detection" field added to JSON output for DeFlock parser
- Serial host detection via heartbeat timeout (5s)
- Companion mode: WiFi AP off + scan duration 2s→3s when connected
- Scan interval/duration converted from #define to mutable variables
Complete rewrite of standalone Flock-You firmware:
- Remove WiFi promiscuous mode, all detection is now BLE-only
- Add web dashboard served on AP "flockyou" at 192.168.4.1
- GPS wardriving via phone browser Geolocation API
- SPIFFS session persistence with auto-save every 60s
- Prior session tab (PREV) survives reboots
- KML export for Google Earth (current + prior session)
- JSON/CSV export with GPS coordinates
- Serial JSON output for Flask live ingestion
- Crow call boot sounds with detection/heartbeat alerts
- 200 unique device storage with FreeRTOS mutex
- Flask app: add KML import endpoint, GPS data handling
- Update platformio.ini with AsyncWebServer, ArduinoJson 7, SPIFFS partition
- Rewrite README to reflect current functionality