Surveillance
These are this fork's, plus the bench beacon that proves the radio side of them works. Everything else in the menus arrived with ESP32-DIV and was touched only where the pin map or the shared bus forced it, which is a different list. That one is under what's different.
Surveillance
Called Spotter until 0.4.0, and still Spotter in the source. The old name did not say what you get; the list is plate readers, body cameras, smart glasses and vehicle modules. It moved out of Bluetooth at the same time, along with the drone detector: neither is a Bluetooth feature, both listen on WiFi and BLE. They sat behind a tile called More until 0.4.13 and now have their own, Detect.
The mark it opens on, the list, and the dwell alert in both its forms. The last two differ by one line: a BLE tracker is offered Hunt can walk you to it, a camera is not, because Hunt lists trackers and nothing else.
Surveillance is one of six features this fork added rather than inherited, and the one with the most to say. It listens for hardware that announces itself to anyone in range, and does nothing else. Two hundred and eighty-three signatures over ten kinds: plate readers and their accessories, body cameras, fixed cameras and doorbells, smart glasses, item trackers, vehicle modules, gunshot sensors, pentest hardware and mesh nodes. Every OUI in there is checked against the IEEE registry by check_oui_registry.py, and every Bluetooth identifier against the SIG's assigned numbers, before it counts as a strong finding. Two of those kinds are not surveillance gear, and are here because knowing they are nearby is worth something on its own. Pwnagotchi beacons from a fixed de:ad:be:ef:de:ad with its stats in the payload, so it announces itself more clearly than most of what it is listening for. Meshtastic nodes advertise a 128-bit service of their own and get a kind of their own with it, rather than being filed under something they are not.
What announces itself
ALPR cameras broadcast WiFi probe requests hunting for the network they upload to. They do it whether or not a hotspot is there and whether or not anyone is listening, so a receiver hears them without transmitting anything. Smart glasses advertise over BLE, and Meta's put both a Luxottica company ID and a Meta service UUID in the same packet. Body cameras carry WiFi so they can offload to a dock, which makes an Axon unit audible the same way. 00:25:DF is Axon Enterprise's own registered block, so unlike most of what follows it is strong evidence of Axon hardware in range, though, not of a camera specifically, because the same block covers their docks, their in-car Fleet systems and their TASERs. That block is matched on BLE as well as WiFi, which is what 0.3.2 added, but only it and Flock's, and only against a public address. The table holds 135 OUI rows now, and the 61 strong ones are the ones that reach BLE: Ring, Hikvision, Axis, Verkada, Avigilon, Wyze and Flipper among them, every one a block IEEE assigned to the company named on the row. Fifteen are graded likely. Seven are Motorola Solutions and Genetec, because Motorola is the public-safety radio industry before it is a camera company and one of its addresses is more often a handheld radio. The other eight are Zigbee coordinators, Hue, SmartThings, Aqara and IKEA, a grade down for the same reason read the other way: the block belongs to the company, and the company sells more than the hub. The remaining 58 are weak, contract manufacturers and module vendors and the radio blocks pentest kit is built on: 23 Liteon, two Espressif, two USI, Murata, Realtek, Telink, Alfa. Pwnagotchi's de:ad:be is here too, weak as a prefix because it is a convention rather than an allocation and anything at all can wear it. The whole de:ad:be:ef:de:ad is a strong row in its own table, where six bytes make the claim that three cannot. A WiFi scan only hears the ones probing for a network, whereas a BLE scan hears everything advertising in the room. Espressif's block on that list would report a shelf of dev boards as surveillance hardware, and the device doing the scanning is an ESP32. The public-address condition matters for a subtler reason: a random BLE address encodes its type in the top two bits rather than a vendor, and 00:25:DF begins 00, which is exactly the shape of a non-resolvable private address. The strongest entry in the table is also the one a randomised address could wear by chance.
KARR, and the cars carrying one
Vehicle modules joined the list in 0.3.3. KARR is a dealer-installed immobiliser and remote control, and UC San Diego's BLE Theft Auto (USENIX Security 2026) found it second only to Tesla among BLE devices on San Diego's highways (608 of 4,314 unique devices) because dealerships fit one to every car on the lot. Every module shares one hard-coded key. It advertises a name of QT or DR followed by exactly eight characters, which is the whole signature. At least a million vehicles carry it, and in roughly half of those the owner does not know, the dealer fitted it and they never bought the service. That is the case for a passive detector in one sentence: the question "is one of these in my car" currently has no answer that does not involve asking the dealership that installed it. That exactness is new machinery. Name signatures matched on a prefix alone, case-insensitively, which is fine for Spectacles and useless for DR, a two-letter prefix claims every device whose name starts that way, and a match is what puts a row on the screen. So a signature can now declare the exact length it expects. It is graded likely, and it names a module rather than a finding. The vulnerability was patched on 2026-07-20, but the fix is applied by hand and nothing in the advertisement says whether it has been. The row means a KARR module is in range. It does not mean the car is vulnerable. Applied by hand is doing a lot of work in that sentence. Most KARR modules have BLE and nothing else (no cellular, so no over-the-air update), and the patch has to be pushed to each one. Owners who never bought the service have no account to authenticate with, so the vendor lets them present their VIN instead, against a database of the cars modules were pre-installed in. A fix that requires each owner to discover a device they did not know about, and then prove which car they own, is not one you should assume has been applied to the module in front of you. It is also not one you can rule out from a scan, which is why the row says what it says. The wardriver will not upload them. The same paper describes the attack beginning with a name-pattern query against a public wardriving database (1,141,798 of those QT addresses were pulled from WiGLE exactly that way), so adding fresh sightings with coordinates would be contributing to that index. The WiGLE conversion drops them and reports how many it dropped. The reason that matters is in the same paper. Of fifty modules sampled from WiGLE, 31 of them (62%) had been seen three or more times inside a 100 m radius: not a car passing on a road, but a car that sleeps somewhere. A wardriving database is a list of where things are parked, and for this particular class of thing that is most of an address. The log on the card keeps them. Withholding from a public database and lying to the operator about what their own radio heard are different things, and only the first is wanted. Receive only, and deliberately so. Surveillance never transmits, never associates, never deauthenticates. It puts the radio in promiscuous mode and runs a passive BLE scan, which is what any WiFi analyser does. That is a legal statement as much as a technical one: listening to a broadcast is a different act from injecting a frame, and this stays on the listening side.
Tyre sensors, and which kind these are
Not the ones your car reads. Factory TPMS is a valve-stem transmitter on 315 MHz or 433 MHz, talking to a receiver in the car, and nothing here decodes it. The CC1101 tunes across both, so the hardware could, and no feature does. If that is what you came for, it is not here. These are the Bluetooth ones. Aftermarket valve-cap sensors that pair with a phone app, and a handful of vendors who put BLE in the sensor itself: Schrader, Goodyear, Pacific, Huf, FOBO, TireCheck. Strong on a vendor's own company ID, because nothing else uses one. The four wheel-position rows are only Likely: they are company 0x0001, which is Nordic's, so the payload byte is doing all the work and the grade says so. The SIG's own tyre pressure service is Weak for the same reason the other allocated services are, since anything implementing the standard lands on it. Worth more than they look. A tyre sensor broadcasts a stable identifier for one wheel, continuously, and the owner did not choose it and cannot switch it off. Four of them is a vehicle fingerprint that survives a change of number plate, and following a car by its tyres is a known technique rather than a hypothetical one. That is the same argument as KARR: something bolted to a car, broadcasting, that its owner never agreed to.
What the REC tag is telling you
While a capture is running the tag reads REC and the row count, in red. Three things change it, and all three turn it orange, because all three mean the file is not what you would otherwise assume. REC 412 !7 is seven frames the capture ring dropped because the card could not keep up. A capture that is quietly incomplete is worse than one that says so: before this, the only evidence was rows that did not add up to what the radio saw. The count appears only when it is not zero. log off is SD logging switched off in Settings, either for Surveillance or by the master switch, with a card sitting in the slot. no SD is the card itself.
Filtering what you see
Nine kinds and three confidence levels is a lot of rows in a car park. The left button opens a filter: toggle any of the ten kinds, and set a floor of everything, likely and up, or strong only. Two things about it are deliberate, and both exist because a filtered list and a quiet street look identical. It only hides rows. Everything is still detected, still counted, still written to the card. A filter that stopped recording would make the capture depend on what the screen happened to be showing while it ran, which is not something you can read six months later. It resets when you leave the screen. The way a remembered filter fails is silent: you set strong-only in a car park, walk somewhere that matters, read an empty screen and conclude there is nothing there. And while any filter is on, the header counts both ways, hits 4/13 rather than hits 13, with the second number being the one that has not changed.
The signature table
The signatures are the part that goes stale, so they live in their own file, away from the matching logic. The contents of that file are on the signature list page; what follows is what each table can see. Vendors change contract manufacturers, buy new OUI blocks and revise firmware; the code that compares three bytes does not. There are nine tables, and the shape matters more than the contents. A vendor in the list goes out of date; what a table can see does not, and it is what explains why something was or was not found. The whole address 1 row. For hardware with no vendor block at all, only a hardcoded joke: the Pwnagotchi advertises de:ad:be:ef:de:ad on every unit. WiFi source MAC prefix 135 rows. The first three bytes, which IEEE assigns to a manufacturer. The oldest and bluntest of these. Network name, anchored 5 rows. An SSID or BLE name matched from the start, optionally at an exact length. Network name, anywhere 78 rows. The same, matched in the middle. A Pineapple's management SSID has whatever the owner typed in front of it. BLE company or service 47 rows. The manufacturer ID in an advertisement, or a 16-bit service UUID. BLE advertised name 7 rows. The name a device puts in its own advertisement, which it chose and can change, so it is the softest of the BLE tables. 128-bit service UUID 2 rows. Somebody's own protocol rather than an allocation out of the shared SIG space, so a hit is worth much more. Manufacturer data 5 rows. The bytes past the company ID. This is what distinguishes an AirTag from every other Apple device in range: company 0x004C is an iPhone, and the type byte 0x12 is a Find My accessory separated from its owner. Service data 3 rows. The bytes past a service UUID. Both a Find My Device tag and a shop's beacon advertise 0xFEAA; the frame type says which. One of these is DULT, the cross-vendor advertisement a tracker is supposed to send when it is separated from its owner, which makes it the one row that covers vendors who have not shipped yet. The last three arrived together, and the AirTag is the reason. Surveillance had Tile, Samsung SmartTag and Eddystone and no Find My rule at all, so the most common tracker there is did not appear on the screen whose job is finding trackers. It could not: nothing in the file could read a byte past a company ID. The gap was a missing kind of question, not a missing vendor. Where a batch of the vendors came from: Flock Safety's own IEEE block, the Flock-, bare Flock and Flock Camera net. network names, the Penguin battery pack's BLE name and XUNTONG company ID, the Raven camera's GATT UUIDs, and the 33 WiFi prefixes that the flock-you and flock-finder community collections have between them. Those 33 are mostly not what they look like. Resolved against the IEEE registry, twenty-three belong to Liteon and two to Espressif: contract manufacturers and a module vendor whose blocks sit in an enormous amount of unrelated consumer hardware. One is registered to nobody, and one has the locally administered bit set, which is to say it is a randomised address rather than a vendor block at all. Reported as a camera on their own they would be wrong far more often than right, so each carries the name the registry actually gives it and each counts as weak evidence only. Which is what the confidence scoring is for. A bare OUI is a hint. Two independent fields agreeing, a weak prefix on the same MAC as a Flock- network name, is a finding, and the screen says which of the two it is showing you. A detector that cries wolf is one you stop believing.
When the address stops helping
The OUI is already being designed around. One of those 33 prefixes has the locally administered bit set, meaning it is not a vendor block but a made-up address, and flock-finder reports newer units randomising precisely to beat prefix matching. A camera that changes its address defeats the table above completely, and it defeats the list it would have appeared in too: the hit table is 48 rows keyed on the address, so one rotating radio fills it on its own. What does not change when the address does is the radio. A probe request carries a set of information elements whose identity, order and contents are chosen by the chipset, the driver and the supplicant rather than by the network being looked for. Hashing that gives a fingerprint a device cannot randomise away without becoming a different device. Two things are deliberately kept out of the hash: the network name, which is the one field that varies between two probes from the same radio, and the channel, because Surveillance hops channels every 260 ms and hashing it would give one camera thirteen fingerprints. Everything else contributes, and the element ids contribute even where their contents do not, because which elements a driver emits, and in what order, is itself the signature. There is no fingerprint table, and that is deliberate. Values for one need packets captured off a real camera, and none have been taken here. Seeing a row appear on the panel is not a capture. FlipDeFlock has some; this fork moved to GPL-3.0 partly so they could be used, with attribution and without its name, which its trademark file keeps separate from its code. They are still somebody else's captures rather than mine. The fingerprint is shown on screen to be written down, and the table stays empty until somebody stands next to a camera. Three things work with no table at all, which is why they were built first. A rotating radio collapses to one row rather than filling the list, five hundred rotations leave forty-seven rows free. A made-up address is marked rnd, which is exactly the case where the OUI beside it means nothing. And every row carries how long the device has been in range, because a handset walks past and a camera is bolted to a pole. That collapsing is the one place this can quietly be wrong, so it is narrow: two addresses only fold together if both are made up. Two cameras of a model share an element set, and a rule that merged on the fingerprint alone would report one where there are two, which is the wrong direction for a detector to fail in. All of that is in 0.2.2, and in the download. The capture is on the Log button and it writes to the card, not anywhere else.
The dwell alarm
Ten minutes is when it stops being a number and starts being an alert. The duration column has been on every row since 0.2.2, which is a number you have to be looking at to read, and the case this feature is for is the one where you are not looking at it. In 0.4.3 the first row to cross ten minutes brings up the mark above, the device and how long, and two rising notes. Shown once, then gone. It fades rather than staying, because this is a passive monitoring screen and a banner that stayed would be covering the list you wanted to read. It re-arms when the last dwelling device goes quiet, so something that leaves and comes back says so twice, which is the case worth hearing about twice. The two screens differ by one line, and that line is the whole point. Hunt lists BLE trackers and nothing else. A dwelling plate reader or body camera will not appear in its picker, so offering it anyway would be sending someone to an empty screen and calling it advice. A tracker gets Hunt can walk you to it; a camera gets the duration and no suggestion. May be is doing real work in the other line. Pueo has no position of its own. It cannot tell a tracker moving with you from a beacon you have been sitting beside for ten minutes, and from here the two are identical. It reports how long, and points at the tool that would settle it. It does not decide. The sound is the half that works when the screen does not. The 3.5" board drives a speaker from GPIO 26, which is how a device in a bag or face down on a table can raise an alarm at all. Two caveats, both worth knowing before deciding the firmware is broken: the 2.8″ board had that pin free but spent GPIO 4 on a radio's chip select and is silent by construction here, and the 3.5" ships with no speaker fitted. The output comes out to a 1.25 mm two-pin connector and you solder your own. The firmware used to assert GPIO 4 as an amplifier enable alongside this, which was read off a datasheet for a board this is not built on. On the real one GPIO 4 is the RGB LED's red channel, so what that line did was blink the LED for the length of every beep. Removed on 2026-09-23. The beeps sit inside the hold rather than adding to it, so the alert lasts the same time whether or not it makes a sound.
Finding an unknown tracker
Surveillance finds, Hunt locates, and the step between them is yours. The two
screens answer different questions and neither answers the one that matters
on its own.
1. Leave Surveillance running. It is passive, so there is nothing to start
and nothing that announces you. Put the device in a bag or face down; the
dwell alarm is audible and that is what the speaker is for. Filter to
TRACKER if a car park is producing more rows than you want to read.
2. Wait for the dwell alarm. Ten minutes of continuous presence is what
turns a row into an alert. A tracker that has been with you that long is
interesting; a beacon in the shop you are sitting in has been there longer
and looks identical from here.
3. Separate the two by moving. This is the part Pueo cannot do, and it
is worth being plain about why: not the firmware, the device. There is no
GNSS fix behind this screen and no dead reckoning. It
hears a radio and knows how long it has heard it, and from inside the device
"a tag in my coat" and "a beacon by the till" are the same observation. Walk
somewhere else, somewhere a fixed beacon cannot follow, and wait. What comes
with you is the thing worth chasing.
4. Walk it down with Hunt. Pick the row and the gauge opens. It reads
signal strength, smoothed, with a green tick at the best reading so far and
WARMER / COLDER in large letters you can read at arm's length. It is not
distance and it does not pretend to be: an unobstructed tag across a room can
read stronger than one under a seat two feet away.
Sweep rather than stare. Turn slowly on the spot and watch where the needle
peaks, move a few paces that way, and let the reading settle before judging
it. Your own body between the device and the tag is worth 10 dB, which is
useful: block it deliberately and the direction it drops is the direction it
is in.
What will not appear, and why that matters
A screen with nothing on it is not evidence of nothing. These are all
invisible to a passive listener and none of them are unusual:
a tracker in its paired state Apple's separated-from-owner broadcast is
type 0x12. A tag still near its owner says
0x07 and Surveillance leaves it alone,
deliberately: somebody's own keys beside
them are not surveillance.
anything cellular-only an LTE tracker with no BLE advertisement
is silent on every radio this device has.
anything switched off a tag with a dead cell, or one that only
wakes periodically, between wakes.
5 GHz Wi-Fi the ESP32 is 2.4 GHz. A 5 GHz-only access
point does not exist as far as this is
concerned.
a wired or recording-only a dashcam writing to a card announces
device nothing at all.
So the honest reading of a quiet screen is "nothing that announces itself is
here". That is a real and useful statement, and it is narrower than "nothing
is here". A tool that blurs the two gives a false sense of security.
Handshakes
Handshakes, in the capture that is already running. Packet Monitor writes every frame it hears to a pcap on the card, data frames included, which is where a WPA handshake travels. It now recognises them. The 802.1X payload is found by computing the header length from the frame control field rather than testing a fixed offset (a frame can carry a fourth address or an HT Control field, and then the payload is somewhere else), and the four messages are told apart by three bits of the Key Information field. The header shows how many networks have M2 plus M3, which is the pair worth having. Recognising them was not enough, because they were being dropped. The pcap pool drops frames when it fills, which on a busy channel is constantly. That is harmless for ordinary traffic and fatal here: the four messages arrive together, in a burst, at the moment the channel is busiest, and losing one makes the other three worthless. A bigger pool was the wrong fix, and the board said so. Raised from three slots to sixteen, it reported four key frames seen and four not written. A bigger pool fills later; the burst still arrives after it has filled. Key frames have eight slots of their own now that nothing else can take, so ordinary traffic cannot evict them however heavy it gets. Every capture ends with one line saying what happened: written=266 dropped=56 keyframes_lost=0 Fifty-six ordinary frames the general pool could not hold, which is expected and was always happening. No key frame lost. The condition that destroyed every handshake is still present in the traffic and no longer reaches the frames that matter. Proved rather than argued: a capture dropping 1,387 of 4,268 frames, 32 per cent, while four key frames arrived consecutively and every one reached the card. Those files were then checked at byte level, not with the parser they are a fixture for, against Wireshark's own WPA reference capture. Forcing a handshake, by deauthenticating somebody so their device reconnects, is not wired into this. The decision logic exists and stops itself on capture or after six bursts, and nothing calls it: that half transmits at other people's equipment, and it is a different thing from listening. That is about the capture rather than about the device. WiFi Deauther came with ESP32-DIV, is still in the Wi-Fi menu as a tool of its own, and Stealth Mode gates it like every other transmitter. What is absent is the automatic version, a capture that nudges whatever it happens to be watching with nobody choosing a target.
What has and has not been tested
One row has come off real hardware. A Flock ALPR raised one on the panel, which is the ALPR signatures and the matching path confirmed together, on one device, on one occasion. Nothing was captured or logged off it, so there is no artefact to point at and nothing to build a fingerprint from. The rest of the table has not been near the hardware it names. Those signatures come out of public research and other people's firmware teardowns, not out of packets captured off a device. See Status.