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::interrupt::software::SoftwareInterruptControl;
18use esp_hal::system::Stack as CoreStack;
19use esp_hal::system::software_reset;
20use esp_hal::timer::timg::TimerGroup;
21use esp_println as _;
22use esp_rtos::embassy::Executor;
23use static_cell::StaticCell;
24
25mod wifi;
26use crate::display::DisplayPeripherals;
27use crate::wifi::Wifi;
28
29mod util;
30mod widget;
31use crate::util::globals;
32
33mod display;
34use crate::display::Display;
35
36mod runtime;
37
38mod storage;
39use crate::storage::Storage;
40
41mod renderer;
42
43mod http_client;
44
45mod http_server;
46
47use crate::alloc::string::ToString;
48use crate::util::esptime::EspTime;
49
50#[panic_handler]
51fn panic(info: &core::panic::PanicInfo) -> ! {
52 if let Some(location) = info.location() {
53 error!(
54 "Panic occurred at {=str}:{=u32}",
55 location.file(),
56 location.line()
57 );
58 } else {
59 error!("Panic occurred at unknown location");
60 }
61
62 esp_println::println!("panic info: {}", info);
63
64 software_reset();
65}
66
67extern crate alloc;
68
69esp_bootloader_esp_idf::esp_app_desc!();
72
73#[allow(
74 clippy::large_stack_frames,
75 reason = "it's not unusual to allocate larger buffers etc. in main"
76)]
77#[esp_rtos::main]
78async fn main(spawner: Spawner) -> ! {
79 let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
82 let peripherals = esp_hal::init(config);
83
84 esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram);
86 esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 73744);
87 let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
91
92 let timg0 = TimerGroup::new(peripherals.TIMG0);
93 esp_rtos::start(timg0.timer0);
95
96 info!("Embassy initialized!");
97
98 let storage =
100 Storage::new(peripherals.FLASH, peripherals.SHA).expect("Failed to initialize storage");
101 globals::init_storage(storage).await;
102
103 let display = Display::new(DisplayPeripherals {
105 spi2: peripherals.SPI2,
106 dma_ch0: peripherals.DMA_CH0,
107 gpio4: peripherals.GPIO4,
108 gpio5: peripherals.GPIO5,
109 gpio6: peripherals.GPIO6,
110 gpio7: peripherals.GPIO7,
111 gpio47: peripherals.GPIO47,
112 gpio48: peripherals.GPIO48,
113 });
114
115 globals::init_display(display).await;
116
117 globals::console_println("WG-Display starting up").await;
118
119 let ssid = globals::with_storage(|storage| storage.config_get("ssid")).await;
121 let password = globals::with_storage(|storage| storage.config_get("pw")).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 if !force_ap_mode {
133 if let (Ok(ssid), Ok(password)) = (ssid, password) {
134 start_station_mode(wifi_peripheral, &spawner, ssid, password).await;
135
136 static APP_CORE_STACK: StaticCell<CoreStack<32768>> = StaticCell::new();
138 let app_core_stack = APP_CORE_STACK.init(CoreStack::new());
139
140 esp_rtos::start_second_core(
141 peripherals.CPU_CTRL,
142 sw_int.software_interrupt0,
143 sw_int.software_interrupt1,
144 app_core_stack,
145 || {
146 static CORE1_EXECUTOR: StaticCell<Executor> = StaticCell::new();
147 let executor = CORE1_EXECUTOR.init(Executor::new());
148
149 executor.run(|core1_spawner| {
150 core1_spawner
151 .spawn(widget_runner())
152 .expect("Failed to spawn widget runner on core1");
153 info!("Widget runner task spawned on core1");
154 });
155 },
156 );
157 } else {
158 info!("WiFi credentials not configured, switching to AP mode");
159 start_ap_mode(wifi_peripheral, &spawner).await;
160 }
161 } else {
162 info!("WiFi mode is set to AP, starting in AP mode");
163 start_ap_mode(wifi_peripheral, &spawner).await;
164 }
165
166 loop {
171 Timer::after(Duration::from_secs(100)).await;
172 }
173}
174
175async fn start_ap_mode(wifi_peripheral: esp_hal::peripherals::WIFI<'static>, spawner: &Spawner) {
176 globals::console_println("No wifi configured, starting in AP mode").await;
177 let _ = globals::with_storage(|storage| storage.config_set("wifi_mode", "ap")).await;
178 let wifi = Wifi::start_station(wifi_peripheral, spawner, "".into(), "".into(), true);
179
180 http_server::start(wifi.stack(), wifi.tls_seed(), spawner);
182 globals::console_println("Connect to 'WG-Display-AP'").await;
183 globals::console_println("and open 192.168.2.1").await;
184}
185
186async fn start_station_mode(
187 wifi_peripheral: esp_hal::peripherals::WIFI<'static>,
188 spawner: &Spawner,
189 ssid: alloc::string::String,
190 password: alloc::string::String,
191) {
192 globals::console_println("Starting in station mode").await;
193 let _ = globals::with_storage(|storage| storage.config_set("wifi_mode", "station")).await;
194 let wifi = Wifi::start_station(wifi_peripheral, spawner, ssid, password, false);
195 let ip = wifi.wait_for_connection().await;
196 let _ = globals::with_storage(|storage| storage.config_set("device_ip", &ip.to_string())).await;
197
198 spawner
200 .spawn(runtime::http_sync::http_handler_task(
201 wifi.stack(),
202 wifi.tls_seed(),
203 ))
204 .expect("Failed to spawn HTTP handler task");
205 info!("HTTP handler task spawned on core0 executor");
206
207 let mut widget_store = widget::store::WidgetStore::new();
209 widget_store
210 .fetch_from_store()
211 .await
212 .expect("Failed to fetch widget store");
213 globals::init_store(widget_store).await;
214
215 http_server::start(wifi.stack(), wifi.tls_seed(), spawner);
217
218 let mut esp_time = EspTime::new();
219 esp_time.fetch_time().await;
220 globals::init_time(esp_time);
221 info!("Global time synced from time API");
222}
223
224#[embassy_executor::task]
226async fn widget_runner() {
227 info!("Widget runner task started");
228
229 let mut renderer = renderer::Renderer::new();
230 renderer.run().await;
232}