mod beacon;
mod channel;
mod sequence;
pub use beacon::{detect_beacon, Beacon, BEACON_PATTERN};
pub use channel::{ChannelHeader, ChannelId, MultiplexedFrame};
pub use sequence::{FrameSequence, MotionStream, StreamFrameHeader};
use crate::{checksum_bytes, Error, Result, MAGIC_DSK2};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[repr(u8)]
pub enum Orientation {
#[default]
Deg0 = 0,
Deg90 = 1,
Deg180 = 2,
Deg270 = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CornerMarkers {
pub tl: u8,
pub tr: u8,
pub bl: u8,
pub br: u8,
}
impl CornerMarkers {
pub fn standard(orientation: Orientation) -> Self {
Self {
tl: 0xA1,
tr: 0xA2,
bl: 0xA3,
br: 0xB0 | (orientation as u8),
}
}
pub fn orientation(self) -> Orientation {
match self.br & 0x0f {
1 => Orientation::Deg90,
2 => Orientation::Deg180,
3 => Orientation::Deg270,
_ => Orientation::Deg0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VisualFrame {
pub version: u8,
pub frame_id: u32,
pub sequence: u32,
pub orientation: Orientation,
pub channels: Vec<ChannelHeader>,
pub payload: Vec<u8>,
pub ecc_level: crate::ecc::EccLevel,
pub checksum: u32,
}
impl VisualFrame {
pub fn from_payload(
frame_id: u32,
sequence: u32,
payload: Vec<u8>,
ecc: crate::ecc::EccLevel,
) -> Self {
let protected = crate::ecc::encode(&payload, ecc).unwrap_or_else(|_| payload.clone());
let mut frame = Self {
version: 2,
frame_id,
sequence,
orientation: Orientation::Deg0,
channels: vec![ChannelHeader {
id: ChannelId(0),
sequence,
payload_type: 1, length: protected.len() as u32,
integrity: checksum_bytes(&protected),
}],
payload: protected,
ecc_level: ecc,
checksum: 0,
};
frame.checksum = frame.compute_checksum();
frame
}
fn compute_checksum(&self) -> u32 {
let mut buf = Vec::new();
buf.extend_from_slice(&self.version.to_be_bytes());
buf.extend_from_slice(&self.frame_id.to_be_bytes());
buf.extend_from_slice(&self.sequence.to_be_bytes());
buf.push(self.orientation as u8);
buf.extend_from_slice(&self.payload);
checksum_bytes(&buf)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let meta = serde_json::to_vec(self).map_err(|e| Error::Parse(e.to_string()))?;
let mut out = Vec::with_capacity(8 + meta.len());
out.extend_from_slice(MAGIC_DSK2);
out.extend_from_slice(&(meta.len() as u32).to_be_bytes());
out.extend_from_slice(&meta);
Ok(out)
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 8 || &data[0..4] != MAGIC_DSK2 {
return Err(Error::Frame("expected DSK2 frame magic".into()));
}
let len = u32::from_be_bytes(data[4..8].try_into().unwrap()) as usize;
if data.len() < 8 + len {
return Err(Error::TruncatedPayload {
needed: 8 + len - data.len(),
});
}
let frame: Self = serde_json::from_slice(&data[8..8 + len])?;
if frame.checksum != frame.compute_checksum() {
return Err(Error::ChecksumMismatch {
expected: frame.checksum,
actual: frame.compute_checksum(),
});
}
Ok(frame)
}
pub fn decode_payload(&self) -> Result<(Vec<u8>, crate::ecc::RecoveryReport)> {
crate::ecc::decode(&self.payload, self.ecc_level)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DetectedFrame {
pub bounds: (u32, u32, u32, u32),
pub orientation: Orientation,
pub confidence: f32,
pub frame_id: Option<u32>,
}