diff --git a/src/apps/index.html b/src/apps/index.html
new file mode 100644
index 0000000..c5396cb
--- /dev/null
+++ b/src/apps/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Apps
+
+
+
diff --git a/src/apps/index.rs b/src/apps/index.rs
new file mode 100644
index 0000000..03114cf
--- /dev/null
+++ b/src/apps/index.rs
@@ -0,0 +1,18 @@
+use crate::socket::HttpResCode;
+
+use super::App;
+
+pub struct IndexApp;
+impl App for IndexApp {
+ fn socket_name(&self) -> &'static str {
+ "index"
+ }
+ async fn handle_request<'a>(&'a mut self, path: &str) -> (HttpResCode, &'static str, &'a [u8]) {
+ match path {
+ "/" | "/index" | "/index.html" => {
+ (HttpResCode::Ok, "html", include_bytes!("./index.html"))
+ }
+ _ => (HttpResCode::NotFound, "", &[]),
+ }
+ }
+}
diff --git a/src/apps/mod.rs b/src/apps/mod.rs
new file mode 100644
index 0000000..9699932
--- /dev/null
+++ b/src/apps/mod.rs
@@ -0,0 +1,9 @@
+use crate::socket::HttpResCode;
+
+pub mod index;
+pub mod ttt;
+
+pub trait App {
+ fn socket_name(&self) -> &'static str;
+ async fn handle_request<'a>(&'a mut self, path: &str) -> (HttpResCode, &'static str, &'a [u8]);
+}
diff --git a/static/index.html b/src/apps/ttt.html
similarity index 100%
rename from static/index.html
rename to src/apps/ttt.html
diff --git a/src/game.rs b/src/apps/ttt.rs
similarity index 56%
rename from src/game.rs
rename to src/apps/ttt.rs
index 67903cb..3d4d049 100644
--- a/src/game.rs
+++ b/src/apps/ttt.rs
@@ -1,30 +1,32 @@
+use core::fmt::Write;
use core::{ops::Not, sync::atomic::Ordering};
use embassy_time::{Duration, Instant};
use heapless::Vec;
use pico_website::unwrap;
use portable_atomic::{AtomicBool, AtomicU32};
-use core::fmt::Write;
use crate::socket::HttpResCode;
+use super::App;
+
static TURN: AtomicBool = AtomicBool::new(false);
// bits [0; 8] : player zero board / bits [9; 17] : player one board
static BOARD: AtomicU32 = AtomicU32::new(0);
-pub struct GameClient {
+pub struct TttApp {
res_buf: Vec,
/// State of the board last time it has been sent
last_board: u32,
team: Team,
- end: Option<(Instant, Option)>
+ end: Option<(Instant, Option)>,
}
-impl GameClient {
+impl TttApp {
pub fn new(team: Team) -> Self {
Self {
res_buf: Vec::new(),
last_board: 0,
team,
- end: None
+ end: None,
}
}
pub fn is_ended(&self, board: u32) -> (bool, Option) {
@@ -40,21 +42,21 @@ impl GameClient {
0b010010010,
0b001001001,
0b100010001,
- 0b001010100
+ 0b001010100,
] {
- if board & (w<>9)) & 0b111111111) == 0b111111111 {
- return (true, None)
+ 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() {
+ if i + Duration::from_secs(7) < Instant::now() {
self.end = None;
BOARD.store(0, Ordering::Release);
*board = 0;
@@ -66,7 +68,12 @@ impl GameClient {
}
}
/// Generate board html
- pub async fn generate_board_res<'a>(&'a mut self, board: u32, turn: Team, outer_html: bool) -> &'a [u8] {
+ async fn generate_board_res<'a>(
+ &'a mut self,
+ board: u32,
+ turn: Team,
+ outer_html: bool,
+ ) -> &'a [u8] {
self.res_buf.clear();
if outer_html {
unwrap(self.res_buf.extend_from_slice(
@@ -76,33 +83,35 @@ impl GameClient {
hx-swap=\"innerHTML\" \
hx-trigger=\"every 100ms\" \
hx-target=\"this\"\
- >"
- )).await;
+ >",
+ ))
+ .await;
}
unwrap(write!(
self.res_buf,
"Team : {}
",
self.team.color(),
self.team.name()
- )).await;
+ ))
+ .await;
match self.end {
- Some((_, Some(t))) => unwrap(write!(
+ Some((_, Some(t))) => {
+ unwrap(write!(
self.res_buf,
"
Team {} has won!
",
t.color(),
t.name()
- )).await,
- Some((_, None)) => unwrap(write!(
- self.res_buf,
- "
Draw!
",
- )).await,
+ ))
+ .await
+ }
+ Some((_, None)) => unwrap(write!(self.res_buf, "
Draw!
",)).await,
None => {}
}
unwrap(self.res_buf.extend_from_slice(b"")).await;
for c in 0..=8 {
- let picked_by = if board & (1<
",
t.color()
- )).await;
+ ))
+ .await;
}
- None => if self.team == turn.into() && self.end.is_none() {
- unwrap(write!(
+ None => {
+ if self.team == turn.into() && self.end.is_none() {
+ unwrap(write!(
self.res_buf,
"",
c
)).await;
- } else {
- unwrap(self.res_buf.extend_from_slice(
- b"",
- )).await;
+ } else {
+ unwrap(
+ self.res_buf
+ .extend_from_slice(b""),
+ )
+ .await;
+ }
}
};
}
@@ -134,45 +148,63 @@ impl GameClient {
}
&self.res_buf
}
- pub async fn handle_request<'a>(&'a mut self, path: &str) -> (HttpResCode, &'static str, &'a [u8]) {
- if (path.starts_with("/ttt/cell") && path.len() == 10) || path == "/ttt/game" {
- let mut board = BOARD.load(Ordering::Acquire);
- let mut turn = TURN.load(Ordering::Acquire);
+}
- // just return correct board in case of unauthorized move
- if path.starts_with("/ttt/cell") && self.team == turn.into() {
- let clicked_c: Cell = match TryInto::::try_into(
- unwrap(path.chars().nth(9).ok_or("no 9th char")).await,
- ) {
- Ok(c) => c,
- Err(_) => return (HttpResCode::NotFound, "", &[]),
- };
- if board
- & (
- (1<<(clicked_c as u32)) + (1<<(9 + clicked_c as u32))
- )
- != 0
- {
- return (HttpResCode::Forbidden, "", &[]);
+impl App for TttApp {
+ fn socket_name(&self) -> &'static str {
+ self.team.name()
+ }
+ async fn handle_request<'a>(&'a mut self, path: &str) -> (HttpResCode, &'static str, &'a [u8]) {
+ match path {
+ "/" | "/index" | "/index.html" | "/ttt" | "/ttt.html" => {
+ (HttpResCode::Ok, "html", include_bytes!("./ttt.html"))
+ }
+ "/ttt/initial_game" => {
+ let board = BOARD.load(Ordering::Acquire);
+ let turn = TURN.load(Ordering::Acquire);
+ (
+ HttpResCode::Ok,
+ "html",
+ self.generate_board_res(board, turn.into(), true).await,
+ )
+ }
+ path => {
+ if (path.starts_with("/ttt/cell") && path.len() == 10) || path == "/ttt/game" {
+ let mut board = BOARD.load(Ordering::Acquire);
+ let mut turn = TURN.load(Ordering::Acquire);
+
+ // just return correct board in case of unauthorized move
+ if path.starts_with("/ttt/cell") && self.team == turn.into() {
+ let clicked_c: Cell = match TryInto::::try_into(
+ unwrap(path.chars().nth(9).ok_or("no 9th char")).await,
+ ) {
+ Ok(c) => c,
+ Err(_) => return (HttpResCode::NotFound, "", &[]),
+ };
+ if board & ((1 << (clicked_c as u32)) + (1 << (9 + clicked_c as u32))) != 0
+ {
+ return (HttpResCode::Forbidden, "", &[]);
+ }
+ board = board | (1 << ((self.team as u32 * 9) + clicked_c as u32));
+ turn = (!self.team).into();
+ BOARD.store(board, Ordering::Release);
+ TURN.store(turn, Ordering::Release);
+ }
+ self.update_end_state(&mut board);
+ if self.last_board != board {
+ self.last_board = board;
+ (
+ HttpResCode::Ok,
+ "html",
+ self.generate_board_res(board, turn.into(), false).await,
+ )
+ } else {
+ (HttpResCode::NoContent, "", &[])
+ }
+ } else {
+ (HttpResCode::NotFound, "", &[])
}
- board = board | (1<<((self.team as u32 * 9) + clicked_c as u32));
- turn = (!self.team).into();
- BOARD.store(board, Ordering::Release);
- TURN.store(turn, Ordering::Release);
}
- self.update_end_state(&mut board);
- if self.last_board != board {
- self.last_board = board;
- (HttpResCode::Ok, "html", self.generate_board_res(board, turn.into(), false).await)
- } else {
- (HttpResCode::NoContent, "", &[])
- }
- } else if path == "/ttt/initial_game" {
- let board = BOARD.load(Ordering::Acquire);
- let turn = TURN.load(Ordering::Acquire);
- (HttpResCode::Ok, "html", self.generate_board_res(board, turn.into(), true).await)
- } else {
- (HttpResCode::NotFound, "", &[])
}
}
}
@@ -191,7 +223,7 @@ impl Into for Team {
fn into(self) -> bool {
match self {
Team::Zero => false,
- Team::One => true
+ Team::One => true,
}
}
}
@@ -200,7 +232,7 @@ impl Not for Team {
fn not(self) -> Self::Output {
match self {
Team::Zero => Team::One,
- Team::One => Team::Zero
+ Team::One => Team::Zero,
}
}
}
@@ -208,13 +240,13 @@ impl Team {
fn color(self) -> &'static str {
match self {
Team::Zero => "dodgerblue",
- Team::One => "firebrick"
+ Team::One => "firebrick",
}
}
fn name(self) -> &'static str {
match self {
Team::Zero => "blue",
- Team::One => "red"
+ Team::One => "red",
}
}
}
@@ -264,4 +296,4 @@ impl TryFrom for Cell {
_ => return Err(()),
})
}
-}
\ No newline at end of file
+}
diff --git a/src/dhcp.rs b/src/dhcp.rs
index 6c65554..72471f7 100644
--- a/src/dhcp.rs
+++ b/src/dhcp.rs
@@ -152,12 +152,12 @@ async fn write_dhcp_opts(buf: &mut Vec, op_codes: &[u8])
6 => (4, &[192, 254, 0, 2]), // DhcpOption::DomainNameServer(&[dhcpv4::Addr([0, 0, 0, 0])]),
12 => (4, b"blue"), // DhcpOption::HostName(b"blue"),
15 => (4, b"wifi"), // DhcpOption::DomainName(b"LocalDomain"),
- 26 => (2, &1514_u16.to_be_bytes()), // DhcpOption::Unknown(26, &[0x5, 0xEA]), // mtu
- 28 => (4, &[192, 254, 0, 255]), // DhcpOption::Unknown(28, &[192, 254, 0, 255]), // broadcast
- 51 => (4, &700_u32.to_be_bytes()), // DhcpOption::AddressLeaseTime(700),
+ 26 => (2, &1514_u16.to_be_bytes()), // mtu
+ 28 => (4, &[192, 254, 0, 255]), // broadcast
+ 51 => (4, &3600_u32.to_be_bytes()), // DhcpOption::AddressLeaseTime(3600),
54 => (4, &[192, 254, 0, 2]), // DhcpOption::ServerIdentifier(&dhcpv4::Addr([192, 254, 0, 2])),
- 58 => (4, &500_u32.to_be_bytes()), // DhcpOption::Unknown(58, &[0, 0, 0x1, 0xF4]), // renewal time = 500s
- 59 => (4, &600_u32.to_be_bytes()), // DhcpOption::Unknown(59, &[0, 0, 0x2, 0x58]), // rebinding time = 600s
+ 58 => (4, &3400_u32.to_be_bytes()), // renewal time
+ 59 => (4, &3500_u32.to_be_bytes()), // rebinding time
80 => (0, &[]),
_ => {
info!("Dhcp: unhandled requested option {}", o);
diff --git a/src/dns.rs b/src/dns.rs
index 2280815..938025e 100644
--- a/src/dns.rs
+++ b/src/dns.rs
@@ -59,13 +59,13 @@ pub async fn dns_server(stack: Stack<'static>) {
for q in msg.questions() {
match q.kind() {
QueryKind::A => {
- if q.name() == "ttt.wifi" || q.name() == "www.ttt.wifi" {
+ if q.name() == "pico.wifi" || q.name() == "www.pico.wifi" {
res.add_question(&q);
res.add_answer(&Answer {
name: q.name().clone(),
kind: QueryKind::A,
class: QueryClass::IN,
- ttl: 60,
+ ttl: 600,
rdata: &[192, 254, 0, 2],
});
info!("Dns: Giving {}", q.name());
diff --git a/src/main.rs b/src/main.rs
index b0adb48..8774e8c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -25,7 +25,7 @@ mod dhcp;
#[cfg(feature = "dns")]
mod dns;
-mod game;
+mod apps;
mod socket;
bind_interrupts!(struct Irqs {
@@ -115,7 +115,7 @@ async fn main(spawner: Spawner) {
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,
@@ -126,8 +126,8 @@ async fn main(spawner: Spawner) {
unwrap(spawner.spawn(net_task(runner))).await;
#[cfg(not(feature = "wifi-connect"))]
- control.start_ap_open("TicTacToe", 5).await;
- // control.start_ap_wpa2("TicTacToe", "password", 5).await;
+ control.start_ap_open("pico", 5).await;
+ // control.start_ap_wpa2("pico", "password", 5).await;
#[cfg(feature = "wifi-connect")]
{
@@ -169,8 +169,19 @@ async fn main(spawner: Spawner) {
#[cfg(feature = "dns")]
unwrap(spawner.spawn(dns::dns_server(stack))).await;
- unwrap(spawner.spawn(socket::listen_task(stack, game::Team::Zero, 80))).await;
- unwrap(spawner.spawn(socket::listen_task(stack, game::Team::One, 81))).await;
+ unwrap(spawner.spawn(socket::index_listen_task(stack, 80))).await;
+ unwrap(spawner.spawn(socket::ttt_listen_task(
+ stack,
+ apps::ttt::TttApp::new(apps::ttt::Team::Zero),
+ 8080,
+ )))
+ .await;
+ unwrap(spawner.spawn(socket::ttt_listen_task(
+ stack,
+ apps::ttt::TttApp::new(apps::ttt::Team::One),
+ 8081,
+ )))
+ .await;
}
#[cfg(feature = "wifi-connect")]
diff --git a/src/socket.rs b/src/socket.rs
index 7e67863..9355839 100644
--- a/src/socket.rs
+++ b/src/socket.rs
@@ -6,10 +6,19 @@ use embedded_io_async::Write as _;
use heapless::Vec;
use log::{info, warn};
-use crate::game::{GameClient, Team};
+use crate::apps::{App, index::IndexApp, ttt};
#[embassy_executor::task(pool_size = 2)]
-pub async fn listen_task(stack: embassy_net::Stack<'static>, team: Team, port: u16) {
+pub async fn ttt_listen_task(stack: embassy_net::Stack<'static>, app: ttt::TttApp, port: u16) {
+ listen_task(stack, app, port).await
+}
+
+#[embassy_executor::task(pool_size = 2)]
+pub async fn index_listen_task(stack: embassy_net::Stack<'static>, port: u16) {
+ listen_task(stack, IndexApp, port).await
+}
+
+pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App, port: u16) {
// loop {
// info!("team:{:?}", team);
// Timer::after_millis(0).await;
@@ -19,22 +28,20 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, team: Team, port: u
let mut buf = [0; 4096];
let mut res_head_buf = Vec::::new();
- let mut game_client = GameClient::new(team);
-
loop {
Timer::after_secs(0).await;
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
socket.set_timeout(Some(Duration::from_secs(30)));
- info!("Socket {:?}: Listening on TCP:{}...", team, port);
+ info!("Socket {}: Listening on TCP:{}...", app.socket_name(), port);
if let Err(e) = socket.accept(port).await {
warn!("accept error: {:?}", e);
continue;
}
info!(
- "Socket {:?}: Received connection from {:?}",
- team,
+ "Socket {}: Received connection from {:?}",
+ app.socket_name(),
socket.remote_endpoint()
);
@@ -47,7 +54,7 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, team: Team, port: u
}
Ok(n) => n,
Err(e) => {
- warn!("Socket {:?}: read error: {:?}", team, e);
+ warn!("Socket {}: read error: {:?}", app.socket_name(), e);
break;
}
};
@@ -114,15 +121,15 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, team: Team, port: u
}
};
- info!("Socket {:?}: {:?} request for {}", team, request_type, path);
+ info!(
+ "Socket {}: {:?} request for {}",
+ app.socket_name(),
+ request_type,
+ path
+ );
Timer::after_secs(0).await;
let (code, res_type, res_content): (HttpResCode, &str, &[u8]) = match path {
- "/" => (
- HttpResCode::Ok,
- "html",
- include_bytes!("../static/index.html"),
- ),
"/htmx.js" => (
HttpResCode::Ok,
"javascript",
@@ -131,7 +138,7 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, team: Team, port: u
#[cfg(not(debug_assertions))]
include_bytes!("../static/htmx.min.js"),
),
- p => game_client.handle_request(p).await,
+ p => app.handle_request(p).await,
};
res_head_buf.clear();
| |