#![no_std] #![no_main] extern crate alloc; use alloc::borrow::ToOwned; use alloc::string::String; use device_state::DeviceState; use embassy_executor::Spawner; use embassy_futures::select::{Either, select}; use embassy_net::tcp::{TcpReader, TcpWriter}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Timer; use esp_backtrace as _; use esp_hal::{ gpio::{Input, InputConfig as GpioInputConfig, Pin, Pull}, interrupt::software::SoftwareInterruptControl, timer::timg::TimerGroup, }; use esp_println::println; mod buffer; mod input; mod net; mod screen; pub use input::ANGLE; const WIFI_NETWORK: &str = "flamme"; const WIFI_PASSWORD: &str = "12345678"; const TARGET_IP: &str = "84.238.32.253"; const TARGET_PORT: u16 = 7070; const WHEEL_PRECISION: i32 = 16; const WHEEL_INVERTED: bool = true; const DEVICE_ID: &str = "esp32-1"; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TcpDisconnect { ReadError, ReadEof, WriteError, Cancelled, } pub static STATE: Mutex = Mutex::new(DeviceState::new()); pub async fn reset_state() { let mut state = STATE.lock().await; state.reset(); } pub async fn reconnecting_state() { let mut state = STATE.lock().await; state.reconnecting(); } #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { println!("PANIC! {:?}", info); loop {} } pub async fn tcp_read_loop( mut read: TcpReader<'_>, cancel: &Signal, ) -> Result<(), TcpDisconnect> { let mut buf = [0u8; 1024]; loop { let read_fut = read.read(&mut buf); let cancel_fut = cancel.wait(); let len = match select(read_fut, cancel_fut).await { Either::First(Ok(len)) => len, Either::First(Err(_)) => { cancel.signal(()); return Err(TcpDisconnect::ReadError); } Either::Second(()) => return Err(TcpDisconnect::Cancelled), }; if len == 0 { cancel.signal(()); return Err(TcpDisconnect::ReadEof); } let Ok(str) = core::str::from_utf8(&buf[..len]) else { continue; }; if let Some(last) = str.lines().last() { let Ok(data) = device_state::parse_proxy_output(last) else { println!("parse proxy output failed: {}", last); continue; }; let mut state = STATE.lock().await; state.apply_proxy_output(data); } } } pub async fn tcp_write_loop( mut write: TcpWriter<'_>, cancel: &Signal, ) -> Result<(), TcpDisconnect> { if write .write( device_state::serialize_write(&device_state::WriteType::DeviceId(DEVICE_ID)) .unwrap() .as_bytes(), ) .await .is_err() { cancel.signal(()); return Err(TcpDisconnect::WriteError); } loop { let input_fut = input::INPUT.receive(); let cancel_fut = cancel.wait(); let data = match select(input_fut, cancel_fut).await { Either::First(data) => data, Either::Second(()) => return Err(TcpDisconnect::Cancelled), }; println!("button={}", data); let value = STATE.lock().await.response_value(data); let data = device_state::WriteType::QuizResponse(value); let buffer = device_state::serialize_write(&data).unwrap(); println!("write: {}", &buffer); if write.write(buffer.as_bytes()).await.is_err() { cancel.signal(()); return Err(TcpDisconnect::WriteError); } } } #[embassy_executor::task] pub async fn main_loop() { println!("Main loop started"); let mut last = (String::new(), String::new()); loop { embassy_time::Timer::after_millis(300).await; let mut state = STATE.lock().await; state.tick(); let lines = state.render_lines(); drop(state); let Some((title_line, second_line)) = lines else { continue; }; let rendered = ( title_line.as_str().to_owned(), second_line.as_str().to_owned(), ); if rendered == last { continue; } last = rendered; println!("lcd1: {}\nlcd2: {}", title_line, second_line); screen::overwrite_lcd(&title_line, &second_line).await; } } esp_bootloader_esp_idf::esp_app_desc!(); #[esp_rtos::main] async fn main(spawner: Spawner) -> ! { let p = esp_hal::init(esp_hal::Config::default().with_cpu_clock(esp_hal::clock::CpuClock::max())); println!("Booting"); let timg0 = TimerGroup::new(p.TIMG0); let sw_int = SoftwareInterruptControl::new(p.SW_INTERRUPT); esp_rtos::start(timg0.timer0, sw_int.software_interrupt0); esp_alloc::heap_allocator!(size: 64 * 1024); let lcd = screen::LcdPins { rs: p.GPIO4, e: p.GPIO16, d4: p.GPIO17, d5: p.GPIO18, d6: p.GPIO19, d7: p.GPIO23, }; spawner.spawn(screen::lcd_display_task(screen::LcdConfig { pins: lcd }).expect("spawn lcd")); spawner.spawn( input::rotation_read_task(input::RotationConfig { i2c0: p.I2C0, scl: p.GPIO22, sda: p.GPIO21, }) .expect("spawn rotation"), ); spawner.spawn( input::button_task( Input::new( p.GPIO26.degrade(), GpioInputConfig::default().with_pull(Pull::Up), ), 0, ) .expect("spawn btn1"), ); spawner.spawn( input::button_task( Input::new( p.GPIO25.degrade(), GpioInputConfig::default().with_pull(Pull::Up), ), 1, ) .expect("spawn btn2"), ); spawner.spawn( input::button_task( Input::new( p.GPIO33.degrade(), GpioInputConfig::default().with_pull(Pull::Up), ), 2, ) .expect("spawn btn3"), ); spawner.spawn( input::button_task( Input::new( p.GPIO32.degrade(), GpioInputConfig::default().with_pull(Pull::Up), ), 3, ) .expect("spawn btn4"), ); spawner.spawn( net::network_setup_task( spawner, net::NetworkConfig { wifi: p.WIFI, wifi_network: WIFI_NETWORK, wifi_password: WIFI_PASSWORD, target_ip: TARGET_IP, target_port: TARGET_PORT, }, ) .expect("spawn net"), ); println!("Init done"); loop { Timer::after_millis(1000).await; // println!("Looping"); } }