This commit is contained in:
Arkitu 2025-05-01 21:30:55 +02:00
parent f085ceba1e
commit b90c978a38
7 changed files with 243 additions and 20 deletions

7
Cargo.lock generated
View File

@ -1111,6 +1111,7 @@ dependencies = [
"panic-probe", "panic-probe",
"portable-atomic", "portable-atomic",
"rand_core", "rand_core",
"ringbuffer",
"serde", "serde",
"serde-json-core", "serde-json-core",
"static_cell", "static_cell",
@ -1304,6 +1305,12 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "ringbuffer"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53"
[[package]] [[package]]
name = "rp-pac" name = "rp-pac"
version = "7.0.0" version = "7.0.0"

View File

@ -56,4 +56,5 @@ serde = { version = "*", optional = true, default-features = false, features = [
"derive", "derive",
] } ] }
dhcparse = { version = "*", default-features = false, optional = true } dhcparse = { version = "*", default-features = false, optional = true }
dnsparse = { version = "*", optional = true } dnsparse = { version = "*", optional = true }
ringbuffer = { version = "*", default-features = false }

53
src/apps/chat.html Normal file
View File

@ -0,0 +1,53 @@
<!doctype html>
<head>
<script src="./htmx.js"></script>
<style type="text/css">
body {
/* #grid {
.cell {
border: 1px dotted black;
padding: 33%;
}
display: grid;
border: 1px solid black;
grid-template-rows: 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
} */
}
</style>
</head>
<html>
<body>
<h1>Chat</h1>
<!-- <div
class="message"
hx-get="/chat/message/abs/0?load=3"
hx-target="this"
hx-swap="outerHTML"
hx-trigger="load"
></div>
<div class="message"></div>
<div class="message"></div>
<div class="message"></div> -->
<form id="login-page">
Enter your name :
<input
id="username"
type="text"
autocomplete="username"
minlength="3"
maxlength="16"
required
/>
<button
hx-get="/chat/connect"
hx-include="#username"
hx-target="#login-page"
hx-swap="outerHTML"
>
Connect
</button>
</form>
</body>
</html>

163
src/apps/chat.rs Normal file
View File

@ -0,0 +1,163 @@
use core::fmt::Write;
use heapless::{String, Vec};
use log::info;
use pico_website::unwrap;
use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
use crate::socket::HttpResCode;
use super::App;
const MEMORY_SIZE: usize = 16;
pub struct ChatApp {
res_buf: Vec<u8, 2048>,
msgs: ConstGenericRingBuffer<Message, MEMORY_SIZE>,
next_msg: u16,
}
impl ChatApp {
pub fn new() -> Self {
let mut msgs = ConstGenericRingBuffer::new();
let mut aut0 = String::new();
write!(&mut aut0, "rael").unwrap();
let mut msg0 = String::new();
write!(&mut msg0, "Prout...").unwrap();
msgs.push(Message {
author: aut0,
content: msg0,
});
let mut aut1 = String::new();
write!(&mut aut1, "josh").unwrap();
let mut msg1 = String::new();
write!(&mut msg1, "Salut !\nJe m'appelle...").unwrap();
msgs.push(Message {
author: aut1,
content: msg1,
});
Self {
res_buf: Vec::new(),
msgs,
next_msg: 2,
}
}
}
impl App for ChatApp {
fn socket_name(&self) -> &'static str {
"chat"
}
async fn handle_request<'a>(&'a mut self, path: &str) -> (HttpResCode, &'static str, &'a [u8]) {
match path {
"/" | "/index" | "/index.html" | "/chat" | "/chat.html" => {
(HttpResCode::Ok, "html", include_bytes!("./chat.html"))
}
path => {
let (path, args) = path.split_once('?').unwrap_or((path, ""));
let mut load = None;
let mut username = None;
for arg in args.split('&') {
match arg.split_once('=') {
Some(("load", n)) => {
let n: u16 = match n.parse() {
Ok(v) => v,
Err(_) => return (HttpResCode::BadRequest, "", &[]),
};
if n > 0 {
load = Some(n);
}
}
Some(("username", u)) => {
if u.len() < 3 || u.len() > 16 {
return (HttpResCode::BadRequest, "", &[]);
}
username = Some(u);
}
_ => {}
}
}
if path == "/chat" && username.is_some() {
return (
HttpResCode::Ok,
"html",
"<div \
class=\"message\" \
hx-get=\"/chat/message/abs/0?load=\" \
hx-target=\"this\" \
hx-swap=\"outerHTML\" \
hx-trigger=\"load\" \
></div>",
);
} else if path.starts_with("/chat/message/abs/") && path.len() > 18 {
let msg_id: u16 = match path[18..].parse() {
Ok(n) => n,
Err(_) => return (HttpResCode::BadRequest, "", &[]),
};
info!("msg_id = {:?}", msg_id);
if msg_id >= self.next_msg {
return (HttpResCode::BadRequest, "", &[]);
}
if msg_id < self.next_msg.saturating_sub(MEMORY_SIZE as u16 + 1) {
return (HttpResCode::NoContent, "", &[]);
}
let msg = match self
.msgs
.get_signed((msg_id as isize) + 1 - (self.next_msg as isize))
{
Some(msg) => msg,
None => return (HttpResCode::NoContent, "", &[]),
};
self.res_buf.clear();
unwrap(write!(&mut self.res_buf, "<div class=\"message\"")).await;
if let Some(n) = load {
unwrap(write!(
&mut self.res_buf,
" hx-get=\"/chat/message/abs/{}?load={}\" \
hx-target=\"this\" \
hx-swap=\"afterend\" \
hx-trigger=\"load\"",
msg_id + 1,
n - 1
))
.await;
}
unwrap(write!(
&mut self.res_buf,
"><b>{}</b>: {}</div>",
msg.author, msg.content
))
.await;
// unwrap(write!(
// &mut self.res_buf,
// "<div class=\"message\" \
// hx-get=\"/chat/message{}\" \
// hx-target=\"next .message\" \
// hx-swap=\"outerHTML\" \
// hx-trigger=\"load\"\
// >\
// <b>{}</b>: {}\
// </div>",
// match load {
// Some(target) => "hx-get=\"/chat/message{}\" \
// hx-target=\"next .message\" \
// hx-swap=\"outerHTML\" \
// hx-trigger=\"load\"\"
// }
// (msg_num + 1),
// msg.author,
// msg.content
// ))
// .await;
return (HttpResCode::Ok, "html", &self.res_buf);
} else {
(HttpResCode::NotFound, "", &[])
}
}
}
}
}
struct Message {
author: String<16>,
content: String<256>,
}

View File

@ -1,5 +1,6 @@
use crate::socket::HttpResCode; use crate::socket::HttpResCode;
pub mod chat;
pub mod index; pub mod index;
pub mod ttt; pub mod ttt;

View File

@ -170,18 +170,9 @@ async fn main(spawner: Spawner) {
unwrap(spawner.spawn(dns::dns_server(stack))).await; unwrap(spawner.spawn(dns::dns_server(stack))).await;
unwrap(spawner.spawn(socket::index_listen_task(stack, 80))).await; unwrap(spawner.spawn(socket::index_listen_task(stack, 80))).await;
unwrap(spawner.spawn(socket::ttt_listen_task( unwrap(spawner.spawn(socket::ttt_listen_task(stack, apps::ttt::Team::Zero, 8080))).await;
stack, unwrap(spawner.spawn(socket::ttt_listen_task(stack, apps::ttt::Team::One, 8081))).await;
apps::ttt::TttApp::new(apps::ttt::Team::Zero), unwrap(spawner.spawn(socket::chat_listen_task(stack, 8082))).await;
8080,
)))
.await;
unwrap(spawner.spawn(socket::ttt_listen_task(
stack,
apps::ttt::TttApp::new(apps::ttt::Team::One),
8081,
)))
.await;
} }
#[cfg(feature = "wifi-connect")] #[cfg(feature = "wifi-connect")]

View File

@ -6,11 +6,11 @@ use embedded_io_async::Write as _;
use heapless::Vec; use heapless::Vec;
use log::{info, warn}; use log::{info, warn};
use crate::apps::{App, index::IndexApp, ttt}; use crate::apps::{App, chat, index::IndexApp, ttt};
#[embassy_executor::task(pool_size = 2)] #[embassy_executor::task(pool_size = 2)]
pub async fn ttt_listen_task(stack: embassy_net::Stack<'static>, app: ttt::TttApp, port: u16) { pub async fn ttt_listen_task(stack: embassy_net::Stack<'static>, team: ttt::Team, port: u16) {
listen_task(stack, app, port).await listen_task(stack, ttt::TttApp::new(team), port).await
} }
#[embassy_executor::task(pool_size = 2)] #[embassy_executor::task(pool_size = 2)]
@ -18,15 +18,20 @@ pub async fn index_listen_task(stack: embassy_net::Stack<'static>, port: u16) {
listen_task(stack, IndexApp, port).await listen_task(stack, IndexApp, port).await
} }
#[embassy_executor::task(pool_size = 1)]
pub async fn chat_listen_task(stack: embassy_net::Stack<'static>, port: u16) {
listen_task(stack, chat::ChatApp::new(), port).await
}
pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App, port: u16) { pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App, port: u16) {
// loop { // loop {
// info!("team:{:?}", team); // info!("team:{:?}", team);
// Timer::after_millis(0).await; // Timer::after_millis(0).await;
// } // }
let mut rx_buffer = [0; 4096]; let mut rx_buffer = [0; 8192];
let mut tx_buffer = [0; 4096]; let mut tx_buffer = [0; 8192];
let mut buf = [0; 4096]; let mut buf = [0; 1024];
let mut res_head_buf = Vec::<u8, 4096>::new(); let mut res_head_buf = Vec::<u8, 1024>::new();
loop { loop {
Timer::after_secs(0).await; Timer::after_secs(0).await;
@ -191,6 +196,7 @@ impl Into<&str> for HttpRequestType {
pub enum HttpResCode { pub enum HttpResCode {
Ok, Ok,
NoContent, NoContent,
BadRequest,
NotFound, NotFound,
Forbidden, Forbidden,
} }
@ -199,6 +205,7 @@ impl Into<&str> for HttpResCode {
match self { match self {
HttpResCode::Ok => "HTTP/1.1 200 OK", HttpResCode::Ok => "HTTP/1.1 200 OK",
HttpResCode::NoContent => "HTTP/1.1 204 NO CONTENT", HttpResCode::NoContent => "HTTP/1.1 204 NO CONTENT",
HttpResCode::BadRequest => "HTTP/1.1 400 BAD REQUEST",
HttpResCode::NotFound => "HTTP/1.1 404 NOT FOUND", HttpResCode::NotFound => "HTTP/1.1 404 NOT FOUND",
HttpResCode::Forbidden => "HTTP/1.1 403 FORBIDDEN", HttpResCode::Forbidden => "HTTP/1.1 403 FORBIDDEN",
} }