ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mdns_adaptive.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 mdns_adaptive.h
6 * @brief Adaptive mDNS beacon scheduling: RF-aware backoff, TTL refresher, auto-sleep beacon
7 * (PC_ENABLE_MDNS_ADAPTIVE).
8 *
9 * The mDNS service (shipped) announces records with a TTL; caches on the network evict a record when its
10 * TTL lapses, so a device must re-announce to stay discoverable. Two pressures shape *when* to announce:
11 *
12 * - **Crowded RF**: on a busy 2.4 GHz channel, hammering announces just adds collisions. So back the
13 * announce interval off (toward a ceiling) when contention is high, and recover it toward the nominal
14 * cadence when the air is quiet.
15 * - **A continuous refresher**: re-announce at ~half the record TTL (RFC 6762 cache eviction is at TTL)
16 * so caches never lapse in steady state.
17 * - **Auto-sleep beacons**: before entering a sleep window that would run past the next refresh, announce
18 * *now* so the record survives the sleep instead of lapsing while the radio is off.
19 *
20 * These are the pure scheduling decisions - what interval, and is an announce due (incl. before a sleep).
21 * The app owns the actual mDNS transmit. Wrap-safe time math, no heap, no stdlib, host-testable.
22 */
23
24#ifndef PROTOCORE_MDNS_ADAPTIVE_H
25#define PROTOCORE_MDNS_ADAPTIVE_H
26
27#include "protocore_config.h"
28#include <stddef.h>
29#include <stdint.h>
30
31#if PC_ENABLE_MDNS_ADAPTIVE
32
33/** @brief Adaptive beacon state. */
34struct MdnsBeacon
35{
36 uint32_t base_ms; ///< nominal refresh cadence (e.g. TTL/2), and the backoff floor.
37 uint32_t max_ms; ///< backoff ceiling under heavy contention.
38 uint32_t cur_ms; ///< current adaptive interval.
39 uint16_t hi_thresh; ///< contention count at/above which the interval backs off.
40};
41
42/** @brief The continuous-refresher cadence for a record TTL: half the TTL, in milliseconds. */
43uint32_t pc_mdns_refresh_interval(uint32_t ttl_s);
44
45/** @brief Initialize a beacon. @p cur_ms starts at @p base_ms. */
46void pc_mdns_beacon_init(MdnsBeacon *b, uint32_t base_ms, uint32_t max_ms, uint16_t hi_thresh);
47
48/**
49 * @brief Adapt the interval to observed RF contention (announces/collisions seen in the last window).
50 * contention >= hi_thresh doubles the interval (capped at max_ms); contention == 0 halves it back
51 * toward base_ms; moderate contention holds.
52 * @return the new interval (ms).
53 */
54uint32_t pc_mdns_beacon_adapt(MdnsBeacon *b, uint16_t contention);
55
56/** @brief Is an announce due now? (wrap-safe: elapsed since @p last_ms >= the current interval). */
57bool pc_mdns_beacon_due(const MdnsBeacon *b, uint32_t last_ms, uint32_t now_ms);
58
59/**
60 * @brief Auto-sleep beacon: should we announce *before* sleeping for @p sleep_ms?
61 * True when the elapsed-since-last plus the sleep would meet/exceed the interval - i.e. the record
62 * would lapse mid-sleep - so we refresh proactively before the radio goes off.
63 */
64bool pc_mdns_beacon_presleep_due(const MdnsBeacon *b, uint32_t last_ms, uint32_t now_ms, uint32_t sleep_ms);
65
66// ---------------------------------------------------------------------------
67// Contention sampling
68// ---------------------------------------------------------------------------
69
70/**
71 * @brief Turns a free-running frame counter into a per-window contention value.
72 *
73 * The RF-contention signal is "how many 802.11 frames went by in the last window". A promiscuous
74 * capture only offers a monotonic running total, so this converts that total into a delta over a
75 * fixed window and clamps it to the uint16 the adapt step takes. Pure and wrap-safe (the counter and
76 * the clock both wrap), so the whole sampling policy is host-testable with synthetic inputs.
77 */
78struct MdnsContentionWindow
79{
80 uint32_t last_count; ///< frame-counter value at the last emitted sample.
81 uint32_t last_ms; ///< time of the last emitted sample.
82 uint32_t window_ms; ///< how long a sampling window is.
83};
84
85/** @brief Start sampling at @p now_ms, anchored to the current counter @p frames_now. */
86void pc_mdns_contention_init(MdnsContentionWindow *w, uint32_t window_ms, uint32_t frames_now, uint32_t now_ms);
87
88/**
89 * @brief If a window has elapsed, report the frames counted in it and start the next window.
90 *
91 * @param frames_now the current running frame total (monotonic; a wrap is handled).
92 * @param out receives the frame count for the window (saturated at 0xFFFF).
93 * @return true when a window closed and @p out was written; false if the window is not up yet.
94 */
95bool pc_mdns_contention_sample(MdnsContentionWindow *w, uint32_t frames_now, uint32_t now_ms, uint16_t *out);
96
97#if defined(ARDUINO) && PC_ENABLE_MDNS && PC_ENABLE_PROMISC
98// ---------------------------------------------------------------------------
99// Device binding (needs PC_ENABLE_MDNS + PC_ENABLE_PROMISC)
100// ---------------------------------------------------------------------------
101//
102// Ties the three shipped pieces together on hardware: a promiscuous capture pinned to the station's
103// own channel supplies the contention count, the beacon scheduler turns that into an announce
104// interval, and the announce is a TXT re-apply on the running mDNS responder (which re-announces on
105// every PCB with no goodbye and no re-probe, so it refreshes the record rather than evicting it).
106//
107// Promiscuous capture is a radio-layer callback, not a socket, so unlike a second bind on UDP 5353
108// it does not turn the responder announce-only - verified on hardware (the record keeps resolving in
109// avahi while the capture runs). It is still the whole radio: this owns promiscuous mode, so it
110// cannot run at the same time as the wifi_sniffer live channel-hopping binding.
111
112/** @brief What to advertise and how aggressively to adapt. */
113struct MdnsAdaptiveCfg
114{
115 const char *key; ///< TXT key re-applied to re-announce (must already exist on the service).
116 const char *value; ///< its value (re-applied unchanged; this is the no-bye refresh).
117 uint32_t ttl_s; ///< record TTL; the base cadence is TTL/2.
118 uint32_t max_interval_ms; ///< requested backoff ceiling; capped at ~7/8 of the TTL so the most
119 ///< backed-off refresh still beats cache eviction (a longer TTL buys range).
120 uint16_t hi_contention; ///< frames-per-window at/above which the interval backs off.
121 uint32_t window_ms; ///< contention sampling window (0 => a 1000 ms default).
122};
123
124/**
125 * @brief Start adaptive announcing: begin promiscuous capture on the station's current channel and
126 * arm the beacon. Call after the mDNS service is up and the station is associated.
127 * @return false if the station is not associated or promiscuous capture could not start.
128 */
129bool pc_mdns_adaptive_begin(const MdnsAdaptiveCfg *cfg);
130
131/**
132 * @brief Advance the schedule: sample contention, adapt the interval, follow the station's channel
133 * if it roamed, and re-announce when due. Cheap; call every loop.
134 */
135void pc_mdns_adaptive_tick(void);
136
137/** @brief Stop adaptive announcing and release promiscuous mode. */
138void pc_mdns_adaptive_end(void);
139
140/** @brief Current adaptive announce interval (ms) - for a diagnostics panel. */
141uint32_t pc_mdns_adaptive_interval_ms(void);
142
143/** @brief Frames counted in the most recently closed window - the live contention signal. */
144uint16_t pc_mdns_adaptive_contention(void);
145
146/** @brief Total announces sent since begin(). */
147uint32_t pc_mdns_adaptive_announces(void);
148#endif // ARDUINO && PC_ENABLE_MDNS && PC_ENABLE_PROMISC
149
150#endif // PC_ENABLE_MDNS_ADAPTIVE
151#endif // PROTOCORE_MDNS_ADAPTIVE_H
User-facing configuration for ProtoCore.