summaryrefslogtreecommitdiff
path: root/src/protocol/frame.rs
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2026-02-08 12:44:10 +0100
committerDawid Rycerz <dawid@rycerz.xyz>2026-02-08 12:44:10 +0100
commit0c20fb86633104744dbccf30ad732296694fff1b (patch)
tree02ffb8494086960b4a84decf3bdc2c8c61bfc4f6 /src/protocol/frame.rs
Initial pipewiremain
Diffstat (limited to 'src/protocol/frame.rs')
-rw-r--r--src/protocol/frame.rs259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/protocol/frame.rs b/src/protocol/frame.rs
new file mode 100644
index 0000000..32475e2
--- /dev/null
+++ b/src/protocol/frame.rs
@@ -0,0 +1,259 @@
+//! UPP protocol frame structures
+
+use serde::{Deserialize, Serialize};
+use std::mem;
+
+/// UPP USB frame header (5 bytes)
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+#[repr(C, packed)]
+pub struct UPPUsbFrame {
+ pub magic: u16, // 0xBBAA
+ pub cid: u8, // Camera ID
+ pub length: u16, // Data length (excluding header)
+}
+
+/// UPP camera frame header (7 bytes)
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
+#[repr(C, packed)]
+pub struct UPPFrameHeader {
+ pub frame_id: u8, // Frame ID
+ pub camera_number: u8, // Camera number
+ pub flags: UPPFlags, // Various flags
+ pub g_sensor: u32, // G-sensor data
+}
+
+/// UPP frame flags
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
+#[repr(C, packed)]
+pub struct UPPFlags {
+ pub has_g: bool, // Has G-sensor data (bit 0)
+ pub button_press: bool, // Button press detected (bit 1)
+ pub other: u8, // Other flags (bits 2-7, 6 bits total)
+}
+
+/// Complete UPP frame
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct UPPFrame {
+ pub header: UPPFrameHeader,
+ pub data: Vec<u8>,
+}
+
+impl UPPUsbFrame {
+ /// Create a new UPP USB frame
+ pub fn new(cid: u8, length: u16) -> Self {
+ Self {
+ magic: super::UPP_USB_MAGIC,
+ cid,
+ length,
+ }
+ }
+
+ /// Get the total frame size including header
+ /// Based on C++ POC: length field includes camera header + data, but not USB header
+ pub fn total_size(&self) -> usize {
+ mem::size_of::<Self>() + self.length as usize
+ }
+
+ /// Validate the frame magic number
+ pub fn is_valid(&self) -> bool {
+ self.magic == super::UPP_USB_MAGIC
+ }
+
+ /// Get the expected payload size (camera header + data)
+ /// Based on C++ POC: this is what the length field represents
+ pub fn expected_payload_size(&self) -> usize {
+ self.length as usize
+ }
+
+ /// Get the expected data size (excluding camera header)
+ /// Camera header is 7 bytes, so data size is payload - 7
+ pub fn expected_data_size(&self) -> usize {
+ if self.length >= 7 {
+ self.length as usize - 7
+ } else {
+ 0
+ }
+ }
+}
+
+impl UPPFrameHeader {
+ /// Create a new UPP frame header
+ pub fn new(
+ frame_id: u8,
+ camera_number: u8,
+ has_g: bool,
+ button_press: bool,
+ g_sensor: u32,
+ ) -> Self {
+ Self {
+ frame_id,
+ camera_number,
+ flags: UPPFlags {
+ has_g,
+ button_press,
+ other: 0,
+ },
+ g_sensor,
+ }
+ }
+
+ /// Check if this frame has G-sensor data
+ pub fn has_g_sensor(&self) -> bool {
+ self.flags.has_g
+ }
+
+ /// Check if button press was detected
+ pub fn button_pressed(&self) -> bool {
+ self.flags.button_press
+ }
+
+ /// Get other flags
+ pub fn other_flags(&self) -> u8 {
+ self.flags.other
+ }
+
+ /// Set other flags
+ pub fn set_other_flags(&mut self, flags: u8) {
+ self.flags.other = flags & 0x3F; // Only 6 bits
+ }
+
+ /// Get G-sensor data
+ pub fn g_sensor_data(&self) -> Option<u32> {
+ if self.has_g_sensor() {
+ Some(self.g_sensor)
+ } else {
+ None
+ }
+ }
+}
+
+impl UPPFrame {
+ /// Create a new UPP frame
+ pub fn new(header: UPPFrameHeader, data: Vec<u8>) -> Self {
+ Self { header, data }
+ }
+
+ /// Get the total frame size
+ pub fn total_size(&self) -> usize {
+ mem::size_of::<UPPFrameHeader>() + self.data.len()
+ }
+
+ /// Get the frame ID
+ pub fn frame_id(&self) -> u8 {
+ self.header.frame_id
+ }
+
+ /// Get the camera number
+ pub fn camera_number(&self) -> u8 {
+ self.header.camera_number
+ }
+
+ /// Check if button was pressed
+ pub fn button_pressed(&self) -> bool {
+ self.header.button_pressed()
+ }
+
+ /// Get G-sensor data if available
+ pub fn g_sensor_data(&self) -> Option<u32> {
+ if self.header.has_g_sensor() {
+ Some(self.header.g_sensor)
+ } else {
+ None
+ }
+ }
+}
+
+impl Default for UPPUsbFrame {
+ fn default() -> Self {
+ Self {
+ magic: super::UPP_USB_MAGIC,
+ cid: super::UPP_CAMID_7,
+ length: 0,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_upp_usb_frame_creation() {
+ let frame = UPPUsbFrame::new(7, 1024);
+ let magic = frame.magic;
+ let cid = frame.cid;
+ let length = frame.length;
+ assert_eq!(magic, super::super::UPP_USB_MAGIC);
+ assert_eq!(cid, 7);
+ assert_eq!(length, 1024);
+ assert!(frame.is_valid());
+ }
+
+ #[test]
+ fn test_upp_usb_frame_validation() {
+ let mut frame = UPPUsbFrame::new(7, 1024);
+ assert!(frame.is_valid());
+
+ frame.magic = 0x1234;
+ assert!(!frame.is_valid());
+ }
+
+ #[test]
+ fn test_upp_frame_header_creation() {
+ let header = UPPFrameHeader::new(1, 0, true, false, 12345);
+ assert_eq!(header.frame_id, 1);
+ assert_eq!(header.camera_number, 0);
+ assert!(header.has_g_sensor());
+ assert!(!header.button_pressed());
+ assert_eq!(header.g_sensor_data(), Some(12345));
+ }
+
+ #[test]
+ fn test_upp_frame_header_flags() {
+ let mut header = UPPFrameHeader::default();
+ assert!(!header.has_g_sensor());
+ assert!(!header.button_pressed());
+
+ header.flags.has_g = true;
+ header.flags.button_press = true;
+ assert!(header.has_g_sensor());
+ assert!(header.button_pressed());
+ }
+
+ #[test]
+ fn test_upp_frame_creation() {
+ let header = UPPFrameHeader::new(1, 0, false, false, 0);
+ let data = vec![1, 2, 3, 4, 5];
+ let frame = UPPFrame::new(header, data.clone());
+
+ assert_eq!(frame.frame_id(), 1);
+ assert_eq!(frame.camera_number(), 0);
+ assert_eq!(frame.data, data);
+ assert_eq!(frame.total_size(), mem::size_of::<UPPFrameHeader>() + 5);
+ }
+
+ #[test]
+ fn test_upp_frame_defaults() {
+ let frame = UPPFrame::default();
+ assert_eq!(frame.frame_id(), 0);
+ assert_eq!(frame.camera_number(), 0);
+ assert!(frame.data.is_empty());
+ assert!(!frame.button_pressed());
+ assert!(frame.g_sensor_data().is_none());
+ }
+
+ #[test]
+ fn test_upp_flags_other_bits() {
+ let mut header = UPPFrameHeader::default();
+ header.set_other_flags(0xFF);
+ assert_eq!(header.other_flags(), 0x3F); // Only 6 bits should be set
+ }
+
+ #[test]
+ fn test_memory_layout() {
+ // Ensure packed structs have correct sizes
+ assert_eq!(mem::size_of::<UPPUsbFrame>(), 5);
+ // UPPFrameHeader: frame_id(1) + camera_number(1) + flags(3) + g_sensor(4) = 9 bytes
+ assert_eq!(mem::size_of::<UPPFrameHeader>(), 9);
+ }
+}