From 2bcdb3451568401867230604b386854e9e860cf2 Mon Sep 17 00:00:00 2001 From: Daniel Bulant Date: Tue, 12 May 2026 15:55:51 +0200 Subject: [PATCH] add serde --- dev-proxy/index.ts | 52 +++++++++++----- esp32/Cargo.lock | 21 +++++++ esp32/Cargo.toml | 2 + esp32/src/main.rs | 142 ++++++++++++++++++++------------------------ esp32/src/screen.rs | 4 +- 5 files changed, 127 insertions(+), 94 deletions(-) diff --git a/dev-proxy/index.ts b/dev-proxy/index.ts index 882f512..f7ad30e 100644 --- a/dev-proxy/index.ts +++ b/dev-proxy/index.ts @@ -4,6 +4,12 @@ type ApiEnvelope = | { type: "hello" } | { type: "device_event"; deviceId: string; event: unknown }; +type DeviceMessage = { + DeviceId: string; +} | { + QuizResponse: number; +} + const sockets = new Map(); const socketIds = new WeakMap(); const apiSocket = new WebSocket("ws://localhost:4000/api/dev-socket/ws"); @@ -16,7 +22,8 @@ function registerSocket(socket: Socket, deviceId: string) { const existing = sockets.get(deviceId); if (existing && existing !== socket) existing.end(); sockets.set(deviceId, socket); - socketIds.set(socket, deviceId); + socketIds.set(socket, deviceId); + console.log("Registered", socket.remoteAddress, deviceId); } const listener = Bun.listen({ @@ -24,27 +31,38 @@ const listener = Bun.listen({ hostname: "0.0.0.0", socket: { open(socket) { - socket.setKeepAlive(true); + socket.setKeepAlive(true); + console.log("Connection", socket.remoteAddress, socket.remotePort); }, data(socket, buf) { - const raw = new TextDecoder().decode(buf).trim(); - if (!raw) return; + const raw = new TextDecoder().decode(buf).trim(); + let data: DeviceMessage; + try { + data = JSON.parse(raw); + } catch { + return; + } + console.log("Data", socket.remoteAddress, data); + if (!data) return; - const currentDeviceId = socketDeviceId(socket); - if (!currentDeviceId) { - registerSocket(socket, raw); - return; - } + if ("DeviceId" in data) { + registerSocket(socket, data.DeviceId); + return; + } + if ("QuizResponse" in data) { - apiSocket?.send( - JSON.stringify({ - type: "device_message", - deviceId: currentDeviceId, - payload: raw, - }), - ); + } + + // apiSocket?.send( + // JSON.stringify({ + // type: "device_message", + // deviceId: currentDeviceId, + // payload: raw, + // }), + // ); }, close(socket) { + console.log("Connection", socket.remoteAddress); const deviceId = socketDeviceId(socket); if (deviceId && sockets.get(deviceId) === socket) { sockets.delete(deviceId); @@ -67,6 +85,8 @@ apiSocket.onmessage = (e) => { socket.write(`${JSON.stringify(message.event)}\n`); }; + + apiSocket.onerror = (error) => { console.error(error); }; diff --git a/esp32/Cargo.lock b/esp32/Cargo.lock index 066a390..e5e4d15 100644 --- a/esp32/Cargo.lock +++ b/esp32/Cargo.lock @@ -1019,6 +1019,8 @@ dependencies = [ "owned_str", "panic-probe", "portable-atomic", + "serde", + "serde_json", "static_cell", "ufmt 0.2.0", ] @@ -1698,6 +1700,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -2178,3 +2193,9 @@ dependencies = [ "quote", "syn 2.0.117", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/esp32/Cargo.toml b/esp32/Cargo.toml index dda3118..0b5ac38 100644 --- a/esp32/Cargo.toml +++ b/esp32/Cargo.toml @@ -12,6 +12,8 @@ codegen-units = 1 [dependencies] [target.'cfg(target_arch = "xtensa")'.dependencies] +serde = { version = "1.0.228", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } arrayvec = { version = "0.7.6", default-features = false } ag-lcd = { version = "0.3", features = ["ufmt"] } as5600 = "0.8.0" diff --git a/esp32/src/main.rs b/esp32/src/main.rs index 3f1740e..134f3f9 100644 --- a/esp32/src/main.rs +++ b/esp32/src/main.rs @@ -21,6 +21,7 @@ use esp_hal::{ }; use esp_println::println; use owned_str::OwnedStr; +use serde::{Deserialize, Serialize}; use ufmt::uwrite; mod buffer; @@ -31,8 +32,6 @@ mod screen; pub use input::ANGLE; -use crate::screen::overwrite_lcd; - const WIFI_NETWORK: &str = "flamme"; const WIFI_PASSWORD: &str = "12345678"; const TARGET_IP: &str = "84.238.32.253"; @@ -41,6 +40,7 @@ const WHEEL_PRECISION: i32 = 32; const WHEEL_INVERTED: bool = false; const DEVICE_ID: &str = "esp32-1"; +#[derive(Deserialize)] enum QuestionType { Choice, Numeric { min: i32, max: i32 }, @@ -58,7 +58,26 @@ struct QuestionData { text: OwnedStr<256>, q_type: QuestionType, points: i32, - generation: usize, + index: usize, +} + +#[derive(Deserialize)] +struct QuestionDataNet<'a> { + text: &'a str, + q_type: QuestionType, + points: i32, + index: usize, +} + +impl<'a> From> for QuestionData { + fn from(value: QuestionDataNet<'a>) -> Self { + QuestionData { + text: OwnedStr::from_str(value.text).unwrap(), + q_type: value.q_type, + points: value.points, + index: value.index, + } + } } #[derive(Clone, Copy)] @@ -99,7 +118,6 @@ pub async fn tcp_read_loop( cancel: &Signal, ) -> Result<(), TcpDisconnect> { let mut buf = [0u8; 1024]; - let mut generation = 1; loop { let read_fut = read.read(&mut buf); @@ -120,7 +138,6 @@ pub async fn tcp_read_loop( let Ok(str) = core::str::from_utf8(&buf[..len]) else { continue; }; - let mut counter = 0; let mut question_data = None; let mut future_wheel = WheelData { value: 0, @@ -128,70 +145,20 @@ pub async fn tcp_read_loop( max: 0, accumulated: 0, }; - for line in str.lines() { - if line == "##" { - overwrite_lcd("Waiting", DEVICE_ID).await; - break; - } - if line == "$$" { - counter = 1; + if let Some(last) = str.lines().last() { + let Ok(data) = serde_json::from_str::(last) else { continue; - } - if counter == 1 { - let mut q_type = QuestionType::Choice; - let mut points = -1; - - for pairs in line.split(' ') { - let (key, value) = pairs.split_once('=').unwrap(); - if key == "type" { - q_type = if value == "choice" { - QuestionType::Choice - } else { - QuestionType::Numeric { min: -1, max: -1 } - }; - } - if key == "points" { - points = value.parse().unwrap(); - } - if key == "rangeMin" || key == "rangeMax" { - match q_type { - QuestionType::Choice => {} - QuestionType::Numeric { - ref mut min, - ref mut max, - } => { - if key == "rangeMin" { - *min = value.parse().unwrap(); - future_wheel.min = *min; - } else { - *max = value.parse().unwrap(); - future_wheel.max = *max; - } - } - } - } + }; + let data: QuestionData = data.into(); + match data.q_type { + QuestionType::Numeric { min, max } => { + future_wheel.max = max; + future_wheel.min = min; + future_wheel.value = (min + max) / 2; } - - if let QuestionType::Numeric { min, max } = q_type { - let diff = max - min; - future_wheel.value = min + diff / 2; - future_wheel.accumulated = 0; - } - - question_data = Some(QuestionData { - text: OwnedStr::new(), - q_type, - points, - generation, - }); - counter = 2; - continue; - } - if counter == 2 { - question_data.as_mut().unwrap().text = OwnedStr::from_str(line).unwrap(); - generation += 1; - counter = 0; - } + _ => {} + }; + question_data = Some(data); } if let Some(question_data) = question_data { @@ -202,15 +169,28 @@ pub async fn tcp_read_loop( } } +#[derive(Serialize)] +enum WriteType<'a> { + QuizResponse(i32), + DeviceId(&'a str), +} + pub async fn tcp_write_loop( mut write: TcpWriter<'_>, cancel: &Signal, ) -> Result<(), TcpDisconnect> { - if write.write(DEVICE_ID.as_bytes()).await.is_err() { + if write + .write( + serde_json::to_string(&WriteType::DeviceId(DEVICE_ID)) + .unwrap() + .as_bytes(), + ) + .await + .is_err() + { cancel.signal(()); return Err(TcpDisconnect::WriteError); } - let mut buffer = buffer::WriteBuffer::<256>::new(); loop { let input_fut = input::INPUT.receive(); let cancel_fut = cancel.wait(); @@ -219,14 +199,24 @@ pub async fn tcp_write_loop( Either::Second(()) => return Err(TcpDisconnect::Cancelled), }; println!("button={}", data); - let angle = *ANGLE.lock().await; - core::writeln!(buffer, "button={} angle={}", data, angle).ok(); + let value = { + let question = QUESTION.lock().await; + let wheel = *WHEEL_VALUE.lock().await; + match question.as_ref() { + Some(q) => match q.q_type { + QuestionType::Numeric { .. } => wheel.value, + QuestionType::Choice => data as _, + }, + _ => data as _, + } + }; + let data = WriteType::QuizResponse(value); + let buffer = serde_json::to_string(&data).unwrap(); println!("write: {}", &buffer); - if write.write(&buffer).await.is_err() { + if write.write(buffer.as_bytes()).await.is_err() { cancel.signal(()); return Err(TcpDisconnect::WriteError); } - buffer.clear(); } } @@ -236,7 +226,7 @@ const DOT: char = char::from_u32(0b1010_0101).unwrap(); #[embassy_executor::task] pub async fn main_loop() { - let mut last_gen = 0; + let mut last_index = 0; let mut title_offset = 0; println!("Main loop started"); loop { @@ -247,8 +237,8 @@ pub async fn main_loop() { continue; }; title_offset += 1; - if question.generation != last_gen { - last_gen = question.generation; + if question.index != last_index { + last_index = question.index; title_offset = 0; } let title_line = if question.text.len() > 16 { diff --git a/esp32/src/screen.rs b/esp32/src/screen.rs index 5dade20..78e8714 100644 --- a/esp32/src/screen.rs +++ b/esp32/src/screen.rs @@ -21,8 +21,8 @@ pub async fn overwrite_lcd(line1: &str, line2: &str) { let mut buffer = SCREEN_BUFFER.lock().await; buffer.line1_ptr = 0; buffer.line2_ptr = 0; - buffer.line1.fill(0); - buffer.line2.fill(0); + buffer.line1.fill(32); + buffer.line2.fill(32); let len1 = line1.len().min(buffer.line1.len()); let len2 = line2.len().min(buffer.line2.len()); buffer.line1[..len1].copy_from_slice(line1[..len1].as_bytes());