67#include <freertos/FreeRTOS.h>
68#include <freertos/portmacro.h>
71#ifdef POLITICIAN_NO_STD_FUNCTION
72#error "PoliticianSense.h requires std::function (POLITICIAN_NO_STD_FUNCTION must not be defined)."
92#ifndef POLITICIAN_SENSE_MAX_WINDOW
93#define POLITICIAN_SENSE_MAX_WINDOW 64
121 , _mean(0.0f), _variance(0.0f)
123 , _lastMotionMs(0), _lastSampleMs(0)
126 memset(_anchor, 0, 6);
127 memset(_buf, 0,
sizeof(_buf));
128 _mux = portMUX_INITIALIZER_UNLOCKED;
161 if (_engine && _engine != &
engine) {
169 portENTER_CRITICAL_SAFE(&_mux);
170 if (anchorBssid && memcmp(anchorBssid,
"\x00\x00\x00\x00\x00\x00", 6) != 0) {
171 memcpy(_anchor, anchorBssid, 6);
174 memset(_anchor, 0, 6);
177 portEXIT_CRITICAL_SAFE(&_mux);
180 int8_t rssi, uint8_t ch, uint32_t ts) {
181 _onPacket(payload, len, rssi, ch, ts);
198 if (!ssid)
return false;
200 uint8_t bestBssid[6] = {};
204 if (strncmp(ap.
ssid, ssid,
sizeof(ap.
ssid)) == 0) {
205 if (ap.rssi > best) {
207 memcpy(bestBssid, ap.bssid, 6);
253 portENTER_CRITICAL_SAFE(&_mux);
257 portEXIT_CRITICAL_SAFE(&_mux);
284 if (!_engine)
return;
288 uint8_t snapCount, snapStart, snapWindow;
291 portENTER_CRITICAL_SAFE(&_mux);
293 snapWindow = _windowSize;
294 snapStart = (snapCount < snapWindow) ? 0 : _head;
295 lastSample = _lastSampleMs;
296 memcpy(snap, _buf, snapWindow);
297 portEXIT_CRITICAL_SAFE(&_mux);
300 if (snapCount < 4)
return;
302 bool isStale = (_staleMs > 0 && (
millis() - lastSample) > _staleMs);
312 for (uint8_t i = 0; i < snapCount; i++) {
313 sum += snap[(snapStart + i) % snapWindow];
315 _mean = sum / snapCount;
319 for (uint8_t i = 0; i < snapCount; i++) {
320 float d = snap[(snapStart + i) % snapWindow] - _mean;
323 _variance = varSum / snapCount;
330 if (_variance >= _threshold) {
334 if ((now - _lastMotionMs) >= _debounceMs) {
339 if (_state != prev && _senseCb) {
340 _senseCb(_state, _variance);
357 portENTER_CRITICAL_SAFE(&_mux);
358 uint32_t n = _totalSamples;
359 portEXIT_CRITICAL_SAFE(&_mux);
403 _engine->setPacketLogger(
nullptr);
414 volatile bool _active;
417 mutable portMUX_TYPE _mux;
425 uint32_t _debounceMs;
432 uint32_t _lastMotionMs;
435 volatile uint32_t _lastSampleMs;
436 uint32_t _totalSamples;
444 void _reset_internal() {
445 portENTER_CRITICAL_SAFE(&_mux);
450 portEXIT_CRITICAL_SAFE(&_mux);
457 void _onPacket(
const uint8_t *payload, uint16_t len,
458 int8_t rssi, uint8_t ch, uint32_t ts) {
459 if (!_active)
return;
460 if (len >=
sizeof(ieee80211_hdr_t)) {
461 const auto *hdr =
reinterpret_cast<const ieee80211_hdr_t *
>(payload);
462 uint16_t fc = hdr->frame_ctrl;
472 portENTER_CRITICAL_SAFE(&_mux);
473 if (_anyAnchor || memcmp(hdr->addr2, _anchor, 6) == 0) {
475 _head = (_head + 1) % _windowSize;
476 if (_count < _windowSize) _count++;
480 portEXIT_CRITICAL_SAFE(&_mux);
483 if (_userPacketCb) _userPacketCb(payload, len, rssi, ch, ts);
#define POLITICIAN_SENSE_MAX_WINDOW
Maximum ring-buffer capacity (samples).
Passive RSSI-based motion and presence detector.
void setStaleTimeout(uint32_t ms)
Sets the stale-data timeout (ms).
uint32_t getTotalSamples() const
float getMeanRssi() const
void setSenseCallback(SenseCb cb)
Sets the state-change callback.
void end()
Detaches from the engine and clears its packet logger.
void setThreshold(float dBm2)
Sets the variance threshold (dBm²) that triggers SENSE_MOTION.
static constexpr uint32_t DEFAULT_DEBOUNCE
Motion hold-time (ms).
static constexpr uint8_t DEFAULT_WINDOW
Sliding window (samples).
void setPacketLogger(Politician::PacketCb cb)
Registers a pass-through raw-packet callback.
static constexpr float DEFAULT_THRESHOLD
Variance threshold (dBm²).
void setDebounce(uint32_t ms)
Sets the debounce hold-time (ms).
static constexpr uint32_t DEFAULT_STALE_MS
Max gap before ignoring stale data (ms).
void begin(Politician &engine, const uint8_t *anchorBssid=nullptr)
Attaches the sensor to a Politician engine.
const uint8_t * getAnchor() const
void setWindowSize(uint8_t n)
Sets the sliding window size in samples (clamped to [4, POLITICIAN_SENSE_MAX_WINDOW]).
SenseEvent getState() const
void reset()
Clears the sample window and resets state without detaching from the engine.
float getVariance() const
void tick()
Main worker — call from loop() alongside engine.tick().
bool beginBySSID(Politician &engine, const char *ssid)
Looks up an AP by SSID in the engine cache and anchors to its BSSID.
The core WiFi handshake capturing engine.
void setPacketLogger(PacketCb cb)
Sets the callback for raw promiscuous mode packets.
std::function< void(const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec)> PacketCb
SenseEvent
State transition delivered to the SenseCb callback.
@ SENSE_MOTION
RSSI variance spiked above threshold — movement detected.
@ SENSE_STILL
RSSI variance returned to baseline — area quiet.
std::function< void(SenseEvent event, float variance)> SenseCb
Callback fired on SENSE_STILL ↔ SENSE_MOTION transitions.
Snapshot of a discovered Access Point from the internal cache.