Update core modules and add GUI panels

This commit is contained in:
2026-06-08 17:32:42 +08:00
parent 2d33b4b2ae
commit 2eb02350da
23 changed files with 822 additions and 161 deletions

View File

@@ -1,11 +1,12 @@
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncRead, AsyncWrite};
use tracing::info;
use crate::error::Result;
use crate::transport::serial::SerialTransport;
use crate::transport::tcp::TcpTransport;
use crate::transport::udp::UdpTransport;
use crate::error::Result;
pub mod serial;
pub mod tcp;
@@ -15,7 +16,7 @@ pub mod udp;
pub enum TransportType {
Serial,
Tcp,
Udp
Udp,
}
impl std::fmt::Display for TransportType {
@@ -23,7 +24,7 @@ impl std::fmt::Display for TransportType {
match self {
TransportType::Serial => write!(f, "Serial"),
TransportType::Tcp => write!(f, "Tcp"),
TransportType::Udp => write!(f, "Udp")
TransportType::Udp => write!(f, "Udp"),
}
}
}
@@ -32,15 +33,15 @@ impl std::fmt::Display for TransportType {
pub enum TransportConfig {
Serial {
port: String,
baud_rate: u32
baud_rate: u32,
},
Tcp {
addr: String
addr: String,
},
Udp {
bind_addr: String,
remote_addr: Option<String>
}
remote_addr: Option<String>,
},
}
#[async_trait]
@@ -98,6 +99,7 @@ impl Connection {
}
pub async fn connect(&mut self) -> Result<()> {
info!(transport = ?self.transport_type(), name = self.name(), "Connection connecting");
match self {
Connection::Serial(t) => t.connect().await,
Connection::Tcp(t) => t.connect().await,
@@ -106,6 +108,7 @@ impl Connection {
}
pub async fn disconnect(&mut self) -> Result<()> {
info!(name = self.name(), "Connection disconnecting");
match self {
Connection::Serial(t) => t.disconnect().await,
Connection::Tcp(t) => t.disconnect().await,
@@ -237,7 +240,11 @@ mod tests {
#[test]
fn test_transport_type_roundtrip() {
for original in [TransportType::Serial, TransportType::Tcp, TransportType::Udp] {
for original in [
TransportType::Serial,
TransportType::Tcp,
TransportType::Udp,
] {
let json = serde_json::to_string(&original).unwrap();
let roundtripped: TransportType = serde_json::from_str(&json).unwrap();
assert_eq!(original, roundtripped);
@@ -503,8 +510,7 @@ mod tests {
unsafe fn wake(_: *const ()) {}
unsafe fn wake_by_ref(_: *const ()) {}
unsafe fn drop(_: *const ()) {}
static VTABLE: RawWakerVTable =
RawWakerVTable::new(clone, wake, wake_by_ref, drop);
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
unsafe { std::task::Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }
}
@@ -530,9 +536,9 @@ mod tests {
#[test]
fn test_connection_poll_read_not_connected() {
use tokio::io::ReadBuf;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::ReadBuf;
for mut conn in [serial_conn(), tcp_conn(), udp_conn()] {
let pinned = Pin::new(&mut conn);
@@ -719,9 +725,13 @@ mod tests {
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
AsyncWriteExt::write_all(&mut stream, b"hello").await.unwrap();
AsyncWriteExt::write_all(&mut stream, b"hello")
.await
.unwrap();
let mut buf = [0u8; 5];
AsyncReadExt::read_exact(&mut stream, &mut buf).await.unwrap();
AsyncReadExt::read_exact(&mut stream, &mut buf)
.await
.unwrap();
assert_eq!(&buf, b"world");
});
@@ -761,7 +771,9 @@ mod tests {
remote.send_to(b"world", from).await.unwrap();
let mut read_buf = [0u8; 5];
AsyncReadExt::read_exact(&mut conn, &mut read_buf).await.unwrap();
AsyncReadExt::read_exact(&mut conn, &mut read_buf)
.await
.unwrap();
assert_eq!(&read_buf, b"world");
}
}
}

View File

@@ -109,17 +109,22 @@ impl Transport for SerialTransport {
async fn connect(&mut self) -> Result<()> {
if self.is_connected() {
return Ok(())
return Ok(());
}
info!("Opening serial port {} at {} baud", self.port_name, self.baud_rate);
info!(
"Opening serial port {} at {} baud",
self.port_name, self.baud_rate
);
let port = tokio_serial::new(&self.port_name, self.baud_rate)
.open_native_async()
.map_err(|e| crate::error::Error::ConnectionFailed(format!(
"Failed to open {}: {}",
self.port_name, e
)))?;
.map_err(|e| {
crate::error::Error::ConnectionFailed(format!(
"Failed to open {}: {}",
self.port_name, e
))
})?;
debug!("Serial port {} opened successfully", self.port_name);
self.port = Some(port);
@@ -152,12 +157,8 @@ mod tests {
unsafe fn raw_wake_by_ref(_: *const ()) {}
unsafe fn raw_drop(_: *const ()) {}
static RAW_VTABLE: RawWakerVTable = RawWakerVTable::new(
raw_clone,
raw_wake,
raw_wake_by_ref,
raw_drop,
);
static RAW_VTABLE: RawWakerVTable =
RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop);
unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &RAW_VTABLE)) }
}

View File

@@ -1,4 +1,3 @@
use async_trait::async_trait;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::TcpStream;
@@ -10,15 +9,12 @@ use crate::error::Result;
#[derive(Debug)]
pub struct TcpTransport {
stream: Option<TcpStream>,
addr: String
addr: String,
}
impl TcpTransport {
pub fn new(addr: String) -> Self {
Self {
stream: None,
addr
}
Self { stream: None, addr }
}
pub fn addr(&self) -> &str {
@@ -281,7 +277,10 @@ mod tests {
async fn test_disconnect_without_connect() {
let mut transport = TcpTransport::new("127.0.0.1:8080".into());
let result = transport.disconnect().await;
assert!(result.is_ok(), "disconnect without connect should be a safe no-op");
assert!(
result.is_ok(),
"disconnect without connect should be a safe no-op"
);
assert!(!transport.is_connected());
}
@@ -322,4 +321,4 @@ mod tests {
// Ensure server task completed without panic
server_handle.await.unwrap();
}
}
}

View File

@@ -17,7 +17,7 @@ pub struct UdpTransport {
read_buf: Vec<u8>,
read_pos: usize,
temp_recv_buf: Vec<u8>,
temp_recv_buf: Vec<u8>,
}
impl UdpTransport {
@@ -49,7 +49,7 @@ impl AsyncRead for UdpTransport {
if this.read_pos < this.read_buf.len() {
let remaining = &this.read_buf[this.read_pos..];
let to_copy = std::cmp::min(remaining.len(), buf.remaining());
buf.put_slice(&remaining[..to_copy]);
this.read_pos += to_copy;
@@ -81,14 +81,16 @@ impl AsyncRead for UdpTransport {
socket.poll_recv(cx, &mut temp_buf)
} else {
// poll_recv_from 会返回 (usize, SocketAddr),我们将其映射回统一的 () 类型
socket.poll_recv_from(cx, &mut temp_buf).map(|res| res.map(|_| ()))
socket
.poll_recv_from(cx, &mut temp_buf)
.map(|res| res.map(|_| ()))
};
match poll_result {
Poll::Ready(Ok(())) => {
let filled = temp_buf.filled();
let to_copy = std::cmp::min(filled.len(), buf.remaining());
buf.put_slice(&filled[..to_copy]);
// 核心逻辑:如果本次读取到的 UDP 包大于外面提供的 buf 的剩余空间
@@ -268,9 +270,7 @@ mod tests {
let result = Pin::new(&mut t).poll_read(&mut cx, &mut buf);
match result {
Poll::Ready(Err(ref e)) => assert_eq!(e.kind(), std::io::ErrorKind::NotConnected),
other => panic!(
"expected Poll::Ready(Err(NotConnected)), got {other:?}"
),
other => panic!("expected Poll::Ready(Err(NotConnected)), got {other:?}"),
}
}
@@ -282,9 +282,7 @@ mod tests {
let result = Pin::new(&mut t).poll_write(&mut cx, data);
match result {
Poll::Ready(Err(ref e)) => assert_eq!(e.kind(), std::io::ErrorKind::NotConnected),
other => panic!(
"expected Poll::Ready(Err(NotConnected)), got {other:?}"
),
other => panic!("expected Poll::Ready(Err(NotConnected)), got {other:?}"),
}
}
@@ -322,7 +320,9 @@ mod tests {
let remote_addr = remote.local_addr().unwrap().to_string();
let mut t = UdpTransport::new("127.0.0.1:0".into(), Some(remote_addr));
t.connect().await.expect("connect with remote should succeed");
t.connect()
.await
.expect("connect with remote should succeed");
assert!(t.is_connected());
t.disconnect().await.expect("disconnect should succeed");