embedded_wg_display/
main.rs1#![no_std]
2#![no_main]
3#![feature(impl_trait_in_assoc_type)]
4#![deny(
5 clippy::mem_forget,
6 reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
7 holding buffers for the duration of a data transfer."
8)]
9#![deny(clippy::large_stack_frames)]
10#![recursion_limit = "256"]
11
12use defmt::error;
13use defmt::info;
14use embassy_executor::Spawner;
15use embassy_time::{Duration, Timer};
16use esp_hal::clock::CpuClock;
17use esp_hal::gpio::{Input, InputConfig, Pull};
18use esp_hal::interrupt::software::SoftwareInterruptControl;
19use esp_hal::system::Stack as CoreStack;
20use esp_hal::system::software_reset;
21use esp_hal::timer::timg::TimerGroup;
22use esp_println as _;
23use esp_rtos::embassy::Executor;
24use static_cell::StaticCell;
25
26mod wifi;
27use crate::display::DisplayPeripherals;
28use crate::wifi::Wifi;
29
30mod util;
31mod widget;
32use crate::util::globals;
33
34mod display;
35use crate::display::Display;
36
37mod runtime;
38
39mod storage;
40use crate::storage::Storage;
41
42mod renderer;
43
44mod http_client;
45
46mod http_server;
47
48use crate::alloc::string::ToString;
49use crate::util::esptime::EspTime;
50
51#[panic_handler]
52fn panic(info: &core::panic::PanicInfo) -> ! {
53 if let Some(location) = info.location() {
54 error!(
55 "Panic occurred at {=str}:{=u32}",
56 location.file(),
57 location.line()
58 );
59 } else {
60 error!("Panic occurred at unknown location");
61 }
62
63 esp_println::println!("panic info: {}", info);
64
65 software_reset();
66}
67
68extern crate alloc;
69
70esp_bootloader_esp_idf::esp_app_desc!();
73
74#[allow(
75 clippy::large_stack_frames,
76 reason = "it's not unusual to allocate larger buffers etc. in main"
77)]
78#[esp_rtos::main]
79async fn main(spawner: Spawner) -> ! {
80 let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
83 let peripherals = esp_hal::init(config);
84
85 esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram);
87 esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 73744);
88 let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
92
93 let timg0 = TimerGroup::new(peripherals.TIMG0);
94 esp_rtos::start(timg0.timer0);
96
97 info!("Embassy initialized!");
98
99 let storage =
101 Storage::new(peripherals.FLASH, peripherals.SHA).expect("Failed to initialize storage");
102 globals::init_storage(storage).await;
103
104 let display = Display::new(DisplayPeripherals {
106 spi2: peripherals.SPI2,
107 dma_ch0: peripherals.DMA_CH0,
108 gpio4: peripherals.GPIO4,
109 gpio5: peripherals.GPIO5,
110 gpio6: peripherals.GPIO6,
111 gpio7: peripherals.GPIO7,
112 gpio47: peripherals.GPIO47,
113 gpio48: peripherals.GPIO48,
114 });
115
116 globals::init_display(display).await;
117
118 globals::console_println("WG-Display starting up").await;
119
120 let wifi_creds = globals::with_storage(|storage| storage.get_wifi_credentials()).await;
122
123 let wifi_mode = globals::with_storage(|storage| storage.config_get("wifi_mode"))
126 .await
127 .unwrap_or_else(|_| alloc::string::String::from("ap"));
128 let force_ap_mode = wifi_mode == "ap";
129
130 let wifi_peripheral = peripherals.WIFI;
131
132 spawner.spawn(reset_button_poll(peripherals.GPIO0)).ok();
133
134 if !force_ap_mode {
136 match wifi_creds {
138 Ok(creds) => {
139 start_station_mode(wifi_peripheral, &spawner, creds.ssid, creds.password).await;
140 static APP_CORE_STACK: StaticCell<CoreStack<32768>> = StaticCell::new();
142 let app_core_stack = APP_CORE_STACK.init(CoreStack::new());
143
144 esp_rtos::start_second_core(
145 peripherals.CPU_CTRL,
146 sw_int.software_interrupt0,
147 sw_int.software_interrupt1,
148 app_core_stack,
149 || {
150 static CORE1_EXECUTOR: StaticCell<Executor> = StaticCell::new();
151 let executor = CORE1_EXECUTOR.init(Executor::new());
152
153 executor.run(|core1_spawner| {
154 core1_spawner
155 .spawn(widget_runner())
156 .expect("Failed to spawn widget runner on core1");
157 info!("Widget runner task spawned on core1");
158 });
159 },
160 );
161 }
162 Err(_) => {
163 info!("WiFi credentials not found in storage, starting in AP mode");
164 start_ap_mode(wifi_peripheral, &spawner).await;
165 }
166 }
167 } else {
168 info!("WiFi mode is set to AP, starting in AP mode");
169 start_ap_mode(wifi_peripheral, &spawner).await;
170 }
171
172 loop {
173 Timer::after(Duration::from_millis(500)).await;
174 }
175}
176
177async fn start_ap_mode(wifi_peripheral: esp_hal::peripherals::WIFI<'static>, spawner: &Spawner) {
178 globals::console_println("No wifi configured, starting in AP mode").await;
179 let _ = globals::with_storage(|storage| storage.config_set("wifi_mode", "ap")).await;
180 let wifi = Wifi::start_station(wifi_peripheral, spawner, "".into(), "".into(), true);
181
182 http_server::start(wifi.stack(), wifi.tls_seed(), spawner);
184 globals::console_println("Connect to 'WG-Display-AP'").await;
185 globals::console_println("and open 192.168.2.1").await;
186}
187
188async fn start_station_mode(
189 wifi_peripheral: esp_hal::peripherals::WIFI<'static>,
190 spawner: &Spawner,
191 ssid: alloc::string::String,
192 password: alloc::string::String,
193) {
194 globals::console_println("Starting in station mode").await;
195 let _ = globals::with_storage(|storage| storage.config_set("wifi_mode", "station")).await;
196 let wifi = Wifi::start_station(wifi_peripheral, spawner, ssid, password, false);
197 let ip = wifi.wait_for_connection().await;
198 let _ = globals::with_storage(|storage| storage.config_set("device_ip", &ip.to_string())).await;
199
200 spawner
202 .spawn(runtime::http_sync::http_handler_task(
203 wifi.stack(),
204 wifi.tls_seed(),
205 ))
206 .expect("Failed to spawn HTTP handler task");
207 info!("HTTP handler task spawned on core0 executor");
208
209 let mut widget_store = widget::store::WidgetStore::new();
211 widget_store
212 .fetch_from_store()
213 .await
214 .expect("Failed to fetch widget store");
215 globals::init_store(widget_store).await;
216
217 http_server::start(wifi.stack(), wifi.tls_seed(), spawner);
219
220 let mut esp_time = EspTime::new();
221 esp_time.fetch_time().await;
222 globals::init_time(esp_time);
223 info!("Global time synced from time API");
224}
225
226#[embassy_executor::task]
228async fn widget_runner() {
229 info!("Widget runner task started");
230
231 let mut renderer = renderer::Renderer::new();
232 renderer.run().await;
234}
235
236#[embassy_executor::task]
237async fn reset_button_poll(gpio0: esp_hal::peripherals::GPIO0<'static>) -> ! {
238 let boot_button = Input::new(gpio0, InputConfig::default().with_pull(Pull::Up));
239 let mut boot_button_timer = 0;
240
241 loop {
242 if boot_button.is_low() {
243 boot_button_timer += 1;
244 if boot_button_timer >= 100 {
245 globals::console_println("Boot button held, resetting WiFi settings").await;
246 let _ =
247 globals::with_storage(|storage| storage.config_set("wifi_mode", "ap")).await;
248 software_reset();
249 }
250 }
251
252 if boot_button.is_high() && boot_button_timer != 0 {
253 boot_button_timer = 0;
254 }
255
256 Timer::after(Duration::from_millis(50)).await;
257 }
258}