ESPectre SDK 2.8.0-280-gac7af68
Wi-Fi CSI motion sensing for ESP32 firmware
Loading...
Searching...
No Matches
runtime_events.h
Go to the documentation of this file.
1/*
2 * ESPectre - Runtime Events
3 *
4 * Runtime listener and event contracts.
5 *
6 * Author: Francesco Pace <francesco.pace@gmail.com>
7 * SPDX-License-Identifier: GPL-3.0-only
8 * Commercial licensing available under separate agreement; see LICENSING.md.
9 */
10#pragma once
11
12#include <cstdint>
13
14#include "runtime_snapshot.h"
15
16namespace espectre {
17
18/**
19 * Everything the runtime tells your firmware.
20 *
21 * Subclass it, override only what your product reacts to, and install it with
22 * `RuntimeFrontendController::setup(listener)`. Every callback has an empty
23 * default, so an integration that only cares about motion overrides one method.
24 *
25 * @par Threading and reentrancy
26 * Callbacks are always delivered on the caller's task, never from an interrupt
27 * or the Wi-Fi driver:
28 * - Sensing events (motion, periodic, live telemetry, calibration completion,
29 * and detector-driven threshold adaptation) originate in the CSI callback
30 * but are deferred through an internal mailbox and dispatched from `loop()`.
31 * - Control-driven events (runtime threshold writes and detector selection)
32 * fire inline on whichever task called the corresponding setter.
33 * `on_threshold_changed()` is used for both: a setter, calibration finish,
34 * or Lightweight settled-level recovery.
35 *
36 * Keep callbacks bounded and non-blocking. Slow work delays the next `loop()`
37 * iteration and can fill the bounded CSI mailbox, causing incoming frames to be
38 * dropped. Queue network publication, NVS writes, and other potentially
39 * blocking work for another task. Calling back into the controller is allowed,
40 * with one exception noted on `on_runtime_fault()`.
41 *
42 * @par Snapshot lifetime
43 * The `snapshot` reference is only valid for the duration of the call. Copy it
44 * if you need it later.
45 *
46 * @par Readiness
47 * Snapshots are delivered during startup calibration as well. Gate anything
48 * user-visible on `RuntimeSnapshot::ready_to_publish` so you do not report
49 * motion from an uncalibrated detector.
50 */
52 public:
53 virtual ~IRuntimeListener() = default;
54
55 /**
56 * The debounced motion state changed.
57 *
58 * Edge-triggered and already filtered by `motion_on_hits` / `motion_off_hits`,
59 * so this is the hook for occupancy, relays, and notifications.
60 *
61 * It also fires with `MotionState::IDLE` when the Wi-Fi link drops, and that
62 * call carries `ready_to_publish == false`. The shipped frontends gate on
63 * that flag and therefore leave their last published value in place across a
64 * disconnect; if your product would rather fail open, handle the
65 * not-ready edge explicitly instead of returning early.
66 *
67 * @param snapshot Sensing state at the moment of the change.
68 */
69 virtual void on_motion_state_changed(const RuntimeSnapshot &snapshot) {}
70 /**
71 * Heartbeat, emitted every `RuntimeConfig::publish_interval_ms` milliseconds.
72 *
73 * Use it for status logging and diagnostics sampling rather than sensing
74 * telemetry. Movement and canonical MQTT telemetry follow detector evaluation
75 * through `on_live_telemetry()`.
76 *
77 * @param snapshot Current sensing state, including the metric and threshold.
78 * @param packets_received CSI packets accepted since the previous heartbeat,
79 * which is the honest measure of the achieved capture rate.
80 */
81 virtual void on_periodic_update(const RuntimeSnapshot &snapshot, uint32_t packets_received) {}
82 /**
83 * The active threshold changed, from a control call, calibration, or
84 * detector-driven adaptation such as Lightweight settled-level recovery.
85 *
86 * Refresh any threshold you mirror in a UI or a published entity. Live
87 * telemetry still carries the per-sample comparison value; this hook is the
88 * control-plane notification when that value itself has moved.
89 */
90 virtual void on_threshold_changed(const RuntimeSnapshot &snapshot) {}
91 /**
92 * The active detector changed.
93 *
94 * Thresholds are per-detector, so `on_threshold_changed()` follows this one.
95 */
96 virtual void on_detector_changed(const RuntimeSnapshot &snapshot) {}
97 /**
98 * Startup calibration began; detection results are not valid yet.
99 *
100 * Lightweight only. ML ships a fixed threshold and completes immediately.
101 */
102 virtual void on_calibration_started(const RuntimeSnapshot &snapshot) {}
103 /**
104 * Startup calibration finished.
105 *
106 * @param snapshot Sensing state at completion, carrying the applied threshold.
107 * @param success false when calibration was cancelled or could not settle on
108 * a threshold. The runtime keeps sensing with the configured value,
109 * so treat this as a signal to surface, not a fatal error.
110 */
111 virtual void on_calibration_finished(const RuntimeSnapshot &snapshot, bool success) {}
112 /**
113 * High-rate movement stream, one call per detector evaluation.
114 *
115 * Frontends publish canonical telemetry and Movement Score from this hook.
116 * Considerably more frequent than `on_periodic_update()`; suppress it with
117 * `set_live_telemetry_enabled(false)` when nothing is watching.
118 *
119 * @param movement Current motion metric.
120 * @param threshold Threshold it is compared against, on the same scale.
121 */
122 virtual void on_live_telemetry(float movement, float threshold) {}
123 /**
124 * A runtime-owned failure your firmware should surface.
125 *
126 * @param message Human-readable cause, valid only for this call.
127 *
128 * Do not drive the runtime from here beyond `shutdown()`: the fault is
129 * reported from inside runtime work, and re-entering control paths from it
130 * is not supported.
131 */
132 virtual void on_runtime_fault(const char *message) {}
133};
134
135} // namespace espectre
Everything the runtime tells your firmware.
virtual ~IRuntimeListener()=default
virtual void on_live_telemetry(float movement, float threshold)
High-rate movement stream, one call per detector evaluation.
virtual void on_calibration_finished(const RuntimeSnapshot &snapshot, bool success)
Startup calibration finished.
virtual void on_motion_state_changed(const RuntimeSnapshot &snapshot)
The debounced motion state changed.
virtual void on_detector_changed(const RuntimeSnapshot &snapshot)
The active detector changed.
virtual void on_calibration_started(const RuntimeSnapshot &snapshot)
Startup calibration began; detection results are not valid yet.
virtual void on_threshold_changed(const RuntimeSnapshot &snapshot)
The active threshold changed, from a control call, calibration, or detector-driven adaptation such as...
virtual void on_runtime_fault(const char *message)
A runtime-owned failure your firmware should surface.
virtual void on_periodic_update(const RuntimeSnapshot &snapshot, uint32_t packets_received)
Heartbeat, emitted every RuntimeConfig::publish_interval_ms milliseconds.
A consistent view of the sensing state at one instant.