ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
rcwl0516.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file rcwl0516.h
6 * @brief RCWL-0516 microwave Doppler presence sensor, and the shared one-GPIO presence facade
7 * (PC_ENABLE_RCWL0516).
8 *
9 * The RCWL-0516 (RCWL-9196 controller + MMBR941M RF amp, ~3.18 GHz Doppler) has no data protocol at
10 * all: a single 3.3 V **OUT** pin that latches HIGH when a moving reflector is detected and returns
11 * LOW once its own retrigger window expires. Everything interesting is therefore in *time*, not in
12 * bytes - which is what this module provides.
13 *
14 * Two problems a bare `digitalRead()` does not solve, and this does:
15 *
16 * 1. **Chatter.** The OUT pin is driven by an analog comparator, so around the detection threshold
17 * it can flicker. A raw read turns one person walking past into a burst of presence events.
18 * A level must therefore hold for @ref PresenceCore::debounce_ms before it is believed.
19 *
20 * 2. **Gaps.** The module drops OUT between retriggers, so a person who is present but briefly
21 * still reads as absent for a moment. Presence is therefore held for
22 * @ref PresenceCore::hold_ms past the last believed-HIGH sample, which turns a stream of
23 * retriggers into one continuous "occupied" span instead of a flapping boolean.
24 *
25 * The core is pure and takes an explicit @p now, exactly like `services/hotswap`: it decides, the
26 * binding acts. That makes the whole machine host-testable by injecting pin levels against a
27 * synthetic clock, with no GPIO and no real time involved. All timing comparisons are unsigned
28 * differences, so they are wrap-safe across a `millis()` rollover.
29 *
30 * @ref PresenceCore is deliberately sensor-agnostic: it is a debounced, hold-extended view of one
31 * active-high presence pin. The RCWL-0516 is simply its first user, via the
32 * @ref pc_rcwl0516_core_init defaults - the HMMD's OUT pin, a PIR, or an HB100 can reuse the same
33 * core by supplying their own two constants.
34 *
35 * Fail-safe start: a freshly initialized core reports *absent* and treats the pin as idle, so
36 * presence is only ever reported after it has actually been observed and believed. Claiming
37 * presence you have not yet measured is the failure mode worth avoiding.
38 *
39 * @author Douglas Quigg (dstroy0)
40 * @date 2026
41 */
42
43#ifndef PROTOCORE_RCWL0516_H
44#define PROTOCORE_RCWL0516_H
45
46#include "protocore_config.h"
47
48#if PC_ENABLE_RCWL0516
49
50#include <stdint.h>
51
52/**
53 * @brief Default hold time (ms) for the RCWL-0516.
54 *
55 * The module's own retrigger window is ~2 s, so holding for at least that long bridges the gap
56 * between retriggers while a target is still present.
57 */
58#ifndef PC_RCWL0516_HOLD_MS
59#define PC_RCWL0516_HOLD_MS 2000
60#endif
61
62/** @brief Default debounce (ms) for the RCWL-0516 - long enough to swallow comparator chatter. */
63#ifndef PC_RCWL0516_DEBOUNCE_MS
64#define PC_RCWL0516_DEBOUNCE_MS 50
65#endif
66
67// ---------------------------------------------------------------------------
68// Host-testable core (sensor-agnostic: any active-high presence pin)
69// ---------------------------------------------------------------------------
70
71/** @brief Debounced, hold-extended state of one active-high presence pin. Pure: it decides. */
72struct PresenceCore
73{
74 uint32_t debounce_ms; ///< a level must hold this long before it is believed.
75 uint32_t hold_ms; ///< presence persists this long past the last believed-HIGH sample.
76 uint32_t raw_since_ms; ///< when the raw pin level last changed.
77 uint32_t last_high_ms; ///< when the believed level was last HIGH.
78 uint8_t raw; ///< last raw pin level as sampled (0/1).
79 uint8_t stable; ///< believed level, after debouncing (0/1).
80 uint8_t present; ///< presence output (0/1) - @ref stable, extended by @ref hold_ms.
81 uint8_t changed; ///< set when @ref present flipped; cleared by @ref pc_presence_take_event.
82};
83
84/**
85 * @brief Initialize to *absent* at @p now, with the pin treated as idle (LOW).
86 *
87 * If the pin is in fact already HIGH, the first update starts its debounce and presence asserts once
88 * that elapses - the sensor is never assumed to be reporting something that has not been sampled.
89 *
90 * @param debounce_ms 0 disables debouncing (every sample is believed immediately).
91 * @param hold_ms 0 disables the hold (presence follows the debounced level exactly).
92 */
93void pc_presence_core_init(PresenceCore *c, uint32_t debounce_ms, uint32_t hold_ms, uint32_t now);
94
95/**
96 * @brief Feed one sample of the presence pin.
97 *
98 * Call it as often as convenient; it is level-driven, not edge-driven, so a missed poll only delays
99 * a transition rather than losing it. Sampling with a non-monotonic or repeated @p now is harmless.
100 *
101 * @return the presence state after this sample (also in @ref PresenceCore::present).
102 */
103bool pc_presence_core_update(PresenceCore *c, bool pin_high, uint32_t now);
104
105/** @brief Current presence, without sampling. */
106bool pc_presence_core_get(const PresenceCore *c);
107
108/**
109 * @brief Consume the presence-changed event.
110 * @return true exactly once per transition, so a caller can publish an event per edge rather than
111 * re-publishing a level every poll. Clears the flag.
112 */
113bool pc_presence_take_event(PresenceCore *c);
114
115// ---------------------------------------------------------------------------
116// RCWL-0516 convenience
117// ---------------------------------------------------------------------------
118
119/** @brief Initialize @p c with the RCWL-0516 defaults (@ref PC_RCWL0516_DEBOUNCE_MS / _HOLD_MS). */
120void pc_rcwl0516_core_init(PresenceCore *c, uint32_t now);
121
122// ---------------------------------------------------------------------------
123// Binding (no-ops on a host build)
124// ---------------------------------------------------------------------------
125
126/** @brief Configure @p out_pin as an input and start the core. @return true on ESP32. */
127bool pc_rcwl0516_begin(int out_pin);
128
129/** @brief Sample the pin at the current time. @return true if presence changed on this poll. */
130bool pc_rcwl0516_poll();
131
132/** @brief Latest debounced, hold-extended presence. */
133bool pc_rcwl0516_present();
134
135#endif // PC_ENABLE_RCWL0516
136#endif // PROTOCORE_RCWL0516_H
User-facing configuration for ProtoCore.