From 0c0024d66c4789d13a8153ba98671dc5e37edf09 Mon Sep 17 00:00:00 2001 From: Arkitu <85173315+Arkitu@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:39:59 +0200 Subject: [PATCH] working turns! --- .gitignore | 1 + Cargo.lock | 20 ++++++ Cargo.toml | 10 ++- src/main.rs | 190 +++++++++++++++++++++++++++++++++++++------------ web/index.html | 2 +- 5 files changed, 175 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..ef64d1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/wifi.json diff --git a/Cargo.lock b/Cargo.lock index 10e9e50..3a94491 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,6 +812,7 @@ checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "defmt", "hash32", + "serde", "stable_deref_trait", ] @@ -1090,6 +1091,8 @@ dependencies = [ "panic-probe", "portable-atomic", "rand_core", + "serde", + "serde-json-core", "static_cell", ] @@ -1295,6 +1298,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -1334,6 +1343,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-json-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b81787e655bd59cecadc91f7b6b8651330b2be6c33246039a65e5cd6f4e0828" +dependencies = [ + "heapless", + "ryu", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" diff --git a/Cargo.toml b/Cargo.toml index 36279f9..a1a1ad0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "pico-website" version = "0.1.0" edition = "2024" +[features] +wifi-connect = ["dep:serde-json-core", "dep:serde"] # you need to add a wifi.conf file with your wifi credentials (for example : "Example Wifi name:pa$$w0rd") +default = ["wifi-connect"] + [dependencies] embassy-executor = { git = "https://github.com/embassy-rs/embassy", features = [ "defmt", @@ -25,8 +29,8 @@ embassy-net = { git = "https://github.com/embassy-rs/embassy", features = [ "proto-ipv4", "tcp", "dhcpv4", - "dns", - "icmp", + # "dns", + # "icmp", "packet-trace" ] } cyw43-pio = {git = "https://github.com/embassy-rs/embassy"} @@ -44,3 +48,5 @@ heapless = "*" rand_core = "*" log = "*" +serde-json-core = {version = "*", optional = true} +serde = {version = "*", optional = true, default-features = false, features = ["derive"]} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 718d754..67098f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,26 +4,30 @@ #![feature(impl_trait_in_assoc_type)] #![feature(slice_split_once)] -use core::fmt::{Debug, Write}; +use core::fmt::{Debug, Display, Write}; +use core::net::Ipv4Addr; +use core::ops::Not; use core::str::from_utf8; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use cortex_m::interrupt::Mutex; use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi}; -use embassy_executor::Spawner; +use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, DhcpConfig, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; +use embassy_rp::multicore::{Stack, spawn_core1}; use embassy_rp::peripherals::USB; use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::program::ProgramWithDefines; use embassy_rp::pio::{InterruptHandler as PioInterruptHandler, Pio}; use embassy_rp::usb::{Driver, InterruptHandler as UsbInterruptHandler}; use embassy_time::Duration; use embassy_time::Timer; use embedded_io_async::Write as _; use heapless::{String, Vec}; -use log::{debug, info, warn}; +use log::{debug, error, info, warn}; use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -50,6 +54,18 @@ async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'sta runner.run().await } +async fn unwrap(res: Result) -> T { + match res { + Ok(v) => v, + Err(e) => { + error!("FATAL ERROR : {:?}", e); + loop { + Timer::after_millis(0).await; + } + } + } +} + #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -76,13 +92,14 @@ async fn main(spawner: Spawner) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; - spawner.spawn(cyw43_task(runner)).unwrap(); + unwrap(spawner.spawn(cyw43_task(runner))).await; control.init(clm).await; control .set_power_management(cyw43::PowerManagementMode::PowerSave) .await; + #[cfg(not(feature = "wifi-connect"))] // Use a link-local address for communication without DHCP server let config = Config::ipv4_static(embassy_net::StaticConfigV4 { address: embassy_net::Ipv4Cidr::new(embassy_net::Ipv4Address::new(192, 254, 0, 2), 16), @@ -90,11 +107,26 @@ async fn main(spawner: Spawner) { gateway: None, }); + #[cfg(feature = "wifi-connect")] + let wifi_conf = unwrap(serde_json_core::from_slice::(include_bytes!("../wifi.json"))).await.0; + // Use a link-local address for communication without DHCP server + // let config = Config::dhcpv4(embassy_net::DhcpConfig::default()); + #[cfg(feature = "wifi-connect")] + let config = match wifi_conf.ip { + Some(ip) => Config::ipv4_static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new(ip, 24), + dns_servers: heapless::Vec::new(), + gateway: None, + }), + None => Config::dhcpv4(DhcpConfig::default()) + }; + + // Generate random seed let seed = rng.next_u64(); // Init network stack - static RESOURCES: StaticCell> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); let (stack, runner) = embassy_net::new( net_device, config, @@ -102,13 +134,45 @@ async fn main(spawner: Spawner) { seed, ); - spawner.spawn(net_task(runner)).unwrap(); + unwrap(spawner.spawn(net_task(runner))).await; + #[cfg(not(feature = "wifi-connect"))] //control.start_ap_open("cyw43", 5).await; control.start_ap_wpa2("cyw43", "password", 5).await; - spawner.spawn(listen_task(stack, Team::Zero, 80)).unwrap(); - spawner.spawn(listen_task(stack, Team::One, 81)).unwrap(); + #[cfg(feature = "wifi-connect")] + { + loop { + match control + .join(wifi_conf.name, cyw43::JoinOptions::new(wifi_conf.password.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + info!("Network joined!"); + info!("waiting for link..."); + stack.wait_link_up().await; + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + stack.wait_config_up().await; + // while !stack.is_config_up() { + // Timer::after_millis(100).await; + // } + info!("DHCP is now up!"); + info!( + "ip : {}", + unwrap(stack.config_v4().ok_or("no dhcp config")) + .await + .address + ) + } + + unwrap(spawner.spawn(listen_task(stack, Team::Zero, 80))).await; + unwrap(spawner.spawn(listen_task(stack, Team::One, 81))).await; } static TURN: AtomicBool = AtomicBool::new(false); @@ -116,10 +180,11 @@ static TURN: AtomicBool = AtomicBool::new(false); static BOARD: AtomicU32 = AtomicU32::new(0); #[embassy_executor::task(pool_size = 2)] -async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { - loop { - info!("team:{:?}", team); - } +async fn listen_task(stack: embassy_net::Stack<'static>, team: Team, port: u16) { + // loop { + // info!("team:{:?}", team); + // Timer::after_millis(0).await; + // } let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; let mut buf = [0; 4096]; @@ -156,13 +221,6 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { } }; - info!( - "Socket {:?}: request :\n{}", - team, - from_utf8(&buf[..n]).unwrap() - ); - Timer::after_secs(0).await; - let mut headers: &[u8] = &buf[..n]; let mut content: &[u8] = &[]; for i in 0..(n - 1) { @@ -187,7 +245,7 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { Some(b"GET") => HttpRequestType::Get, Some(b"POST") => HttpRequestType::Post, Some(t) => { - warn!("Unknown request type : {}", from_utf8(t).unwrap()); + warn!("Unknown request type : {}", unwrap(from_utf8(t)).await); break; } None => { @@ -196,7 +254,7 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { } }, match l1.next() { - Some(path) => from_utf8(path).unwrap(), + Some(path) => unwrap(from_utf8(path)).await, None => { warn!("No path"); break; @@ -206,6 +264,14 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { } }; + info!( + "Socket {:?}: {:?} request for {}", + team, + request_type, + path + ); + Timer::after_secs(0).await; + let (code, res_type, res_content): (HttpResCode, &str, &[u8]) = match path { "/" => (HttpResCode::Ok, "html", include_bytes!("../web/index.html")), "/htmx.min.js" => ( @@ -221,13 +287,16 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { p => 'res: { if (p.starts_with("/ttt/cell") && p.len() == 10) || p == "/ttt/board" { let mut board = BOARD.load(Ordering::Acquire); + let mut turn = TURN.load(Ordering::Acquire); - if p.starts_with("/ttt/cell") { - let clicked_c: Cell = - match TryInto::::try_into(p.chars().nth(9).unwrap()) { - Ok(c) => c, - Err(_) => break 'res (HttpResCode::NotFound, "", &[]), - }; + // just return correct board in case of unauthorized move + if p.starts_with("/ttt/cell") && team == turn.into() { + let clicked_c: Cell = match TryInto::::try_into( + unwrap(p.chars().nth(9).ok_or("no 9th char")).await, + ) { + Ok(c) => c, + Err(_) => break 'res (HttpResCode::NotFound, "", &[]), + }; if board & ((2_u32.pow(clicked_c as u32)) + (2_u32.pow(9 + clicked_c as u32))) @@ -236,7 +305,9 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { break 'res (HttpResCode::Forbidden, "", &[]); } board = board | 2_u32.pow((team as u32 * 9) + clicked_c as u32); + turn = (!team).into(); BOARD.store(board, Ordering::Release); + TURN.store(turn, Ordering::Release); } res_buf.clear(); @@ -250,24 +321,26 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { }; match picked_by { Some(Team::Zero) => { - res_buf + unwrap(res_buf .extend_from_slice( b"
", - ) - .unwrap(); + )).await; } Some(Team::One) => { - res_buf.extend_from_slice( + unwrap(res_buf.extend_from_slice( b"
", - ) - .unwrap(); + )).await; } - None => { - write!( + None => if team == turn.into() { + unwrap(write!( &mut res_buf, "", c - ).unwrap(); + )).await; + } else { + unwrap(res_buf.extend_from_slice( + b"
", + )).await; } }; } @@ -292,13 +365,6 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { break; } - info!( - "Socket {:?}: response :\n{}", - team, - from_utf8(&res_head_buf).unwrap() - ); - Timer::after_secs(0).await; - match socket.write_all(&res_head_buf).await { Ok(()) => {} Err(e) => { @@ -317,10 +383,19 @@ async fn listen_task(stack: Stack<'static>, team: Team, port: u16) { } } +#[derive(Clone, Copy, Debug)] enum HttpRequestType { Get, Post, } +impl Into<&str> for HttpRequestType { + fn into(self) -> &'static str { + match self { + Self::Get => "GET", + Self::Post => "POST" + } + } +} #[derive(Debug, Clone, Copy)] enum HttpResCode { @@ -338,7 +413,7 @@ impl Into<&str> for HttpResCode { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Team { Zero = 0, One = 1, @@ -348,6 +423,23 @@ impl From for Team { if value { Team::One } else { Team::Zero } } } +impl Into for Team { + fn into(self) -> bool { + match self { + Team::Zero => false, + Team::One => true + } + } +} +impl Not for Team { + type Output = Team; + fn not(self) -> Self::Output { + match self { + Team::Zero => Team::One, + Team::One => Team::Zero + } + } +} #[derive(Debug, Clone, Copy)] enum Cell { @@ -395,3 +487,11 @@ impl TryFrom for Cell { }) } } + +#[cfg(feature="wifi-connect")] +#[derive(serde::Deserialize)] +struct WifiConnectConf<'a> { + name: &'a str, + password: &'a str, + ip: Option +} \ No newline at end of file diff --git a/web/index.html b/web/index.html index 5fd1735..f3d3bfe 100644 --- a/web/index.html +++ b/web/index.html @@ -23,7 +23,7 @@ id="grid" hx-post="/ttt/board" hx-swap="innerHTML" - hx-trigger="load" + hx-trigger="every 1s" >