Skip to main content

embedded_wg_display/
main.rs

1#![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
69// This creates a default app-descriptor required by the esp-idf bootloader.
70// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
71esp_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    // generator version: 1.2.0
80
81    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
82    let peripherals = esp_hal::init(config);
83
84    // initalizeing PSRAM before heap fixes widget http host function access for some reason
85    esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram);
86    esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 73744);
87    //  esp_alloc::heap_allocator!(size: 73 * 1024);
88
89    // Setup software interrupts for executors
90    let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
91
92    let timg0 = TimerGroup::new(peripherals.TIMG0);
93    // Note: On Xtensa, esp_rtos::start doesn't take a software interrupt parameter
94    esp_rtos::start(timg0.timer0);
95
96    info!("Embassy initialized!");
97
98    // -- Storage setup --
99    let storage =
100        Storage::new(peripherals.FLASH, peripherals.SHA).expect("Failed to initialize storage");
101    globals::init_storage(storage).await;
102
103    // -- Display setup --
104    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    // -- Wifi setup --
120    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    // check current wifi mode, if nothing is set (first boot) or wifi connection fails this will be set to ap (Access Point) mode
124    // otherwise the device will start in station mode and try to connect to the set wifi network, will switch back to ap mode if this fails
125    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    // start in station mode
132    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            // -- Init and start widget runner on second core --
137            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    // TODO: Spawn some tasks
167    // let _ = spawner;
168
169    // TODO: button for move to AP mode.
170    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    // -- Server setup --
181    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    // -- Spawn HTTP handler task for widget runtime --
199    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    // init widget store
208    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    // -- Server setup --
216    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/// Renderer task look that is spawned on the second core
225#[embassy_executor::task]
226async fn widget_runner() {
227    info!("Widget runner task started");
228
229    let mut renderer = renderer::Renderer::new();
230    // will loop forever
231    renderer.run().await;
232}