1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! ESP-NOW PHY forcing (per-peer rate / HT40) and radio bring-up helpers.
//!
//! esp-radio doesn't expose `esp_now_set_peer_rate_config`, the only API that
//! actually forces the ESP-NOW frame PHY (rate + HT bandwidth), so we bind it
//! directly here alongside the startup helpers that get the radio into the
//! state that binding requires.
use WifiPhyRate;
use StationConfig;
use ;
use cratelog_ln;
/// Take over esp-radio's ESP-NOW receive dispatcher as early as possible.
///
/// `esp_radio::wifi::new` eagerly builds `EspNow` (via `EspNow::new_internal`),
/// which calls `esp_now_init()`, registers esp-radio's heap-allocating
/// `rcv_cb`, and adds a broadcast peer. From that instant — and `esp_rtos`
/// is already running — every overheard ESP-NOW vendor action frame is
/// `Box`ed and `push_back`ed into a heap-backed `VecDeque<ReceivedData>`
/// that nothing in this crate drains (our consumers read the static
/// [`crate::esp_now_pool`] queue instead). Any blocking Wi-Fi call during
/// startup (`set_protocols`, `set_csi`) gives that callback time to fire and
/// grow the VecDeque; on the small ESP32-S3 heap the next grow allocation
/// can't be satisfied → `handle_alloc_error` panic *inside esp-radio's
/// `rcv_cb`*, before our pool was ever installed.
///
/// Calling this *first* in `run`/`run_duration`, before any other Wi-Fi
/// reconfiguration, closes that startup window:
/// - non-sniffer modes get our static-pool `rcv_cb` (no heap, ever);
/// - sniffer mode drops the callback entirely so overheard frames are
/// discarded at the C layer with zero allocation (sniffers never consume
/// ESP-NOW data).
pub
/// Bring the radio up in **started STA mode** for the ESP-NOW HT40 path.
///
/// ESP-NOW PHY configuration only takes effect on a started STA interface:
/// esp-radio applies the ESP-NOW rate to `WIFI_IF_STA`
/// (`esp_wifi_config_espnow_rate`) and gates `set_bandwidths` on STA/AP mode,
/// and `esp_wifi_start()` runs *only* inside `set_config`. Without this the
/// controller stays in `WIFI_MODE_NULL`, so `set_rate`/`set_bandwidths` are
/// silent no-ops and frames go out legacy / 20 MHz regardless of
/// `with_ht40()` + `set_rate()` (confirmed via the `bw_check` example).
///
/// We do not associate to an AP — an unassociated STA is all ESP-NOW needs
/// (this mirrors the ESP-IDF reference, which runs ESP-NOW in `WIFI_MODE_STA`).
/// Best-effort: on error we log and continue (the run still works, just at the
/// default PHY). `set_config` restarts the radio, so the static-pool ESP-NOW
/// receive callback is re-installed afterward.
pub
/// Put the radio on an HT40-capable channel (primary + secondary). This is
/// necessary but **not sufficient** for HT40 ESP-NOW: the PPDU bandwidth/rate
/// is set per-peer via [`set_peer_espnow_phy`], not by the interface bandwidth.
pub
// ESP-NOW per-peer TX rate config (ESP-IDF `esp_now_set_peer_rate_config`).
// This is the ONLY way to force the ESP-NOW frame PHY (rate + HT bandwidth):
// it's a per-peer property, not the interface bandwidth/rate (which esp-radio
// sets via the deprecated `esp_wifi_config_espnow_rate`, a no-op on IDF v5.5).
// esp-radio doesn't expose this, so we bind it directly (same pattern as the
// pool's `esp_now_register_recv_cb`). `esp_now_rate_config_t == wifi_tx_rate_config_t`.
// wifi_phy_mode_t values (esp-wifi-sys).
const WIFI_PHY_MODE_11B: u32 = 1;
const WIFI_PHY_MODE_11G: u32 = 2;
const WIFI_PHY_MODE_HT20: u32 = 4;
const WIFI_PHY_MODE_HT40: u32 = 5;
unsafe extern "C"
/// Map esp-radio's contiguous [`WifiPhyRate`] discriminant to the C
/// `wifi_phy_rate_t` value. The C enum reserves `0x04`, so everything from
/// `Rate2mS` (esp-radio 4) up is shifted by one; the two LoRa rates jump to
/// 41/42. Without this fix-up, MCS rates would be off by one (e.g. esp-radio
/// `RateMcs0Lgi` = 15 vs C `WIFI_PHY_RATE_MCS0_LGI` = 16).
/// Derive the ESP-NOW `phymode` from the rate and the HT40 secondary-channel
/// choice. MCS rates (C 16–31) are HT → HT40 if a secondary channel is set,
/// else HT20. Legacy DSSS rates (C 0–7) are 11b; legacy OFDM (8–15) is 11g.
/// Force a peer's ESP-NOW TX PHY to the configured `rate` and bandwidth
/// (HT40 when `secondary` is set, else HT20/legacy per the rate). Must be
/// called after `esp_wifi_start()`, `esp_now_init` (done in `wifi::new`), and
/// the peer being added. Best-effort: a non-zero return is logged, not fatal.
///
/// The library applies this automatically for `EspNowConfig`-driven nodes; it's
/// public so raw-ESP-NOW examples (e.g. the CPU-test TX) can match the same PHY
/// after bringing the radio up in started STA mode (`Config::Station`) and
/// setting an HT40 channel.