ESPectre SDK 2.8.0-280-gac7af68
Wi-Fi CSI motion sensing for ESP32 firmware
Loading...
Searching...
No Matches
runtime_frontend_controller.h
Go to the documentation of this file.
1/*
2 * ESPectre - Runtime Frontend Controller
3 *
4 * Owns runtime lifecycle and exposes a frontend-friendly control surface.
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 <memory>
13
15#include "runtime_events.h"
16#include "runtime_interface.h"
17#include "runtime_snapshot.h"
18
19namespace espectre {
20
21/**
22 * The recommended entry point for firmware embedding ESPectre.
23 *
24 * It owns the runtime backend, picks the right one from
25 * `RuntimeConfig::runtime_profile`, caches the latest snapshot and the
26 * discovered capabilities, and validates control calls before they reach the
27 * backend. The shipped Native and Matter frontends are thin wrappers over it.
28 *
29 * @code
30 * class ProductFrontend : public espectre::IRuntimeListener {
31 * public:
32 * bool setup() {
33 * espectre::RuntimeConfig config;
34 * config.detection_algorithm = espectre::DetectionAlgorithm::LIGHTWEIGHT;
35 * runtime_.set_config(config);
36 * return runtime_.setup(this);
37 * }
38 *
39 * void loop() { runtime_.loop(); }
40 *
41 * void on_motion_state_changed(const espectre::RuntimeSnapshot &snapshot) override {
42 * if (snapshot.ready_to_publish) publish(snapshot.motion_state);
43 * }
44 *
45 * private:
46 * espectre::RuntimeFrontendController runtime_;
47 * };
48 * @endcode
49 *
50 * @par Lifecycle
51 * `set_config()` -> `setup(listener)` -> `loop()` repeatedly -> `shutdown()`.
52 * The controller is reusable after `shutdown()`: configuration survives, and
53 * `set_config()` becomes effective again.
54 *
55 * @par Threading
56 * Carries no internal locking. Run `setup()`, `loop()`, and `shutdown()` on
57 * one task. See `espectre_sdk.h` for the full contract, including where
58 * listener callbacks land and how to handle controls driven from a transport
59 * callback.
60 *
61 * @par Control calls before setup
62 * The setters work before `setup()` and simply update the pending
63 * configuration, so a frontend can accept provisioning commands during boot
64 * without special-casing the ordering.
65 */
67 public:
68 /** Shut the runtime down on scope exit. Explicit `shutdown()` remains recommended. */
70 /**
71 * Stage the configuration used by the next `setup()`.
72 *
73 * Ignored once setup has started, so reconfiguring a running runtime means
74 * `shutdown()` first, or the `set_*_runtime()` methods for the fields that
75 * support live changes.
76 */
78 /**
79 * Mutable access to the staged configuration.
80 *
81 * Provided so a frontend can adjust individual fields before `setup()`
82 * without rebuilding the whole struct. Writing to it after setup changes
83 * only this cached copy, not the running runtime.
84 */
85 RuntimeConfig &config() { return config_; }
86 /** Read-only view of the staged configuration. */
87 const RuntimeConfig &config() const { return config_; }
88 /**
89 * Latest known snapshot, without querying the backend.
90 *
91 * Refreshed automatically at `setup()`, by control calls, and before every
92 * listener callback is forwarded to your frontend.
93 * Use the cached snapshot for on-demand reads such as answering a status
94 * query; use the listener callbacks to react to change.
95 */
96 const RuntimeSnapshot &snapshot() const { return snapshot_; }
97 /**
98 * Read backend counters without touching the cached sensing snapshot.
99 *
100 * Unlike `snapshot()`, this queries the backend on every call. Invoke it from
101 * an existing periodic sensing callback, not from the hot loop. Returns a
102 * zeroed snapshot before `setup()`.
103 */
105 /**
106 * What the active backend supports. Meaningful only after `setup()`.
107 *
108 * Gate your product surface on it rather than hardcoding: the controller
109 * already refuses capability-gated calls, and this is how you avoid exposing
110 * a control the runtime will reject.
111 */
112 const RuntimeCapabilities &capabilities() const { return capabilities_; }
113 /** True between a successful `setup()` and the next `shutdown()`. */
114 bool is_setup_complete() const { return setup_complete_; }
115
116 /**
117 * Create the backend, apply the configuration, and start sensing.
118 *
119 * Calling it twice is a no-op that returns true.
120 *
121 * @param listener Event sink, or `nullptr` for none. Not owned; it must
122 * outlive the controller.
123 * @return false when the backend cannot start, for example a
124 * `RuntimeProfile::STREAM` config in a build without the stream
125 * runtime. On failure the backend is dropped and the controller
126 * stays un-setup, so it is safe to fix the config and retry.
127 */
128 bool setup(IRuntimeListener *listener);
129 /**
130 * Advance runtime work and deliver pending listener callbacks.
131 *
132 * Call it continuously from your loop task. Safe, and a no-op, before setup.
133 */
134 void loop();
135 /** Stop sensing and release the backend. Safe before setup and to repeat. */
136 void shutdown();
137
138 /**
139 * Gate runtime-owned services without tearing the runtime down.
140 *
141 * Sticky: the value is remembered and reapplied to the backend created by a
142 * later `setup()`. Matter uses it to stay silent until commissioning.
143 * Frontends use it to pause CSI without dropping Wi-Fi.
144 */
145 void set_services_armed(bool armed);
146 /** Enable or suppress `IRuntimeListener::on_live_telemetry()`. Also sticky. */
147 void set_live_telemetry_enabled(bool enabled);
148 /** Current armed state, including before setup. */
149 bool services_armed() const { return services_armed_; }
150 /**
151 * Quiet the runtime ahead of an OTA update.
152 *
153 * Drops live telemetry and disarms services so the download is not competing
154 * with CSI capture and traffic generation. Reverse it with
155 * `set_services_armed(true)` if the update is abandoned.
156 */
158
159 /**
160 * Set the motion threshold, validating it against the active detector.
161 *
162 * @param threshold Value on the 0..1 metric scale.
163 * @return false when out of range, or when the backend refuses it. Before
164 * setup the value is staged and returns true.
165 */
166 bool set_threshold_runtime(float threshold);
167 /**
168 * Set the hit filter.
169 *
170 * @param motion_on_hits Consecutive above-threshold evaluations to report
171 * motion (1..20). Higher trades latency for fewer false positives.
172 * @param motion_off_hits Consecutive below-threshold evaluations to clear it
173 * (1..20).
174 * @return false when either value is out of range, or when the runtime is up
175 * and does not advertise
176 * `RuntimeCapabilities::supports_runtime_motion_hits_updates`.
177 */
178 bool set_motion_hits_runtime(uint8_t motion_on_hits, uint8_t motion_off_hits);
179 /**
180 * Change the live CSI traffic ownership mode.
181 *
182 * @return false when the mode is invalid, or when the runtime is up and does
183 * not advertise `RuntimeCapabilities::supports_traffic_control`.
184 */
186 /**
187 * Change the live internal traffic generator packet type.
188 *
189 * @return false when the mode is invalid, or when the runtime is up and does
190 * not advertise `RuntimeCapabilities::supports_traffic_control`.
191 */
193 /**
194 * Switch detector while running.
195 *
196 * The threshold follows the detector: the controller adopts the new
197 * detector's threshold rather than carrying the old value across scales.
198 *
199 * @return false for an unknown algorithm, or when the runtime is up and does
200 * not advertise
201 * `RuntimeCapabilities::supports_runtime_detector_selection`.
202 */
204 /**
205 * Restart startup calibration.
206 *
207 * @return false before setup, or when the backend does not advertise
208 * `RuntimeCapabilities::supports_manual_recalibration`. Success only
209 * means calibration started; the outcome arrives through
210 * `IRuntimeListener::on_calibration_finished()`.
211 */
213 /** True while the backend is calibrating. False before setup. */
214 bool is_calibrating() const;
215
216 private:
217 void on_motion_state_changed(const RuntimeSnapshot &snapshot) override;
218 void on_periodic_update(const RuntimeSnapshot &snapshot, uint32_t packets_received) override;
219 void on_threshold_changed(const RuntimeSnapshot &snapshot) override;
220 void on_detector_changed(const RuntimeSnapshot &snapshot) override;
221 void on_calibration_started(const RuntimeSnapshot &snapshot) override;
222 void on_calibration_finished(const RuntimeSnapshot &snapshot, bool success) override;
223 void on_live_telemetry(float movement, float threshold) override;
224 void on_runtime_fault(const char *message) override;
225
226 void cache_snapshot_(const RuntimeSnapshot &snapshot);
227 void begin_callback_();
228 void end_callback_();
229 void apply_deferred_shutdown_();
230
231 RuntimeConfig config_{};
232 RuntimeSnapshot snapshot_{};
233 RuntimeCapabilities capabilities_{};
234 std::unique_ptr<IEspectreRuntime> runtime_;
235 IRuntimeListener *listener_{nullptr};
236 bool setup_complete_{false};
237 bool services_armed_{true};
238 bool live_telemetry_enabled_{true};
239 uint8_t callback_depth_{0U};
240 bool shutdown_requested_{false};
241};
242
243} // namespace espectre
Everything the runtime tells your firmware.
The recommended entry point for firmware embedding ESPectre.
bool set_traffic_generator_mode_runtime(RuntimeTrafficMode mode)
Change the live internal traffic generator packet type.
RuntimeConfig & config()
Mutable access to the staged configuration.
bool set_threshold_runtime(float threshold)
Set the motion threshold, validating it against the active detector.
bool trigger_recalibration()
Restart startup calibration.
bool set_csi_traffic_mode_runtime(CsiTrafficMode mode)
Change the live CSI traffic ownership mode.
bool set_motion_hits_runtime(uint8_t motion_on_hits, uint8_t motion_off_hits)
Set the hit filter.
bool is_calibrating() const
True while the backend is calibrating.
bool setup(IRuntimeListener *listener)
Create the backend, apply the configuration, and start sensing.
void quiesce_for_ota()
Quiet the runtime ahead of an OTA update.
RuntimeDiagnosticsSnapshot diagnostics() const
Read backend counters without touching the cached sensing snapshot.
void shutdown()
Stop sensing and release the backend.
void set_config(const RuntimeConfig &config)
Stage the configuration used by the next setup().
bool services_armed() const
Current armed state, including before setup.
const RuntimeCapabilities & capabilities() const
What the active backend supports.
const RuntimeConfig & config() const
Read-only view of the staged configuration.
bool is_setup_complete() const
True between a successful setup() and the next shutdown().
void loop()
Advance runtime work and deliver pending listener callbacks.
void set_services_armed(bool armed)
Gate runtime-owned services without tearing the runtime down.
const RuntimeSnapshot & snapshot() const
Latest known snapshot, without querying the backend.
~RuntimeFrontendController() override
Shut the runtime down on scope exit.
void set_live_telemetry_enabled(bool enabled)
Enable or suppress IRuntimeListener::on_live_telemetry().
bool set_detection_algorithm_runtime(DetectionAlgorithm algorithm)
Switch detector while running.
RuntimeTrafficMode
Which packet the internal generator sends to solicit CSI from the AP.
CsiTrafficMode
Where the CSI-bearing traffic comes from.
DetectionAlgorithm
Which detector runs.
Runtime configuration and the backend contract behind it.
What a runtime actually offers its frontend.
Everything the runtime needs to know before setup().
Low-frequency counters and radio state used by optional diagnostic surfaces.
A consistent view of the sensing state at one instant.