Compare commits

...

4 commits

Author SHA1 Message Date
Daniel Bulant
f0d7abdf0e
switch back to tcp 2026-05-05 14:55:30 +02:00
Daniel Bulant
21ae0e4968
small refactor 2026-05-05 13:33:18 +02:00
Daniel Bulant
fb05e624b7
hide party invite 2026-05-04 13:41:33 +02:00
Daniel Bulant
ba5274e726
fixup dev socket 2026-05-04 13:41:27 +02:00
8 changed files with 118 additions and 87 deletions

View file

@ -10,6 +10,7 @@ import { partyApp } from "./routes/party";
import { partySocketApp, pubsub } from "./routes/party-socket"; import { partySocketApp, pubsub } from "./routes/party-socket";
import { quizRoutes } from "./routes/quiz.ts"; import { quizRoutes } from "./routes/quiz.ts";
import { statsApp } from "./routes/stats.ts"; import { statsApp } from "./routes/stats.ts";
import { deviceSocketApp } from "./routes/device-socket.ts";
const app = new Elysia() const app = new Elysia()
.use(betterAuthElysia) .use(betterAuthElysia)
@ -20,7 +21,8 @@ const app = new Elysia()
.use(partyApp) .use(partyApp)
.use(partyAnalysisApp) .use(partyAnalysisApp)
.use(partySocketApp) .use(partySocketApp)
.use(quizRoutes) .use(quizRoutes)
.use(deviceSocketApp)
.get("/", () => ({ ok: true })), .get("/", () => ({ ok: true })),
) )
.listen(4000); .listen(4000);

View file

@ -8,7 +8,7 @@ import {
userTopic, userTopic,
} from "./party-socket"; } from "./party-socket";
export const partySocketApp = new Elysia().group("/dev-socket", (app) => export const deviceSocketApp = new Elysia().group("/dev-socket", (app) =>
app app
.get("/test", () => ({ ok: 1 })) .get("/test", () => ({ ok: 1 }))
.ws("/ws", { .ws("/ws", {

View file

@ -2,19 +2,28 @@ import type {
PartySocketEvent, PartySocketEvent,
PartyState, PartyState,
} from "../api/src/party-types"; } from "../api/src/party-types";
let ws: WebSocket | null = new WebSocket("ws://localhost:3000/api/dev-socket/ws");
let last_ep_port: null|number = null
let last_ep_addr: null|string = null
const socket = await Bun.udpSocket({ const socket = await Bun.udpSocket({
port: 7070, port: 7070,
socket: { socket: {
data(socket, buf, port, addr) { data(socket, buf, port, addr) {
console.log(`message from ${addr}:${port}:`); // console.log(`message from ${addr}:${port}:`);
console.log(buf.toString()); last_ep_addr = addr
last_ep_port = port
const str = buf.toString();
console.log(str);
const opts = str.split(" ").map(t => t.trim().split("="))
const parsedOpts = Object.fromEntries(opts)
const { in1, in2, in3, in4, angle } = parsedOpts
}, },
}, },
}); });
let ws: WebSocket | null = new WebSocket("ws://localhost:4000/api/dev-socket/ws");
ws.onerror = e => { ws.onerror = e => {
console.error(e) console.error(e)
@ -36,8 +45,9 @@ ws.onmessage = e => {
const { currentQuestion } = partyData const { currentQuestion } = partyData
console.log(currentQuestion) console.log(currentQuestion)
let text = currentQuestion?.text let text = currentQuestion?.text
if (text) { if (text && last_ep_port !== null && last_ep_addr !== null) {
ws?.send(text) socket.send(text, last_ep_port, last_ep_addr)
// ws?.send(text)
} }
break; break;
} }

57
pico/src/buffer.rs Normal file
View file

@ -0,0 +1,57 @@
use core::{convert::Infallible, ops::Deref};
use arrayvec::ArrayVec;
use embassy_futures::yield_now;
use embassy_net::Stack;
pub 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> {
pub fn new() -> Self {
Self(ArrayVec::new())
}
pub fn clear(&mut self) {
self.0.clear();
}
}
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(())
}
}
pub async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 {
loop {
if let Some(config) = stack.config_v4() {
return config.clone();
}
yield_now().await;
}
}

View file

@ -1,23 +1,18 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use arrayvec::ArrayVec;
use core::convert::Infallible;
use core::net::SocketAddrV4; 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::TcpSocket;
use embassy_net::udp::{PacketMetadata, UdpMetadata, UdpSocket}; use embassy_net::udp::{PacketMetadata, UdpMetadata, UdpSocket};
use embedded_io::ReadReady;
use crate::buffer::{WriteBuffer, wait_for_config};
use ag_lcd::{Blink, Cursor, Display, LcdDisplay}; use ag_lcd::{Blink, Cursor, Display, LcdDisplay};
use as5600::As5600; use as5600::As5600;
use cyw43::{JoinOptions, aligned_bytes}; use cyw43::{JoinOptions, aligned_bytes};
use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi}; use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi};
use defmt::*; use defmt::*;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_futures::yield_now;
use embassy_net::tcp::client::{TcpClient, TcpClientState};
use embassy_net::{Stack, StackResources}; use embassy_net::{Stack, StackResources};
use embassy_rp::clocks::RoscRng; use embassy_rp::clocks::RoscRng;
use embassy_rp::gpio::{Input, Level, Output}; use embassy_rp::gpio::{Input, Level, Output};
@ -29,8 +24,11 @@ use embassy_rp::{peripherals::USB, usb};
use embassy_time::{Delay, Duration, Timer}; use embassy_time::{Delay, Duration, Timer};
use static_cell::StaticCell; use static_cell::StaticCell;
use ufmt::{uWrite, uwrite}; use ufmt::{uWrite, uwrite};
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
mod buffer;
bind_interrupts!(struct Irqs { bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>; PIO0_IRQ_0 => InterruptHandler<PIO0>;
DMA_IRQ_0 => dma::InterruptHandler<DMA_CH0>; DMA_IRQ_0 => dma::InterruptHandler<DMA_CH0>;
@ -55,7 +53,7 @@ async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'sta
runner.run().await runner.run().await
} }
const WIFI_NETWORK: &str = "aura"; const WIFI_NETWORK: &str = "flamme";
const WIFI_PASSWORD: &str = "12345678"; const WIFI_PASSWORD: &str = "12345678";
#[embassy_executor::main] #[embassy_executor::main]
@ -98,15 +96,15 @@ async fn main(spawner: Spawner) {
let edelay = &mut Delay; let edelay = &mut Delay;
let mut lcd: LcdDisplay<_, _> = LcdDisplay::new( let mut lcd: LcdDisplay<_, _> = LcdDisplay::new(
Output::new(p.PIN_14, Level::Low),
Output::new(p.PIN_15, Level::Low), Output::new(p.PIN_15, Level::Low),
Output::new(p.PIN_14, Level::Low),
edelay, edelay,
) )
.with_half_bus( .with_half_bus(
Output::new(p.PIN_10, Level::Low),
Output::new(p.PIN_11, Level::Low),
Output::new(p.PIN_12, Level::Low),
Output::new(p.PIN_13, Level::Low), Output::new(p.PIN_13, Level::Low),
Output::new(p.PIN_12, Level::Low),
Output::new(p.PIN_11, Level::Low),
Output::new(p.PIN_10, Level::Low),
) )
.with_display(Display::On) .with_display(Display::On)
.with_blink(Blink::On) .with_blink(Blink::On)
@ -168,22 +166,29 @@ 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 rx_meta = [PacketMetadata::EMPTY; 16];
let mut tx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096];
let mut tx_meta = [PacketMetadata::EMPTY; 16]; // let mut tx_meta = [PacketMetadata::EMPTY; 16];
let mut socket = UdpSocket::new( // let mut socket = UdpSocket::new(
stack, // stack,
&mut rx_meta, // &mut rx_meta,
&mut rx_buffer, // &mut rx_buffer,
&mut tx_meta, // &mut tx_meta,
&mut tx_buffer, // &mut tx_buffer,
); // );
socket.bind(7070).unwrap(); // socket.bind(7070).unwrap();
let host_addr = embassy_net::Ipv4Address::from_str("192.168.12.1").unwrap(); let host_addr = embassy_net::Ipv4Address::from_str("84.238.32.253").unwrap();
let target = UdpMetadata::from(SocketAddrV4::new(host_addr, 7070)); // let target = UdpMetadata::from(SocketAddrV4::new(host_addr, 7070));
uwrite!(lcd, "udp."); let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
socket.set_timeout(Some(Duration::from_secs(10)));
if let Err(e) = socket.connect(SocketAddrV4::new(host_addr, 7070)).await {
uwrite!(lcd, "tcperr");
error!("tcp connect error: {}", e);
} else {
uwrite!(lcd, "tcpok");
}
let delay = Duration::from_millis(100); let delay = Duration::from_millis(100);
let mut buffer = WriteBuffer::<1024>::new(); let mut buffer = WriteBuffer::<1024>::new();
@ -206,12 +211,12 @@ async fn main(spawner: Spawner) {
); );
} }
{ {
let _ = socket.send_to(&*buffer, target).await; let _ = socket.write(&*buffer).await;
buffer.0.clear(); buffer.clear();
} }
if socket.may_recv() { if socket.may_recv() {
let mut rx_buffer = [0; 4096]; let mut rx_buffer = [0; 4096];
let (n, ep) = socket.recv_from(&mut rx_buffer).await.unwrap(); let n = socket.read(&mut rx_buffer).await.unwrap();
if n > 0 { if n > 0 {
lcd.clear(); lcd.clear();
lcd.home(); lcd.home();
@ -228,51 +233,3 @@ async fn main(spawner: Spawner) {
Timer::after(delay).await; Timer::after(delay).await;
} }
} }
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 {
loop {
if let Some(config) = stack.config_v4() {
return config.clone();
}
yield_now().await;
}
}

View file

@ -1,4 +1,4 @@
import { treaty } from "@elysiajs/eden"; import { treaty } from "@elysiajs/eden";
import type { App } from "../../../api/src/index"; import type { App } from "../../../api/src/index";
export const client = treaty<App>("127.0.0.1:3000", {}); export const client = treaty<App>("aura.rpi1.danbulant.cloud", {});

View file

@ -17,7 +17,7 @@ function App() {
<MainContent> <MainContent>
<UserInfo /> <UserInfo />
{!user?.lastSyncAt && <SyncButton />} {!user?.lastSyncAt && <SyncButton />}
{user && <PartyQr />} {user && party?.data?.status != "running" && <PartyQr />}
{party && !party.data && members.length > 1 && <StartParty />} {party && !party.data && members.length > 1 && <StartParty />}
{party?.data && <PartyView />} {party?.data && <PartyView />}
</MainContent> </MainContent>

View file

@ -11,14 +11,19 @@ const config = defineConfig({
plugins: [ plugins: [
devtools(), devtools(),
tailwindcss(), tailwindcss(),
tanstackStart(), tanstackStart({
spa: {
enabled: true
}
}),
viteReact({ viteReact({
babel: { babel: {
plugins: ["babel-plugin-react-compiler"], plugins: ["babel-plugin-react-compiler"],
}, },
}), }),
], ],
server: { server: {
allowedHosts: ["aura.rpi1.danbulant.cloud"],
proxy: { proxy: {
"/api": { "/api": {
target: "http://localhost:4000", target: "http://localhost:4000",