Skip to main content

csi_webclient/ui/
config.rs

1use crate::state::{
2    AppState, CollectionMode, CsiDeliveryMode, HT40_OPTIONS, LogMode, OutputMode, PHY_RATES,
3    UserIntent, WiFiMode,
4};
5
6/// Render the configuration view.
7pub 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}