Add pressure visual modes and breakout game

This commit is contained in:
lennlouisgeek
2026-06-29 03:13:57 +08:00
parent eab24fc2bf
commit f30ebcf20b
12 changed files with 1739 additions and 165 deletions

View File

@@ -13,6 +13,15 @@ pub struct TactileACodec {
expected_data_len: usize,
}
impl From<u8> for TactileAFrameStatusCode {
fn from(value: u8) -> Self {
match value {
0 => TactileAFrameStatusCode::Success,
_ => TactileAFrameStatusCode::Failure,
}
}
}
impl TactileACodec {
pub fn new(cols: usize, rows: usize) -> TactileACodec {
Self {
@@ -37,7 +46,7 @@ impl TactileACodec {
Ok(vals)
}
pub fn build_req_frame(cols: usize, rows: usize) -> TactileAFrame {
pub fn build_req_frame(cols: usize, rows: usize) -> anyhow::Result<TactileAFrame> {
let header = [0x55, 0xAA];
let payload_len: usize = 9;
let device_addr: u8 = 0x34;
@@ -46,7 +55,7 @@ impl TactileACodec {
let start_addr: u32 = 7168;
let except_data_len: usize = cols * rows * 2;
let checksum: u8 = 0;
TactileAFrame::Req(TactileAReqFrame {
Ok(TactileAFrame::Req(TactileAReqFrame {
meta: TactileAFrameMetaData {
header,
payload_len,
@@ -57,7 +66,7 @@ impl TactileACodec {
except_data_len,
checksum,
},
})
}))
}
}
@@ -102,10 +111,7 @@ impl Codec<TactileAFrame> for TactileACodec {
self.buffer[10],
]);
let except_data_len = u16::from_le_bytes([self.buffer[11], self.buffer[12]]) as usize;
let status = match self.buffer[13] {
0 => TactileAFrameStatusCode::Success,
_ => TactileAFrameStatusCode::Failure,
};
let status = TactileAFrameStatusCode::from(self.buffer[13]);
if except_data_len != self.expected_data_len {
log::debug!(

View File

@@ -9,6 +9,8 @@ pub enum SerialError {
AlreadyConnected,
StateError,
NoRecordedData,
ExportError,
ImportError,
}
impl fmt::Display for SerialError {
@@ -21,6 +23,8 @@ impl fmt::Display for SerialError {
SerialError::AlreadyConnected => write!(f, "Already Connected"),
SerialError::StateError => write!(f, "State Error"),
SerialError::NoRecordedData => write!(f, "No Recorded Data"),
SerialError::ExportError => write!(f, "Export Error"),
SerialError::ImportError => write!(f, "Import Error"),
}
}
}

View File

@@ -1,3 +1,13 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestFrame {
pub header: [u8; 2],
pub cmd: u8,
pub length: usize,
pub payload: Vec<u8>,
pub checksum: u8,
pub dts_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TactileAFrameMetaData {
pub header: [u8; 2],

View File

@@ -1,13 +1,18 @@
use crate::serial_core::codec::Codec;
use crate::serial_core::codecs::tactile_a::TactileACodec;
use crate::serial_core::frame::TactileAFrame;
use crate::serial_core::utils::elapsed_millis;
use crossbeam_channel::{Receiver, Sender, TryRecvError};
use crossbeam_channel::{Receiver, Sender};
use std::io::{Read, Write};
use std::time::{Duration, Instant};
const POLL_INTERVAL_MS: u64 = 10;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct SerialIoStats {
pub rx_bytes: u64,
pub tx_bytes: u64,
}
/// Runs the serial polling loop on the calling (background) thread.
/// Sends decoded pressure matrix data (Vec<i32>) to the output channel.
pub fn run_serial_loop(
@@ -16,12 +21,20 @@ pub fn run_serial_loop(
cols: usize,
cancel_rx: &Receiver<()>,
sample_tx: &Sender<Vec<i32>>,
stats_tx: Option<&Sender<SerialIoStats>>,
) {
let session_started_at = Instant::now();
let mut codec = TactileACodec::new(cols, rows);
let req_frame = TactileACodec::build_req_frame(cols, rows);
let req_frame = match TactileACodec::build_req_frame(cols, rows) {
Ok(frame) => frame,
Err(err) => {
eprintln!("[serial] request frame build error: {err}");
return;
}
};
let mut buffer = [0u8; 1024];
let mut poll_interval = Duration::from_millis(POLL_INTERVAL_MS);
let poll_interval = Duration::from_millis(POLL_INTERVAL_MS);
let mut io_stats = SerialIoStats::default();
loop {
// Check cancel
@@ -31,7 +44,10 @@ pub fn run_serial_loop(
// Send poll request
if let Ok(req_bytes) = codec.encode(&req_frame) {
let _ = port.write_all(&req_bytes);
if port.write_all(&req_bytes).is_ok() {
io_stats.tx_bytes += req_bytes.len() as u64;
publish_stats(stats_tx, io_stats);
}
}
// Read response with poll interval
@@ -43,6 +59,8 @@ pub fn run_serial_loop(
match port.read(&mut buffer) {
Ok(n) if n > 0 => {
io_stats.rx_bytes += n as u64;
publish_stats(stats_tx, io_stats);
if let Ok(frames) = codec.decode(&buffer[..n], session_started_at) {
for frame in frames {
if let TactileAFrame::Rep(rep) = frame {
@@ -68,6 +86,12 @@ pub fn run_serial_loop(
}
}
fn publish_stats(stats_tx: Option<&Sender<SerialIoStats>>, stats: SerialIoStats) {
if let Some(tx) = stats_tx {
let _ = tx.try_send(stats);
}
}
/// Trait abstracting read+write for the serial port
pub trait ReadWrite: Send {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;

View File

@@ -1,5 +1,26 @@
use std::time::Instant;
pub fn usize_to_u16_be_bytes(n: usize) -> [u8; 2] {
(n as u16).to_be_bytes()
}
pub fn usize_to_u16_le_bytes(n: usize) -> [u8; 2] {
(n as u16).to_be_bytes()
}
pub fn u16_to_hex_be_bytes(n: u16) -> [u8; 2] {
n.to_be_bytes()
}
pub fn u16_to_hex_le_bytes(n: u16) -> [u8; 2] {
n.to_le_bytes()
}
pub fn calc_crc8_smbus(c: &[u8]) -> u8 {
let crc8_smbus = crc::Crc::<u8>::new(&crc::CRC_8_SMBUS);
crc8_smbus.checksum(c)
}
pub fn calc_crc8_itu(c: &[u8]) -> u8 {
let crc8_itu_alg = crc::Crc::<u8>::new(&crc::CRC_8_I_432_1);
crc8_itu_alg.checksum(c)
@@ -8,3 +29,26 @@ pub fn calc_crc8_itu(c: &[u8]) -> u8 {
pub fn elapsed_millis(start_at: Instant) -> u64 {
start_at.elapsed().as_millis() as u64
}
#[cfg(test)]
mod test {
use crate::serial_core::utils::{calc_crc8_itu, calc_crc8_smbus};
#[test]
fn test_crc8_itu() {
let req_vec = vec![
0x55, 0xAA, 0x09, 0x00, 0x34, 0x00, 0xFB, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00,
];
let checksum = calc_crc8_itu(req_vec.as_slice());
assert_eq!(checksum, 0x7A);
}
#[test]
fn test_crc8_smbus() {
let req_vec = vec![
0x55, 0xAA, 0x09, 0x00, 0x34, 0x00, 0xFB, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00,
];
let checksum = calc_crc8_smbus(req_vec.as_slice());
assert_eq!(checksum, 0x2F);
}
}