1use crate::state::{
2 AppState, CollectionMode, CsiDeliveryMode, HT40_OPTIONS, LogMode, OutputMode, PHY_RATES,
3 UserIntent, WiFiMode,
4};
5
6pub fn render(ui: &mut egui::Ui, state: &mut AppState) {
8 ui.heading("Configuration");
9 ui.separator();
10
11 egui::ScrollArea::vertical()
12 .auto_shrink([false, false])
13 .show(ui, |ui| {
14 render_body(ui, state);
15 });
16}
17
18fn render_body(ui: &mut egui::Ui, state: &mut AppState) {
19 ui.collapsing("Wi-Fi", |ui| {
20 ui.horizontal_wrapped(|ui| {
21 ui.label("Mode");
22 wifi_mode_picker(ui, &mut state.persistent.wifi.mode);
23 });
24
25 ui.horizontal_wrapped(|ui| {
26 ui.label("STA SSID");
27 ui.add(
28 egui::TextEdit::singleline(&mut state.persistent.wifi.sta_ssid)
29 .desired_width(220.0),
30 );
31 });
32
33 ui.horizontal_wrapped(|ui| {
34 ui.label("STA Password");
35 ui.add(
36 egui::TextEdit::singleline(&mut state.persistent.wifi.sta_password)
37 .password(true)
38 .desired_width(220.0),
39 );
40 });
41
42 ui.horizontal_wrapped(|ui| {
43 ui.label("Channel");
44 ui.add(
45 egui::TextEdit::singleline(&mut state.persistent.wifi.channel)
46 .desired_width(60.0),
47 );
48 });
49
50 ui.horizontal_wrapped(|ui| {
51 ui.label("Peer MAC (ESP-NOW)");
52 ui.add(
53 egui::TextEdit::singleline(&mut state.persistent.wifi.peer_mac)
54 .hint_text("auto")
55 .desired_width(160.0),
56 );
57 ui.label("HT40");
58 ht40_picker(ui, &mut state.persistent.wifi.ht40);
59 });
60 ui.add(
61 egui::Label::new(
62 "Peer MAC / HT40 apply to esp-now-central / esp-now-peripheral only; \
63 leave Peer MAC empty for automatic pairing.",
64 )
65 .wrap(),
66 );
67
68 if ui.button("Apply Wi-Fi Config").clicked() {
69 state.push_intent(UserIntent::SetWifi(state.persistent.wifi.clone()));
70 }
71 });
72
73 ui.separator();
74
75 ui.collapsing("Traffic", |ui| {
76 ui.horizontal_wrapped(|ui| {
77 ui.label("Frequency (Hz)");
78 ui.add(
79 egui::TextEdit::singleline(&mut state.persistent.traffic.frequency_hz)
80 .desired_width(100.0),
81 );
82 if ui.button("Apply Traffic Config").clicked() {
83 state.push_intent(UserIntent::SetTraffic(state.persistent.traffic.clone()));
84 }
85 });
86 });
87
88 ui.separator();
89
90 ui.collapsing("CSI Flags", |ui| {
91 ui.horizontal_wrapped(|ui| {
92 ui.checkbox(&mut state.persistent.csi.disable_lltf, "disable_lltf");
93 ui.checkbox(&mut state.persistent.csi.disable_htltf, "disable_htltf");
94 ui.checkbox(
95 &mut state.persistent.csi.disable_stbc_htltf,
96 "disable_stbc_htltf",
97 );
98 ui.checkbox(
99 &mut state.persistent.csi.disable_ltf_merge,
100 "disable_ltf_merge",
101 );
102 ui.checkbox(&mut state.persistent.csi.disable_csi, "disable_csi");
103 ui.checkbox(
104 &mut state.persistent.csi.disable_csi_legacy,
105 "disable_csi_legacy",
106 );
107 ui.checkbox(
108 &mut state.persistent.csi.disable_csi_ht20,
109 "disable_csi_ht20",
110 );
111 ui.checkbox(
112 &mut state.persistent.csi.disable_csi_ht40,
113 "disable_csi_ht40",
114 );
115 ui.checkbox(&mut state.persistent.csi.disable_csi_su, "disable_csi_su");
116 ui.checkbox(&mut state.persistent.csi.disable_csi_mu, "disable_csi_mu");
117 ui.checkbox(&mut state.persistent.csi.disable_csi_dcm, "disable_csi_dcm");
118 ui.checkbox(
119 &mut state.persistent.csi.disable_csi_beamformed,
120 "disable_csi_beamformed",
121 );
122 });
123
124 ui.horizontal_wrapped(|ui| {
125 ui.label("csi_he_stbc (u32)");
126 ui.add(
127 egui::TextEdit::singleline(&mut state.persistent.csi.csi_he_stbc)
128 .desired_width(80.0),
129 );
130 ui.label("val_scale_cfg (u32)");
131 ui.add(
132 egui::TextEdit::singleline(&mut state.persistent.csi.val_scale_cfg)
133 .desired_width(80.0),
134 );
135 });
136
137 if ui.button("Apply CSI Config").clicked() {
138 state.push_intent(UserIntent::SetCsi(state.persistent.csi.clone()));
139 }
140 });
141
142 ui.separator();
143
144 ui.collapsing("PHY Rate (ESP-NOW only)", |ui| {
145 ui.horizontal_wrapped(|ui| {
146 ui.label("Rate");
147 phy_rate_picker(ui, &mut state.persistent.phy_rate.rate);
148 if ui.button("Apply Rate").clicked() {
149 state.push_intent(UserIntent::SetPhyRate(state.persistent.phy_rate.clone()));
150 }
151 });
152 ui.add(
153 egui::Label::new(
154 "Honored by esp-now-central / esp-now-peripheral; ignored by station/sniffer.",
155 )
156 .wrap(),
157 );
158 });
159
160 ui.separator();
161
162 ui.collapsing("IO Tasks", |ui| {
163 ui.horizontal_wrapped(|ui| {
164 ui.checkbox(&mut state.persistent.io_tasks.tx, "TX (traffic generation)");
165 ui.checkbox(&mut state.persistent.io_tasks.rx, "RX (CSI capture)");
166 });
167 if ui.button("Apply IO Tasks").clicked() {
168 state.push_intent(UserIntent::SetIoTasks(state.persistent.io_tasks.clone()));
169 }
170 });
171
172 ui.separator();
173
174 ui.collapsing("CSI Delivery", |ui| {
175 ui.horizontal_wrapped(|ui| {
176 ui.label("Mode");
177 csi_delivery_picker(ui, &mut state.persistent.csi_delivery.mode);
178 ui.checkbox(
179 &mut state.persistent.csi_delivery.logging,
180 "inline UART logging",
181 );
182 });
183 if ui.button("Apply CSI Delivery").clicked() {
184 state.push_intent(UserIntent::SetCsiDelivery(
185 state.persistent.csi_delivery.clone(),
186 ));
187 }
188 });
189
190 ui.separator();
191
192 ui.horizontal_wrapped(|ui| {
193 ui.label("Collection Mode");
194 collection_mode_picker(ui, &mut state.persistent.collection_mode);
195 if ui.button("Apply").clicked() {
196 state.push_intent(UserIntent::SetCollectionMode(
197 state.persistent.collection_mode,
198 ));
199 }
200 });
201
202 ui.horizontal_wrapped(|ui| {
203 ui.label("Log Mode");
204 log_mode_picker(ui, &mut state.persistent.log_mode);
205 if ui.button("Apply").clicked() {
206 state.push_intent(UserIntent::SetLogMode(state.persistent.log_mode));
207 }
208 });
209
210 ui.horizontal_wrapped(|ui| {
211 ui.label("Output Mode");
212 output_mode_picker(ui, &mut state.persistent.output_mode);
213 if ui.button("Apply").clicked() {
214 state.push_intent(UserIntent::SetOutputMode(state.persistent.output_mode));
215 }
216 });
217
218 ui.horizontal_wrapped(|ui| {
219 if ui.button("Reset Config Defaults").clicked() {
220 state.push_intent(UserIntent::ResetConfig);
221 }
222
223 if ui.button("Refresh Config").clicked() {
224 state.push_intent(UserIntent::FetchConfig);
225 }
226 });
227}
228
229fn wifi_mode_picker(ui: &mut egui::Ui, mode: &mut WiFiMode) {
230 egui::ComboBox::from_id_salt("wifi_mode_combo")
231 .selected_text(mode.as_api_value())
232 .show_ui(ui, |ui| {
233 ui.selectable_value(mode, WiFiMode::Station, "station");
234 ui.selectable_value(mode, WiFiMode::Sniffer, "sniffer");
235 ui.selectable_value(mode, WiFiMode::EspNowCentral, "esp-now-central");
236 ui.selectable_value(mode, WiFiMode::EspNowPeripheral, "esp-now-peripheral");
237 });
238}
239
240fn collection_mode_picker(ui: &mut egui::Ui, mode: &mut CollectionMode) {
241 egui::ComboBox::from_id_salt("collection_mode_combo")
242 .selected_text(mode.as_api_value())
243 .show_ui(ui, |ui| {
244 ui.selectable_value(mode, CollectionMode::Collector, "collector");
245 ui.selectable_value(mode, CollectionMode::Listener, "listener");
246 });
247}
248
249fn log_mode_picker(ui: &mut egui::Ui, mode: &mut LogMode) {
250 egui::ComboBox::from_id_salt("log_mode_combo")
251 .selected_text(mode.as_api_value())
252 .show_ui(ui, |ui| {
253 ui.selectable_value(mode, LogMode::Text, "text");
254 ui.selectable_value(mode, LogMode::ArrayList, "array-list");
255 ui.selectable_value(mode, LogMode::Serialized, "serialized");
256 ui.selectable_value(mode, LogMode::EspCsiTool, "esp-csi-tool");
257 });
258}
259
260fn output_mode_picker(ui: &mut egui::Ui, mode: &mut OutputMode) {
261 egui::ComboBox::from_id_salt("output_mode_combo")
262 .selected_text(mode.as_api_value())
263 .show_ui(ui, |ui| {
264 ui.selectable_value(mode, OutputMode::Stream, "stream");
265 ui.selectable_value(mode, OutputMode::Dump, "dump");
266 ui.selectable_value(mode, OutputMode::Both, "both");
267 });
268}
269
270fn csi_delivery_picker(ui: &mut egui::Ui, mode: &mut CsiDeliveryMode) {
271 egui::ComboBox::from_id_salt("csi_delivery_combo")
272 .selected_text(mode.as_api_value())
273 .show_ui(ui, |ui| {
274 ui.selectable_value(mode, CsiDeliveryMode::Off, "off");
275 ui.selectable_value(mode, CsiDeliveryMode::Callback, "callback");
276 ui.selectable_value(mode, CsiDeliveryMode::Async, "async");
277 ui.selectable_value(mode, CsiDeliveryMode::Raw, "raw");
278 });
279}
280
281fn ht40_picker(ui: &mut egui::Ui, ht40: &mut String) {
282 egui::ComboBox::from_id_salt("ht40_combo")
283 .selected_text(ht40.as_str())
284 .show_ui(ui, |ui| {
285 for option in HT40_OPTIONS {
286 ui.selectable_value(ht40, (*option).to_owned(), *option);
287 }
288 });
289}
290
291fn phy_rate_picker(ui: &mut egui::Ui, rate: &mut String) {
292 egui::ComboBox::from_id_salt("phy_rate_combo")
293 .selected_text(rate.as_str())
294 .show_ui(ui, |ui| {
295 for option in PHY_RATES {
296 ui.selectable_value(rate, (*option).to_owned(), *option);
297 }
298 });
299}