312 lines
9.6 KiB
Rust
312 lines
9.6 KiB
Rust
#![no_std]
|
|
#![no_main]
|
|
|
|
use core::str::FromStr;
|
|
|
|
use embassy_net::tcp::{TcpReader, TcpWriter};
|
|
use embassy_rp::multicore::{Stack, spawn_core1};
|
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
|
use embassy_sync::mutex::Mutex;
|
|
use embassy_sync::signal::Signal;
|
|
use embedded_io::Write;
|
|
use log::info;
|
|
use owned_str::OwnedStr;
|
|
use ufmt::uwrite;
|
|
|
|
use crate::buffer::WriteBuffer;
|
|
use crate::input::{ANGLE, INPUT, InputConfig};
|
|
use crate::net::network_setup_task;
|
|
use crate::owned_str_writer::{OwnedStrWriter, center_str};
|
|
use crate::screen::{lcd_display_task, overwrite_lcd};
|
|
use ag_lcd::{Blink, Cursor, Display, LcdDisplay};
|
|
use defmt::*;
|
|
use embassy_executor::{Executor, Spawner};
|
|
use embassy_rp::gpio::{Level, Output};
|
|
use embassy_rp::i2c::{self};
|
|
use embassy_rp::peripherals::{DMA_CH0, I2C0, PIO0};
|
|
use embassy_rp::pio::InterruptHandler;
|
|
use embassy_rp::{bind_interrupts, dma};
|
|
use embassy_rp::{peripherals::USB, usb};
|
|
use embassy_time::{Delay, Timer};
|
|
use static_cell::StaticCell;
|
|
|
|
use {defmt_rtt as _, panic_probe as _};
|
|
|
|
mod buffer;
|
|
mod input;
|
|
mod net;
|
|
mod owned_str_writer;
|
|
mod screen;
|
|
|
|
const WIFI_NETWORK: &str = "flamme";
|
|
const WIFI_PASSWORD: &str = "12345678";
|
|
const TARGET_IP: &str = "84.238.32.253";
|
|
const TARGET_PORT: u16 = 7070;
|
|
|
|
bind_interrupts!(struct Irqs {
|
|
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
|
DMA_IRQ_0 => dma::InterruptHandler<DMA_CH0>;
|
|
USBCTRL_IRQ => usb::InterruptHandler<USB>;
|
|
I2C0_IRQ => i2c::InterruptHandler<I2C0>;
|
|
});
|
|
|
|
#[embassy_executor::task]
|
|
async fn logger_task(usb: embassy_rp::Peri<'static, embassy_rp::peripherals::USB>) {
|
|
let driver = embassy_rp::usb::Driver::new(usb, Irqs);
|
|
|
|
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
|
|
}
|
|
|
|
enum QuestionType {
|
|
Choice,
|
|
Numeric { min: i32, max: i32 },
|
|
}
|
|
|
|
struct QuestionData {
|
|
text: OwnedStr<256>,
|
|
q_type: QuestionType,
|
|
points: i32,
|
|
generation: usize,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct WheelData {
|
|
value: i32,
|
|
min: i32,
|
|
max: i32,
|
|
}
|
|
|
|
static QUESTION: Mutex<CriticalSectionRawMutex, Option<QuestionData>> = Mutex::new(None);
|
|
static QUESTION_UPDATE: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
|
static WHEEL_VALUE: Mutex<CriticalSectionRawMutex, WheelData> = Mutex::new(WheelData {
|
|
value: 0,
|
|
min: 0,
|
|
max: 0,
|
|
});
|
|
|
|
pub async fn tcp_read_loop(mut read: TcpReader<'_>) {
|
|
let mut buf = [0u8; 1024];
|
|
let mut generation = 1;
|
|
|
|
while let Ok(len) = read.read(&mut buf).await {
|
|
if len == 0 {
|
|
break;
|
|
}
|
|
let Ok(str) = str::from_utf8(&buf[..len]) else {
|
|
continue;
|
|
};
|
|
let mut counter = 0;
|
|
let mut question_data = None;
|
|
let mut future_wheel = WheelData {
|
|
value: 0,
|
|
min: 0,
|
|
max: 0,
|
|
};
|
|
for line in str.lines() {
|
|
if line == "$$" {
|
|
counter = 1;
|
|
continue;
|
|
}
|
|
if counter == 1 {
|
|
let mut q_type = QuestionType::Choice;
|
|
let mut points = -1;
|
|
|
|
for pairs in line.split(" ") {
|
|
let (key, value) = pairs.split_once("=").unwrap();
|
|
if key == "type" {
|
|
q_type = if value == "choice" {
|
|
QuestionType::Choice
|
|
} else {
|
|
QuestionType::Numeric { min: -1, max: -1 }
|
|
};
|
|
}
|
|
if key == "points" {
|
|
points = value.parse().unwrap();
|
|
}
|
|
if key == "rangeMin" || key == "rangeMax" {
|
|
match q_type {
|
|
QuestionType::Choice => {}
|
|
QuestionType::Numeric {
|
|
ref mut min,
|
|
ref mut max,
|
|
} => {
|
|
if key == "rangeMin" {
|
|
*min = value.parse().unwrap();
|
|
future_wheel.min = *min;
|
|
} else {
|
|
*max = value.parse().unwrap();
|
|
future_wheel.max = *max;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
match q_type {
|
|
QuestionType::Numeric { min, max } => {
|
|
let diff = max - min;
|
|
future_wheel.value = min + diff / 2;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
question_data = Some(QuestionData {
|
|
text: OwnedStr::new(),
|
|
q_type,
|
|
points,
|
|
generation,
|
|
});
|
|
counter = 2;
|
|
continue;
|
|
}
|
|
if counter == 2 {
|
|
question_data.as_mut().unwrap().text = OwnedStr::from_str(line).unwrap();
|
|
generation += 1;
|
|
counter = 0;
|
|
}
|
|
}
|
|
|
|
if let Some(question_data) = question_data {
|
|
*QUESTION.lock().await = Some(question_data);
|
|
*WHEEL_VALUE.lock().await = future_wheel;
|
|
QUESTION_UPDATE.signal(());
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn tcp_write_loop(mut write: TcpWriter<'_>) {
|
|
let mut buffer = WriteBuffer::<256>::new();
|
|
loop {
|
|
let data = INPUT.receive().await;
|
|
info!("button={}", data);
|
|
let angle = *ANGLE.lock().await;
|
|
core::writeln!(buffer, "button={} angle={}", data, angle).ok();
|
|
info!("write: {}", &buffer);
|
|
write.write(&buffer).await.ok();
|
|
buffer.clear();
|
|
}
|
|
}
|
|
|
|
const ARROW_RIGHT: char = char::from_u32(0b0111_1110).unwrap();
|
|
const ARROW_LEFT: char = char::from_u32(0b0111_1111).unwrap();
|
|
const DOT: char = char::from_u32(0b1010_0101).unwrap();
|
|
|
|
#[embassy_executor::task]
|
|
pub async fn main_loop() {
|
|
let mut last_gen = 0;
|
|
let mut title_offset = 0;
|
|
info!("Main loop started");
|
|
loop {
|
|
Timer::after_millis(50).await;
|
|
let wheel = *WHEEL_VALUE.lock().await;
|
|
let question = QUESTION.lock().await;
|
|
let Some(question) = question.as_ref() else {
|
|
continue;
|
|
};
|
|
title_offset += 1;
|
|
if question.generation != last_gen {
|
|
last_gen = question.generation;
|
|
title_offset = 0;
|
|
}
|
|
let title_line = if question.text.len() > 16 {
|
|
title_offset %= question.text.len() - 16;
|
|
&question.text[title_offset..title_offset + 16]
|
|
} else {
|
|
&question.text
|
|
};
|
|
let number_str: OwnedStr<16> = match question.q_type {
|
|
QuestionType::Choice => {
|
|
let mut writer = OwnedStrWriter::new();
|
|
writer.push(DOT).unwrap();
|
|
writer.push(' ').unwrap();
|
|
uwrite!(writer, "{}", question.points).unwrap();
|
|
writer.into()
|
|
}
|
|
QuestionType::Numeric { min, max } => {
|
|
let mut writer = OwnedStrWriter::new();
|
|
if wheel.value > min {
|
|
writer.push(ARROW_LEFT).unwrap();
|
|
writer.push(' ').unwrap();
|
|
}
|
|
uwrite!(writer, "{}", wheel.value).unwrap();
|
|
if wheel.value < max {
|
|
writer.push(ARROW_RIGHT).unwrap();
|
|
writer.push(' ').unwrap();
|
|
}
|
|
|
|
writer.into()
|
|
}
|
|
};
|
|
let second_line = center_str::<16>(&number_str, 16).unwrap();
|
|
info!("lcd: {} {}", title_line, second_line);
|
|
overwrite_lcd(title_line, &second_line).await;
|
|
}
|
|
}
|
|
|
|
static mut CORE1_STACK: Stack<4096> = Stack::new();
|
|
// static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
|
|
static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
|
|
|
|
// #[cortex_m_rt::entry]
|
|
|
|
#[embassy_executor::main]
|
|
async fn main(spawner: Spawner) -> () {
|
|
let p = embassy_rp::init(Default::default());
|
|
|
|
let lcd: LcdDisplay<_, _> = LcdDisplay::new(
|
|
Output::new(p.PIN_10, Level::Low),
|
|
Output::new(p.PIN_11, Level::Low),
|
|
Delay,
|
|
)
|
|
.with_half_bus(
|
|
Output::new(p.PIN_12, Level::Low),
|
|
Output::new(p.PIN_13, Level::Low),
|
|
Output::new(p.PIN_14, Level::Low),
|
|
Output::new(p.PIN_15, Level::Low),
|
|
)
|
|
.with_autoscroll(ag_lcd::AutoScroll::Off)
|
|
.with_display(Display::On)
|
|
.with_blink(Blink::On)
|
|
.with_cursor(Cursor::On)
|
|
.with_lines(ag_lcd::Lines::TwoLines);
|
|
|
|
spawn_core1(
|
|
p.CORE1,
|
|
unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
|
|
move || {
|
|
let executor1 = EXECUTOR1.init(Executor::new());
|
|
executor1.run(|spawner| {
|
|
let mut lcd = lcd.build();
|
|
lcd.set_blink(Blink::Off);
|
|
lcd.set_cursor(Cursor::Off);
|
|
spawner.spawn(unwrap!(lcd_display_task(lcd)));
|
|
});
|
|
},
|
|
);
|
|
|
|
// let executor0 = EXECUTOR0.init(Executor::new());
|
|
// executor0.run(|spawner| {
|
|
spawner.spawn(unwrap!(logger_task(p.USB)));
|
|
spawner.spawn(unwrap!(network_setup_task(
|
|
p.PIN_23, p.PIN_25, p.PIO0, p.PIN_24, p.PIN_29, p.DMA_CH0, spawner
|
|
)));
|
|
// let mut lcd = lcd.build();
|
|
// lcd.set_blink(Blink::Off);
|
|
// lcd.set_cursor(Cursor::Off);
|
|
// spawner.spawn(unwrap!(lcd_display_task(lcd)));
|
|
crate::input::setup(
|
|
spawner,
|
|
InputConfig {
|
|
i2c0: p.I2C0,
|
|
scl: p.PIN_5,
|
|
sda: p.PIN_4,
|
|
button_pins: [
|
|
(p.PIN_18.into(), 1),
|
|
(p.PIN_19.into(), 2),
|
|
(p.PIN_20.into(), 3),
|
|
(p.PIN_21.into(), 4),
|
|
],
|
|
},
|
|
);
|
|
// });
|
|
}
|