diff --git a/src/apps/index.rs b/src/apps/index.rs index 80c6af1..1e18da5 100644 --- a/src/apps/index.rs +++ b/src/apps/index.rs @@ -1,3 +1,6 @@ +use heapless::Vec; +use pico_website::unwrap; + use crate::{ apps::Content, socket::{HttpRequestType, HttpResCode}, @@ -22,17 +25,50 @@ impl App for IndexApp { "html", Some(include_str!("./index.html").into()), ), - "/ttt" => ( - HttpResCode::Ok, - "html", - Some(include_str!("ttt.html").into()), - ), - "/ttt.js" => ( - HttpResCode::Ok, - "javascript", - Some(include_str!("ttt.js").into()), - ), - _ => (HttpResCode::NotFound, "", None), + path => { + let (path, args) = path.split_once('?').unwrap_or((path, "")); + let mut team = None; + for arg in args.split('&') { + match arg.split_once('=') { + Some(("team", "0")) => team = Some("0"), + Some(("team", "1")) => team = Some("1"), + _ => {} + } + } + if path == "/ttt" { + let Some(team) = team else { + return (HttpResCode::BadRequest, "", None); + }; + let html = include_str!("ttt.html"); + let mut content = Vec::new(); + let r: Result<(), &str> = try { + let (html1, html2) = html.split_once("/ttt.js").ok_or("")?; + + content.push(html1)?; + content.push("/ttt.js?team=")?; + content.push(team)?; + content.push(html2)?; + }; + unwrap(r).await; + (HttpResCode::Ok, "html", Some(Content(content))) + } else if path == "/ttt.js" { + let Some(team) = team else { + return (HttpResCode::BadRequest, "", None); + }; + + let mut content = Vec::new(); + let r: Result<(), &str> = try { + content.push("const team = ")?; + content.push(team)?; + content.push(";\n")?; + content.push(include_str!("ttt.js"))?; + }; + unwrap(r).await; + (HttpResCode::Ok, "javascript", Some(Content(content))) + } else { + (HttpResCode::NotFound, "", None) + } + } } } } diff --git a/src/apps/ttt.html b/src/apps/ttt.html index 4a2d0c5..6e05c56 100644 --- a/src/apps/ttt.html +++ b/src/apps/ttt.html @@ -29,7 +29,7 @@

-
+
diff --git a/src/apps/ttt.js b/src/apps/ttt.js index 97ae49a..b77430d 100644 --- a/src/apps/ttt.js +++ b/src/apps/ttt.js @@ -1,12 +1,22 @@ -const team = 0; -const teamName = "blue"; -const color = "dodgerblue"; -const otherColor = "firebrick"; +//const team = 0; +if (team != 0 && team != 1) { + throw "team is not 0 or 1! team=" + team; +} +let teamName = "blue"; +let color = "dodgerblue"; +let otherColor = "firebrick"; +let port = "8080"; +if (team === 1) { + teamName = "red"; + color = "firebrick"; + otherColor = "dodgerblue"; + port = "8081"; +} document.getElementById("team").innerHTML = 'Team : ' + teamName + ""; -const ws = new WebSocket("ws://192.254.0.2:8080/" + teamName); +const ws = new WebSocket("ws://192.254.0.2:" + port + "/" + teamName); ws.onmessage = (event) => { console.log(event.data); diff --git a/src/apps/ttt.rs b/src/apps/ttt.rs index ea6e7cf..818806e 100644 --- a/src/apps/ttt.rs +++ b/src/apps/ttt.rs @@ -1,10 +1,11 @@ -use core::fmt::Write; +use core::str::from_utf8_unchecked; use core::{ops::Not, sync::atomic::Ordering}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Instant, Timer}; use heapless::{String, Vec}; -use log::info; -use pico_website::unwrap; -use portable_atomic::{AtomicBool, AtomicU32}; +use log::{info, warn}; +use pico_website::{unwrap, unwrap_opt}; use serde::Serialize; use crate::apps::Content; @@ -13,61 +14,75 @@ use crate::socket::{HttpRequestType, HttpResCode}; use super::App; -static TURN: AtomicBool = AtomicBool::new(false); // bits [0; 8] : player zero board / bits [9; 17] : player one board / is_ended [18] / is_draw [19] / winner [20]: 0=blue 1=green / current_turn [21]: 0=blue 1=green -static BOARD: AtomicU32 = AtomicU32::new(0b01000000_001000000); + +#[derive(Debug, Serialize, Clone, PartialEq, Eq, Default)] +struct Game { + board: [Option; 9], + turn: Option, + winner: Option, +} +static GAME: Mutex = Mutex::new(Game { + board: [None; 9], + turn: Some(Team::Zero), + winner: None, +}); + +// {"board"=[null,null,null,null,null,null,null,null,null],"turn"=null,"winner":null} pub struct TttApp { team: Team, - last_board: u32, + last_game: Game, end: Option<(Instant, Option)>, + json_buf: [u8; 128], } impl TttApp { pub fn new(team: Team) -> Self { Self { team, - last_board: 0, + last_game: Game::default(), end: None, + json_buf: [0; 128], } } - pub fn is_ended(&self, board: u32) -> (bool, Option) { - if let Some((_, t)) = self.end { - return (true, t); - } - for (t, m) in [(Team::Zero, 0), (Team::One, 9)] { - for w in [ - 0b111000000, - 0b000111000, - 0b000000111, - 0b100100100, - 0b010010010, - 0b001001001, - 0b100010001, - 0b001010100, - ] { - if board & (w << m) == (w << m) { - return (true, Some(t)); - } - } - } - if ((board | (board >> 9)) & 0b111111111) == 0b111111111 { - return (true, None); - } - (false, None) - } - pub fn update_end_state(&mut self, board: &mut u32) { - if let Some((i, _)) = self.end { - if i + Duration::from_secs(7) < Instant::now() { - self.end = None; - BOARD.store(0, Ordering::Release); - *board = 0; - } - } else { - if let (true, t) = self.is_ended(*board) { - self.end = Some((Instant::now(), t)); - } - } - } + // pub fn is_ended(&self, board: u32) -> (bool, Option) { + // if let Some((_, t)) = self.end { + // return (true, t); + // } + // for (t, m) in [(Team::Zero, 0), (Team::One, 9)] { + // for w in [ + // 0b111000000, + // 0b000111000, + // 0b000000111, + // 0b100100100, + // 0b010010010, + // 0b001001001, + // 0b100010001, + // 0b001010100, + // ] { + // if board & (w << m) == (w << m) { + // return (true, Some(t)); + // } + // } + // } + // if ((board | (board >> 9)) & 0b111111111) == 0b111111111 { + // return (true, None); + // } + // (false, None) + // } + // pub fn update_end_state(&mut self, board: &mut u32) { + // if let Some((i, _)) = self.end { + // if i + Duration::from_secs(7) < Instant::now() { + // self.end = None; + // // BOARD.store(0, Ordering::Release); + // *board = 0; + // } + // } else { + // if let (true, t) = self.is_ended(*board) { + // self.end = Some((Instant::now(), t)); + // } + // } + // } } impl App for TttApp { @@ -97,36 +112,59 @@ impl App for TttApp { _path: &str, mut ws: Ws<'a, BUF_SIZE, RES_HEAD_BUF_SIZE>, ) { + Timer::after_millis(500).await; let r: Result<(), ()> = try { loop { - let board = BOARD.load(Ordering::Acquire); - ws.send(WsMsg::Text( - &serde_json_core::to_string::<_, 40>(&ServerMsg { - board, - turn: Some(Team::Zero), - winner: None, - }) - .unwrap(), - )) - .await?; + Timer::after_millis(1).await; + let Ok(mut game) = GAME.try_lock() else { + info!("locked"); + continue; + }; + // match GAME.try_lock() ; + if self.last_game != *game { + // let json = unwrap(serde_json_core::to_string::(&game)).await; + let n = unwrap(serde_json_core::to_slice(&(*game), &mut self.json_buf)).await; + let json = + unsafe { from_utf8_unchecked(&unwrap_opt(self.json_buf.get(..n)).await) }; + info!("{:?}", json); + ws.send(WsMsg::Text(json)).await?; + self.last_game = game.clone(); + } + if ws.last_msg.elapsed() >= Duration::from_secs(5) { + ws.send(WsMsg::Ping(&[])).await?; + info!("ping"); + } while let Some(r) = ws.rcv().await? { info!("{:?}", r); + if let WsMsg::Bytes([c]) = r { + let c = *c; + info!("c={}", c); + if c > 8 {} + match game.board.get_mut(c as usize) { + None => { + warn!("Cell played is too big!"); + return; + } + Some(Some(_)) => { + warn!("Cell is already taken!"); + return; + } + Some(cell) => { + *cell = Some(self.team); + } + } + info!("{:#?}", game); + } } - Timer::after_secs(1).await; + // Timer::after_secs(1).await; } }; - info!("{:?}", r); + warn!("error: {:?}", r); + Timer::after_micros(100).await; } } -#[derive(Debug, Serialize)] -struct ServerMsg { - board: u32, - turn: Option, - winner: Option, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Team { Zero = 0, diff --git a/src/dhcp.rs b/src/dhcp.rs index 72471f7..92143fd 100644 --- a/src/dhcp.rs +++ b/src/dhcp.rs @@ -11,7 +11,7 @@ use embassy_net::{ use embassy_time::Timer; use heapless::Vec; use log::{info, warn}; -use pico_website::unwrap; +use pico_website::{unwrap, unwrap_opt}; #[embassy_executor::task(pool_size = 1)] pub async fn dhcp_server(stack: Stack<'static>) { @@ -38,7 +38,8 @@ pub async fn dhcp_server(stack: Stack<'static>) { loop { let (n, _) = unwrap(socket.recv_from(&mut buf).await).await; - let msg = unwrap(dhcpv4::Message::new(&buf[..n])).await; + let msg = unwrap_opt(buf.get(..n)).await; + let msg = unwrap(dhcpv4::Message::new(&msg)).await; let msg_type = unwrap(v4_options!(msg; MessageType required)).await; let mut rapid_commit = false; diff --git a/src/dns.rs b/src/dns.rs index 7e35e24..519ce60 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -5,7 +5,7 @@ use embassy_net::{ }; use embassy_time::Timer; use log::{info, warn}; -use pico_website::unwrap; +use pico_website::{unwrap, unwrap_opt}; #[embassy_executor::task(pool_size = 1)] pub async fn dns_server(stack: Stack<'static>) { @@ -30,7 +30,8 @@ pub async fn dns_server(stack: Stack<'static>) { Timer::after_secs(0).await; let (n, meta) = unwrap(socket.recv_from(&mut buf).await).await; - let msg = match dnsparse::Message::parse(&mut buf[..n]) { + let msg = unwrap_opt(buf.get_mut(..n)).await; + let msg = match dnsparse::Message::parse(msg) { Ok(msg) => msg, Err(e) => { warn!("Dns: Error while parsing DNS message : {:#?}", e); diff --git a/src/lib.rs b/src/lib.rs index 5bb0c12..86b664d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use core::fmt::Debug; +use core::{fmt::Debug, panic::PanicInfo}; use embassy_time::Timer; use log::info; @@ -13,3 +13,20 @@ pub async fn unwrap(res: Result) -> T { }, } } +pub async fn unwrap_opt(opt: Option) -> T { + unwrap(opt.ok_or(())).await +} + +pub async fn assert(condition: bool) { + if !condition { + let err: Result<(), &str> = Err("assert failed"); + unwrap(err).await; + } +} + +// Doesn't work +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + info!("PANIC: {}", info); + loop {} +} diff --git a/src/main.rs b/src/main.rs index de9284f..59661da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,12 @@ #![no_std] #![no_main] #![allow(async_fn_in_trait)] +#![deny( + // clippy::unwrap_used, + // clippy::expect_used, + clippy::panic, + clippy::indexing_slicing +)] #![feature(impl_trait_in_assoc_type)] #![feature(slice_split_once)] #![feature(try_blocks)] @@ -10,6 +16,8 @@ use core::net::Ipv4Addr; use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi}; +use defmt::warn; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; @@ -23,7 +31,6 @@ use log::info; use pico_website::unwrap; use rand_core::RngCore; use static_cell::StaticCell; -use {defmt_rtt as _, panic_probe as _}; #[cfg(feature = "dhcp")] mod dhcp; @@ -175,6 +182,12 @@ async fn main(spawner: Spawner) { .address ) } + // embassy_time::Timer::after_secs(5).await; + // info!("test0"); + + // embassy_time::Timer::after_secs(3).await; + + // panic!("test"); #[cfg(feature = "dhcp")] unwrap(spawner.spawn(dhcp::dhcp_server(stack))).await; diff --git a/src/socket.rs b/src/socket.rs index ec0872a..e841d58 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -6,6 +6,7 @@ use embassy_time::{Duration, Timer}; use embedded_io_async::Write as _; use heapless::{String, Vec}; use log::{info, warn}; +use pico_website::{unwrap, unwrap_opt}; use sha1::{Digest, Sha1}; use crate::apps::Content; @@ -71,7 +72,8 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps: } }; head_buf.clear(); - let (headers, content) = match from_utf8(&buf[..n]) { + let msg = unwrap_opt(buf.get(..n)).await; + let (headers, content) = match from_utf8(msg) { Ok(b) => match b.split_once("\r\n\r\n") { Some(t) => t, None => (b, ""), @@ -82,7 +84,8 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps: } }; - info!("\n{:?}\n", headers); + // info!("\n{:?}\n", headers); + // Timer::after_micros(100).await; let mut hl = headers.lines(); let (request_type, path) = match hl.next() { @@ -144,12 +147,13 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps: host, path, ); - Timer::after_secs(0).await; + Timer::after_micros(100).await; head_buf.clear(); let res_content: Result, core::fmt::Error> = try { if ws_handshake { if !app.accept_ws(path) { + warn!("No ws there!"); write!( &mut head_buf, "{}\r\n\r\n", @@ -206,7 +210,7 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps: Content-Length: {}\r\n", res_type, c.len() - )? + )?; } write!(&mut head_buf, "\r\n\r\n")?; res_content @@ -221,7 +225,8 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps: } }; - info!("\n{}\n", from_utf8(&head_buf).unwrap()); + info!("\n{}\n", unwrap(from_utf8(&head_buf)).await); + Timer::after_micros(1000).await; let w: Result<(), embassy_net::tcp::Error> = try { socket.write_all(&head_buf).await?; @@ -229,7 +234,6 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps: for s in c.0.iter() { socket.write_all(s.as_bytes()).await?; } - } else { } }; @@ -239,11 +243,13 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps: }; if ws_handshake { - ws_path = Some(String::from_str(path).unwrap()); + ws_path = Some(unwrap(String::from_str(path)).await); break; } } if let Some(path) = ws_path { + info!("handle ws"); + Timer::after_micros(200).await; app.handle_ws( &path, Ws::new(&mut socket, &mut buf, &mut head_buf, app.socket_name()), @@ -291,11 +297,12 @@ impl Into<&str> for HttpResCode { async fn compute_ws_accept(key: &str) -> Result, EncodeSliceError> { let mut res = Vec::::new(); + Timer::after_micros(10).await; res.extend_from_slice(&[0; 28]).unwrap(); let mut hasher = Sha1::new(); hasher.update(key.as_bytes()); hasher.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); let hash = hasher.finalize(); BASE64_STANDARD.encode_slice(hash, &mut res)?; - Ok(String::from_utf8(res).unwrap()) + Ok(unwrap(String::from_utf8(res)).await) } diff --git a/src/socket/ws.rs b/src/socket/ws.rs index a5b68cb..b938614 100644 --- a/src/socket/ws.rs +++ b/src/socket/ws.rs @@ -5,6 +5,7 @@ use embassy_time::{Instant, Timer}; use embedded_io_async::{ErrorType, ReadReady, Write}; use heapless::Vec; use log::{info, warn}; +use pico_website::{assert, unwrap, unwrap_opt}; #[derive(Clone, Copy, Debug)] pub enum WsMsg<'a> { @@ -38,7 +39,6 @@ impl WsMsg<'_> { struct WsRx<'a, const BUF_SIZE: usize> { socket: TcpReader<'a>, buf: &'a mut [u8; BUF_SIZE], - last_msg: Instant, msg_in_buf: Option<(usize, usize)>, // (start, length) } struct WsTx<'a, const HEAD_BUF_SIZE: usize> { @@ -48,15 +48,17 @@ struct WsTx<'a, const HEAD_BUF_SIZE: usize> { impl<'a, const HEAD_BUF_SIZE: usize> WsTx<'a, HEAD_BUF_SIZE> { pub async fn send<'m>(&mut self, msg: WsMsg<'m>) -> Result<(), ()> { self.head_buf.clear(); - self.head_buf.push(0b1000_0000 | msg.code()).unwrap(); + unwrap(self.head_buf.push(0b1000_0000 | msg.code())).await; if msg.len() < 126 { - self.head_buf.push(msg.len() as u8).unwrap(); + unwrap(self.head_buf.push(msg.len() as u8)).await; } else { - self.head_buf.push(0b0111_1110).unwrap(); - self.head_buf - .extend_from_slice(&(msg.len() as u16).to_le_bytes()) - .unwrap(); - self.head_buf.extend_from_slice(msg.as_bytes()).unwrap(); + unwrap(self.head_buf.push(0b0111_1110)).await; + unwrap( + self.head_buf + .extend_from_slice(&(msg.len() as u16).to_le_bytes()), + ) + .await; + // self.head_buf.extend_from_slice(msg.as_bytes()).unwrap(); } let w: Result<(), as ErrorType>::Error> = try { self.socket.write_all(&self.head_buf).await?; @@ -72,6 +74,7 @@ impl<'a, const HEAD_BUF_SIZE: usize> WsTx<'a, HEAD_BUF_SIZE> { pub struct Ws<'a, const BUF_SIZE: usize = 1024, const RES_HEAD_BUF_SIZE: usize = 256> { rx: WsRx<'a, BUF_SIZE>, tx: WsTx<'a, RES_HEAD_BUF_SIZE>, + pub last_msg: Instant, name: &'a str, } impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEAD_BUF_SIZE> { @@ -86,13 +89,13 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA rx: WsRx { socket: rx, buf, - last_msg: Instant::MIN, msg_in_buf: None, }, tx: WsTx { socket: tx, head_buf, }, + last_msg: Instant::MIN, name, } } @@ -100,8 +103,9 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA pub async fn rcv(&mut self) -> Result, ()> { let n = match self.rx.msg_in_buf.take() { Some(n) => { + assert(n.0 + n.1 <= self.rx.buf.len()).await; self.rx.buf.copy_within(n.0..n.0 + n.1, 0); - if self.rx.socket.read_ready().unwrap() { + if unwrap(self.rx.socket.read_ready()).await { let n_rcv = match self.rx.socket.read(&mut self.rx.buf[n.1..]).await { Ok(0) => { info!("read EOF"); @@ -119,7 +123,7 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA } } None => { - if self.rx.socket.read_ready().unwrap() { + if unwrap(self.rx.socket.read_ready()).await { match self.rx.socket.read(self.rx.buf).await { Ok(0) => { info!("read EOF"); @@ -177,7 +181,7 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA .iter_mut() .enumerate() { - *x ^= mask_key[i & 0xff]; + *x ^= unwrap_opt(mask_key.get(i & 0xff)).await; } if n_after_length + 4 + (length as usize) < n { self.rx.msg_in_buf = Some(( @@ -199,7 +203,7 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA } &self.rx.buf[n_after_length..n_after_length + length as usize] }; - self.rx.last_msg = Instant::now(); + self.last_msg = Instant::now(); match self.rx.buf[0] & 0b0000_1111 { // Text message 1 => { @@ -222,6 +226,8 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA } } pub async fn send(&mut self, msg: WsMsg<'_>) -> Result<(), ()> { - self.tx.send(msg).await + self.tx.send(msg).await?; + self.last_msg = Instant::now(); + Ok(()) } }