Compare commits

...

3 commits

Author SHA1 Message Date
Daniel Bulant
9ef6b48de0
question improvements 2026-05-04 12:52:11 +02:00
Daniel Bulant
3977f101ad
switch to udp 2026-05-04 12:51:58 +02:00
Daniel Bulant
7c51ee47b5
working tcp 2026-05-04 12:38:00 +02:00
5 changed files with 109 additions and 63 deletions

View file

@ -38,7 +38,7 @@ async function getAlbumReleaseYear({
type: "numeric", type: "numeric",
text: `What's the release year of ${subject}?`, text: `What's the release year of ${subject}?`,
correct, correct,
range: getQuestionRange(correct, 5), range: getQuestionRange(correct, 10, 3),
points: 10, points: 10,
}; };
} }

View file

@ -77,10 +77,14 @@ export function getQuestionDistanceScore(
export function getQuestionRange( export function getQuestionRange(
correctValue: number, correctValue: number,
tolerance: number, tolerance: number,
randomness: number,
): { min: number; max: number } { ): { min: number; max: number } {
const min = Math.floor(correctValue - tolerance);
const max = Math.ceil(correctValue + tolerance);
const range = max - min;
return { return {
min: Math.floor(correctValue - tolerance), min: min + Math.floor(Math.random() * range * randomness),
max: Math.ceil(correctValue + tolerance), max: max - Math.floor(Math.random() * range * randomness),
}; };
} }

View file

@ -2,41 +2,45 @@ import type {
PartySocketEvent, PartySocketEvent,
PartyState, PartyState,
} from "../api/src/party-types"; } from "../api/src/party-types";
let ws: WebSocket | null = null; let ws: WebSocket | null = new WebSocket("ws://localhost:3000/api/dev-socket/ws");
Bun.listen({ const socket = await Bun.udpSocket({
hostname: "0.0.0.0",
port: 7070, port: 7070,
socket: { socket: {
data(socket, data) { data(socket, buf, port, addr) {
// in1={} in2={} in3={} in4={} angle={} console.log(`message from ${addr}:${port}:`);
console.log("recv", data) console.log(buf.toString());
}, // message received from client },
open(socket) {
console.log("Connected");
ws = new WebSocket("ws://localhost:3000/api/dev-socket/ws");
ws.onmessage = e => {
const data = JSON.parse(e.data) as PartySocketEvent;
switch (data.type) {
case "party_status":
const { party: { data: { currentQuestion } } } = data;
console.log(currentQuestion)
let text = currentQuestion?.text
if (text) {
ws?.send(text)
}
break;
}
}
}, // socket opened
close(socket, error) {
ws?.close();
ws = null;
}, // socket closed
drain(socket) {}, // socket ready for more data
error(socket, error) {}, // error handler
}, },
}); });
ws.onerror = e => {
console.error(e)
}
ws.onopen = () => {
console.log("WebSocket open")
}
ws.onmessage = e => {
const data = JSON.parse(e.data) as PartySocketEvent;
console.log(data)
switch (data.type) {
case "party_status":
const { party } = data;
if (!party) return;
const partyData = party.data;
if (!partyData) return;
const { currentQuestion } = partyData
console.log(currentQuestion)
let text = currentQuestion?.text
if (text) {
ws?.send(text)
}
break;
}
}
console.log("Started on :7070") console.log("Started on :7070")

View file

@ -2,8 +2,12 @@
#![no_main] #![no_main]
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use core::convert::Infallible;
use core::net::SocketAddrV4;
use core::ops::Deref;
use core::str::FromStr; use core::str::FromStr;
use embassy_net::tcp::ConnectError; use embassy_net::tcp::ConnectError;
use embassy_net::udp::{PacketMetadata, UdpMetadata, UdpSocket};
use embedded_io::ReadReady; use embedded_io::ReadReady;
use ag_lcd::{Blink, Cursor, Display, LcdDisplay}; use ag_lcd::{Blink, Cursor, Display, LcdDisplay};
@ -164,34 +168,25 @@ async fn main(spawner: Spawner) {
let in4 = Input::new(p.PIN_21, embassy_rp::gpio::Pull::Up); let in4 = Input::new(p.PIN_21, embassy_rp::gpio::Pull::Up);
let mut rx_buffer = [0; 4096]; let mut rx_buffer = [0; 4096];
let mut rx_meta = [PacketMetadata::EMPTY; 16];
let mut tx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096];
let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); let mut tx_meta = [PacketMetadata::EMPTY; 16];
socket.set_timeout(Some(Duration::from_secs(10)));
let mut socket = UdpSocket::new(
stack,
&mut rx_meta,
&mut rx_buffer,
&mut tx_meta,
&mut tx_buffer,
);
socket.bind(7070).unwrap();
led.set_low();
info!("Connecting...");
uwrite!(lcd, "con2.");
let host_addr = embassy_net::Ipv4Address::from_str("192.168.12.1").unwrap(); let host_addr = embassy_net::Ipv4Address::from_str("192.168.12.1").unwrap();
if let Err(e) = socket.connect((host_addr, 7070)).await { let target = UdpMetadata::from(SocketAddrV4::new(host_addr, 7070));
lcd.clear(); uwrite!(lcd, "udp.");
uwrite!(lcd, "conerr");
Timer::after(Duration::from_micros(100)).await;
lcd.set_position(0, 1);
let emsg = match e {
ConnectError::ConnectionReset => "rst",
ConnectError::InvalidState => "inv",
ConnectError::TimedOut => "tout",
ConnectError::NoRoute => "nroute",
};
uwrite!(lcd, "{}", emsg);
warn!("connect error: {:?}", e);
} else {
uwrite!(lcd, "conok");
}
info!("Connected to {:?}", socket.remote_endpoint());
let delay = Duration::from_millis(100); let delay = Duration::from_millis(100);
let mut buffer = ArrayVec::<u8, 512>::new(); let mut buffer = WriteBuffer::<1024>::new();
loop { loop {
let in1 = in1.is_low(); let in1 = in1.is_low();
let in2 = in2.is_low(); let in2 = in2.is_low();
@ -201,7 +196,7 @@ async fn main(spawner: Spawner) {
{ {
use embedded_io::Write; use embedded_io::Write;
let _ = core::writeln!( let _ = core::writeln!(
&mut buffer[..], &mut buffer,
"in1={} in2={} in3={} in4={} angle={}", "in1={} in2={} in3={} in4={} angle={}",
in1, in1,
in2, in2,
@ -211,12 +206,12 @@ async fn main(spawner: Spawner) {
); );
} }
{ {
use embedded_io_async::Write; let _ = socket.send_to(&*buffer, target).await;
let _ = socket.write_all(&*buffer).await; buffer.0.clear();
} }
if socket.read_ready().unwrap_or(false) { if socket.may_recv() {
let mut rx_buffer = [0; 4096]; let mut rx_buffer = [0; 4096];
let n = socket.read(&mut rx_buffer).await.unwrap_or(0); let (n, ep) = socket.recv_from(&mut rx_buffer).await.unwrap();
if n > 0 { if n > 0 {
lcd.clear(); lcd.clear();
lcd.home(); lcd.home();
@ -234,6 +229,45 @@ async fn main(spawner: Spawner) {
} }
} }
struct WriteBuffer<const CAP: usize>(ArrayVec<u8, CAP>);
impl<const CAP: usize> Deref for WriteBuffer<CAP> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const CAP: usize> WriteBuffer<CAP> {
fn new() -> Self {
Self(ArrayVec::new())
}
}
impl<const CAP: usize> embedded_io::ErrorType for WriteBuffer<CAP> {
type Error = Infallible;
}
impl<const CAP: usize> embedded_io::Write for WriteBuffer<CAP> {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let _ = self.0.try_extend_from_slice(buf); //silently fails!
Ok(buf.len())
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.write(buf)?;
Ok(())
}
#[inline]
fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 {
loop { loop {
if let Some(config) = stack.config_v4() { if let Some(config) = stack.config_v4() {

View file

@ -54,7 +54,11 @@ export function Question() {
setSelectedValue(question.type === "numeric" ? question.range.min : null); setSelectedValue(question.type === "numeric" ? question.range.min : null);
}, [question]); }, [question]);
if (!question) return null; if (!question) return (
<Section>
<SectionTitle>Preparing quiz...</SectionTitle>
</Section>
);
const partyId = party.id; const partyId = party.id;
const timeLeft = formatTimeLeft(question.endTimestamp - now); const timeLeft = formatTimeLeft(question.endTimestamp - now);