257 lines
6.8 KiB
Rust
257 lines
6.8 KiB
Rust
#![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<CriticalSectionRawMutex, DeviceState> = 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<CriticalSectionRawMutex, ()>,
|
|
) -> 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<CriticalSectionRawMutex, ()>,
|
|
) -> 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");
|
|
}
|
|
}
|