chore: polish code
This commit is contained in:
@@ -40,7 +40,7 @@ use crate::net::command::{
|
|||||||
use crate::net::discovery::Discovery;
|
use crate::net::discovery::Discovery;
|
||||||
use crate::net::event::{ClientEvent, NotificationLevel, ServerEvent};
|
use crate::net::event::{ClientEvent, NotificationLevel, ServerEvent};
|
||||||
use crate::net::network::{AudioSocket, Connection};
|
use crate::net::network::{AudioSocket, Connection};
|
||||||
use crate::net::protocol::{ClientInfo, ControlPacket};
|
use crate::net::protocol::ControlPacket;
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use session::handshake;
|
use session::handshake;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@@ -49,24 +49,37 @@ use tokio::sync::{RwLock, broadcast};
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
/// 客户端配置
|
/// 客户端配置
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
/// 客户端型号
|
/// 版本
|
||||||
pub model: String,
|
pub version: String,
|
||||||
/// 序列号
|
/// 客户端认证
|
||||||
pub serial_number: String,
|
pub client_auth: String,
|
||||||
|
/// 服务端认证
|
||||||
|
pub server_auth: String,
|
||||||
/// 心跳间隔(秒)
|
/// 心跳间隔(秒)
|
||||||
pub heartbeat_interval: u64,
|
pub heartbeat_interval: u64,
|
||||||
/// 连接超时(秒)
|
/// 连接超时(秒)
|
||||||
pub timeout: u64,
|
pub timeout: u64,
|
||||||
|
/// 客户端型号
|
||||||
|
pub model: String,
|
||||||
|
/// 序列号
|
||||||
|
pub serial_number: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ClientConfig {
|
impl Default for ClientConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
model: "Open-XiaoAi-V2".to_string(),
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
serial_number: "00:00:00:00:00:00".to_string(),
|
server_auth: std::env::var("XIAO_SERVER_AUTH")
|
||||||
|
.unwrap_or_else(|_| "xiao-server".to_string()),
|
||||||
|
client_auth: std::env::var("XIAO_CLIENT_AUTH")
|
||||||
|
.unwrap_or_else(|_| "xiao-client".to_string()),
|
||||||
heartbeat_interval: 10,
|
heartbeat_interval: 10,
|
||||||
timeout: 60,
|
timeout: 60,
|
||||||
|
// todo 获取设备信息
|
||||||
|
model: "Open-XiaoAi-V2".to_string(),
|
||||||
|
serial_number: "00:00:00:00:00:00".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,8 +94,6 @@ pub struct Client {
|
|||||||
cancel: CancellationToken,
|
cancel: CancellationToken,
|
||||||
/// 服务端事件广播
|
/// 服务端事件广播
|
||||||
server_events: broadcast::Sender<ServerEvent>,
|
server_events: broadcast::Sender<ServerEvent>,
|
||||||
/// 客户端启动时间
|
|
||||||
started_at: std::time::Instant,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
@@ -94,7 +105,6 @@ impl Client {
|
|||||||
session: RwLock::new(None),
|
session: RwLock::new(None),
|
||||||
cancel: CancellationToken::new(),
|
cancel: CancellationToken::new(),
|
||||||
server_events,
|
server_events,
|
||||||
started_at: std::time::Instant::now(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,13 +163,8 @@ impl Client {
|
|||||||
let audio_socket = Arc::new(AudioSocket::bind().await?);
|
let audio_socket = Arc::new(AudioSocket::bind().await?);
|
||||||
|
|
||||||
// 执行握手
|
// 执行握手
|
||||||
let client_info = ClientInfo {
|
|
||||||
model: self.config.model.clone(),
|
|
||||||
serial_number: self.config.serial_number.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let handshake_result =
|
let handshake_result =
|
||||||
handshake(&conn, audio_socket.port(), server_addr, client_info).await?;
|
handshake(&conn, audio_socket.port(), server_addr, &self.config).await?;
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"[Client] Handshake OK, server audio at {}",
|
"[Client] Handshake OK, server audio at {}",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use std::net::SocketAddr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use super::pipeline::PipelineHandle;
|
use super::{ClientConfig, pipeline::PipelineHandle};
|
||||||
|
|
||||||
/// 活动管道追踪
|
/// 活动管道追踪
|
||||||
pub struct ActivePipelines {
|
pub struct ActivePipelines {
|
||||||
@@ -224,20 +224,17 @@ pub async fn handshake(
|
|||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
audio_port: u16,
|
audio_port: u16,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
client_info: ClientInfo,
|
config: &ClientConfig,
|
||||||
) -> Result<HandshakeResult> {
|
) -> Result<HandshakeResult> {
|
||||||
let version = env!("CARGO_PKG_VERSION").to_string();
|
|
||||||
let server_auth =
|
|
||||||
std::env::var("XIAO_SERVER_AUTH").unwrap_or_else(|_| "xiao-server".to_string());
|
|
||||||
let client_auth =
|
|
||||||
std::env::var("XIAO_CLIENT_AUTH").unwrap_or_else(|_| "xiao-client".to_string());
|
|
||||||
|
|
||||||
// 发送 ClientHello
|
// 发送 ClientHello
|
||||||
conn.send(&ControlPacket::ClientHello {
|
conn.send(&ControlPacket::ClientHello {
|
||||||
auth: server_auth,
|
|
||||||
version: version.clone(),
|
|
||||||
udp_port: audio_port,
|
udp_port: audio_port,
|
||||||
info: client_info,
|
auth: config.server_auth.clone(),
|
||||||
|
version: config.version.clone(),
|
||||||
|
info: ClientInfo {
|
||||||
|
model: config.model.clone(),
|
||||||
|
serial_number: config.serial_number.clone(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -248,10 +245,10 @@ pub async fn handshake(
|
|||||||
udp_port,
|
udp_port,
|
||||||
auth,
|
auth,
|
||||||
} => {
|
} => {
|
||||||
if v != version {
|
if v != config.version {
|
||||||
return Err(anyhow!("Server version mismatch: {} != {}", v, version));
|
return Err(anyhow!("Server version mismatch"));
|
||||||
}
|
}
|
||||||
if auth != client_auth {
|
if auth != config.client_auth {
|
||||||
return Err(anyhow!("Invalid server auth"));
|
return Err(anyhow!("Invalid server auth"));
|
||||||
}
|
}
|
||||||
udp_port
|
udp_port
|
||||||
|
|||||||
@@ -56,8 +56,36 @@ use std::net::SocketAddr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
/// 服务端配置
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
/// 版本
|
||||||
|
pub version: String,
|
||||||
|
/// 客户端认证
|
||||||
|
pub client_auth: String,
|
||||||
|
/// 服务端认证
|
||||||
|
pub server_auth: String,
|
||||||
|
/// 连接超时(秒)
|
||||||
|
pub timeout: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServerConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
server_auth: std::env::var("XIAO_SERVER_AUTH")
|
||||||
|
.unwrap_or_else(|_| "xiao-server".to_string()),
|
||||||
|
client_auth: std::env::var("XIAO_CLIENT_AUTH")
|
||||||
|
.unwrap_or_else(|_| "xiao-client".to_string()),
|
||||||
|
timeout: 60,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 实时音频服务器
|
/// 实时音频服务器
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
/// 配置
|
||||||
|
config: ServerConfig,
|
||||||
/// 会话管理器
|
/// 会话管理器
|
||||||
sessions: Arc<SessionManager>,
|
sessions: Arc<SessionManager>,
|
||||||
/// 音频总线
|
/// 音频总线
|
||||||
@@ -72,10 +100,11 @@ pub struct Server {
|
|||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
/// 创建新服务器
|
/// 创建新服务器
|
||||||
pub async fn new() -> Result<Self> {
|
pub async fn new(config: ServerConfig) -> Result<Self> {
|
||||||
let audio_bus = Arc::new(AudioBus::new().await?);
|
let audio_bus = Arc::new(AudioBus::new().await?);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
config,
|
||||||
sessions: Arc::new(SessionManager::new()),
|
sessions: Arc::new(SessionManager::new()),
|
||||||
audio_bus,
|
audio_bus,
|
||||||
event_bus: Arc::new(ServerEventBus::default()),
|
event_bus: Arc::new(ServerEventBus::default()),
|
||||||
@@ -216,12 +245,6 @@ impl Server {
|
|||||||
conn: &Arc<Connection>,
|
conn: &Arc<Connection>,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) -> Result<(crate::net::protocol::ClientInfo, SocketAddr)> {
|
) -> Result<(crate::net::protocol::ClientInfo, SocketAddr)> {
|
||||||
let version = env!("CARGO_PKG_VERSION").to_string();
|
|
||||||
let server_auth =
|
|
||||||
std::env::var("XIAO_SERVER_AUTH").unwrap_or_else(|_| "xiao-server".to_string());
|
|
||||||
let client_auth =
|
|
||||||
std::env::var("XIAO_CLIENT_AUTH").unwrap_or_else(|_| "xiao-client".to_string());
|
|
||||||
|
|
||||||
// 等待客户端 Hello
|
// 等待客户端 Hello
|
||||||
let (info, client_audio_port) = match conn.recv().await? {
|
let (info, client_audio_port) = match conn.recv().await? {
|
||||||
ControlPacket::ClientHello {
|
ControlPacket::ClientHello {
|
||||||
@@ -230,10 +253,10 @@ impl Server {
|
|||||||
udp_port,
|
udp_port,
|
||||||
info,
|
info,
|
||||||
} => {
|
} => {
|
||||||
if v != version {
|
if v != self.config.version {
|
||||||
return Err(anyhow!("Version mismatch: {} != {}", v, version));
|
return Err(anyhow!("Client version mismatch"));
|
||||||
}
|
}
|
||||||
if auth != server_auth {
|
if auth != self.config.server_auth {
|
||||||
return Err(anyhow!("Invalid client auth"));
|
return Err(anyhow!("Invalid client auth"));
|
||||||
}
|
}
|
||||||
(info, udp_port)
|
(info, udp_port)
|
||||||
@@ -243,8 +266,8 @@ impl Server {
|
|||||||
|
|
||||||
// 发送服务器 Hello
|
// 发送服务器 Hello
|
||||||
conn.send(&ControlPacket::ServerHello {
|
conn.send(&ControlPacket::ServerHello {
|
||||||
auth: client_auth,
|
auth: self.config.client_auth.clone(),
|
||||||
version,
|
version: self.config.version.clone(),
|
||||||
udp_port: self.audio_bus.port(),
|
udp_port: self.audio_bus.port(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@@ -255,7 +278,7 @@ impl Server {
|
|||||||
|
|
||||||
/// Session 消息循环
|
/// Session 消息循环
|
||||||
async fn session_loop(&self, session: Arc<Session>) -> Result<()> {
|
async fn session_loop(&self, session: Arc<Session>) -> Result<()> {
|
||||||
let timeout = std::time::Duration::from_secs(60);
|
let timeout = std::time::Duration::from_secs(self.config.timeout);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ impl AudioConfig {
|
|||||||
audio_scene: AudioScene::Voice,
|
audio_scene: AudioScene::Voice,
|
||||||
bitrate: 32_000,
|
bitrate: 32_000,
|
||||||
vbr: true,
|
vbr: true,
|
||||||
fec: true,
|
fec: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +42,9 @@ impl AudioConfig {
|
|||||||
channels: 2,
|
channels: 2,
|
||||||
frame_size: 960, // 20ms
|
frame_size: 960, // 20ms
|
||||||
audio_scene: AudioScene::Music,
|
audio_scene: AudioScene::Music,
|
||||||
bitrate: 128_000,
|
bitrate: 320_000,
|
||||||
vbr: true,
|
vbr: true,
|
||||||
fec: true,
|
fec: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,17 @@ pub struct AudioPlayer {
|
|||||||
|
|
||||||
impl AudioPlayer {
|
impl AudioPlayer {
|
||||||
pub fn new(config: &AudioConfig) -> Result<Self> {
|
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||||
let pcm = PCM::new(&config.playback_device, Direction::Playback, false)?;
|
let pcm = PCM::new(&config.playback_device, Direction::Playback, false)
|
||||||
|
.context("Failed to open playback PCM device")?;
|
||||||
{
|
{
|
||||||
let hwp = HwParams::any(&pcm)?;
|
let hwp = HwParams::any(&pcm).context("Failed to get HwParams")?;
|
||||||
hwp.set_access(Access::RWInterleaved)?;
|
hwp.set_access(Access::RWInterleaved)?;
|
||||||
hwp.set_format(Format::s16())?;
|
hwp.set_format(Format::s16())?;
|
||||||
hwp.set_rate_near(config.sample_rate, alsa::ValueOr::Nearest)?;
|
hwp.set_rate_near(config.sample_rate, alsa::ValueOr::Nearest)?;
|
||||||
hwp.set_channels_near(config.channels as u32)?;
|
hwp.set_channels(config.channels as u32)?;
|
||||||
|
|
||||||
// 100ms buffer to prevent underruns
|
|
||||||
let buffer_size = (config.sample_rate as f64 * 0.1) as u32;
|
|
||||||
hwp.set_buffer_size_near(buffer_size as alsa::pcm::Frames)?;
|
|
||||||
|
|
||||||
pcm.hw_params(&hwp)?;
|
pcm.hw_params(&hwp)?;
|
||||||
}
|
}
|
||||||
pcm.prepare()?;
|
pcm.prepare().context("Failed to prepare PCM")?;
|
||||||
Ok(Self { pcm })
|
Ok(Self { pcm })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,18 +24,15 @@ fn main() {
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
println!("╔═══════════════════════════════════════════════════════╗");
|
println!("╔═══════════════════════════════════════════════════════╗");
|
||||||
println!("║ XiaoAi Audio Client v{} ║", env!("CARGO_PKG_VERSION"));
|
println!(
|
||||||
|
"║ XiaoAi Audio Client v{} ║",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
);
|
||||||
println!("╚═══════════════════════════════════════════════════════╝");
|
println!("╚═══════════════════════════════════════════════════════╝");
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
// 创建客户端(可以自定义配置)
|
// 创建客户端
|
||||||
let config = ClientConfig {
|
let config = ClientConfig::default();
|
||||||
model: "Open-XiaoAi-Demo".to_string(),
|
|
||||||
serial_number: get_mac_address(),
|
|
||||||
heartbeat_interval: 10,
|
|
||||||
timeout: 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = Arc::new(Client::new(config));
|
let client = Arc::new(Client::new(config));
|
||||||
|
|
||||||
// 启动事件监听器
|
// 启动事件监听器
|
||||||
@@ -104,17 +101,3 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
client.shutdown();
|
client.shutdown();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn get_mac_address() -> String {
|
|
||||||
// 尝试获取 MAC 地址
|
|
||||||
if let Ok(output) = std::process::Command::new("cat")
|
|
||||||
.arg("/sys/class/net/eth0/address")
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
return String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"00:00:00:00:00:00".to_string()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! - 事件广播
|
//! - 事件广播
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use xiao::app::server::Server;
|
use xiao::app::server::{Server, ServerConfig};
|
||||||
use xiao::audio::config::AudioConfig;
|
use xiao::audio::config::AudioConfig;
|
||||||
use xiao::net::command::Command;
|
use xiao::net::command::Command;
|
||||||
use xiao::net::event::{NotificationLevel, ServerEvent};
|
use xiao::net::event::{NotificationLevel, ServerEvent};
|
||||||
@@ -15,11 +15,16 @@ use xiao::net::event::{NotificationLevel, ServerEvent};
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
println!("╔═══════════════════════════════════════════════════════╗");
|
println!("╔═══════════════════════════════════════════════════════╗");
|
||||||
println!("║ XiaoAi Audio Server v{} ║", env!("CARGO_PKG_VERSION"));
|
println!(
|
||||||
|
"║ XiaoAi Audio Server v{} ║",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
);
|
||||||
println!("╚═══════════════════════════════════════════════════════╝");
|
println!("╚═══════════════════════════════════════════════════════╝");
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let server = Arc::new(Server::new().await?);
|
// 创建服务端
|
||||||
|
let config = ServerConfig::default();
|
||||||
|
let server = Arc::new(Server::new(config).await?);
|
||||||
let s = server.clone();
|
let s = server.clone();
|
||||||
|
|
||||||
// 启动服务器
|
// 启动服务器
|
||||||
@@ -98,18 +103,6 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.await;
|
.await;
|
||||||
println!(" ✅ Event broadcasted");
|
println!(" ✅ Event broadcasted");
|
||||||
|
|
||||||
// 5. 测试音频录制
|
|
||||||
println!("\n5️⃣ Testing Audio Recording (5 seconds)...");
|
|
||||||
match server.start_record(addr, AudioConfig::voice_16k()).await {
|
|
||||||
Ok(_) => {
|
|
||||||
println!(" ⏺️ Recording started...");
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
|
||||||
server.stop_record(addr).await?;
|
|
||||||
println!(" ⏹️ Recording stopped");
|
|
||||||
}
|
|
||||||
Err(e) => println!(" ❌ Recording failed: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 测试音频播放(如果有测试文件)
|
// 6. 测试音频播放(如果有测试文件)
|
||||||
println!("\n6️⃣ Testing Audio Playback...");
|
println!("\n6️⃣ Testing Audio Playback...");
|
||||||
if std::path::Path::new("temp/test.wav").exists() {
|
if std::path::Path::new("temp/test.wav").exists() {
|
||||||
@@ -126,6 +119,18 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
println!(" ⚠️ No test file found at temp/test.wav, skipping...");
|
println!(" ⚠️ No test file found at temp/test.wav, skipping...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. 测试音频录制
|
||||||
|
println!("\n5️⃣ Testing Audio Recording (5 seconds)...");
|
||||||
|
match server.start_record(addr, AudioConfig::voice_16k()).await {
|
||||||
|
Ok(_) => {
|
||||||
|
println!(" ⏺️ Recording started...");
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||||
|
server.stop_record(addr).await?;
|
||||||
|
println!(" ⏹️ Recording stopped");
|
||||||
|
}
|
||||||
|
Err(e) => println!(" ❌ Recording failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
println!("\n═══════════════════════════════════════════════════════");
|
println!("\n═══════════════════════════════════════════════════════");
|
||||||
println!("✅ All tests completed!");
|
println!("✅ All tests completed!");
|
||||||
println!("\nServer status:");
|
println!("\nServer status:");
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ pub struct ClientInfo {
|
|||||||
pub serial_number: String,
|
pub serial_number: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 音频数据包 - UDP 通道传输
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct AudioPacket {
|
||||||
|
/// Opus 编码的音频数据
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 控制包 ====================
|
// ==================== 控制包 ====================
|
||||||
|
|
||||||
/// 控制包 - TCP 通道传输的所有消息类型
|
/// 控制包 - TCP 通道传输的所有消息类型
|
||||||
@@ -23,7 +30,10 @@ pub struct ClientInfo {
|
|||||||
pub enum ControlPacket {
|
pub enum ControlPacket {
|
||||||
// ========== 服务发现 ==========
|
// ========== 服务发现 ==========
|
||||||
/// 服务发现广播
|
/// 服务发现广播
|
||||||
Discovery { protocol: String, port: u16 },
|
Discovery {
|
||||||
|
protocol: String,
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
|
|
||||||
// ========== 握手 ==========
|
// ========== 握手 ==========
|
||||||
/// 服务端握手
|
/// 服务端握手
|
||||||
@@ -46,9 +56,15 @@ pub enum ControlPacket {
|
|||||||
|
|
||||||
// ========== RPC ==========
|
// ========== RPC ==========
|
||||||
/// RPC 请求(新版,使用 Command 类型)
|
/// RPC 请求(新版,使用 Command 类型)
|
||||||
RpcRequest { id: u32, command: Command },
|
RpcRequest {
|
||||||
|
id: u32,
|
||||||
|
command: Command,
|
||||||
|
},
|
||||||
/// RPC 响应(新版,使用 CommandResult 类型)
|
/// RPC 响应(新版,使用 CommandResult 类型)
|
||||||
RpcResponse { id: u32, result: CommandResult },
|
RpcResponse {
|
||||||
|
id: u32,
|
||||||
|
result: CommandResult,
|
||||||
|
},
|
||||||
|
|
||||||
// ========== 事件 ==========
|
// ========== 事件 ==========
|
||||||
/// 服务端事件推送
|
/// 服务端事件推送
|
||||||
@@ -56,48 +72,27 @@ pub enum ControlPacket {
|
|||||||
/// 客户端事件推送
|
/// 客户端事件推送
|
||||||
ClientEvent(ClientEvent),
|
ClientEvent(ClientEvent),
|
||||||
|
|
||||||
|
// ========== 订阅管理 ==========
|
||||||
|
/// 订阅事件类型
|
||||||
|
Subscribe {
|
||||||
|
event_types: Vec<String>,
|
||||||
|
},
|
||||||
|
/// 取消订阅
|
||||||
|
Unsubscribe {
|
||||||
|
event_types: Vec<String>,
|
||||||
|
},
|
||||||
|
|
||||||
// ========== 音频控制 ==========
|
// ========== 音频控制 ==========
|
||||||
/// 开始录音
|
/// 开始录音
|
||||||
StartRecording { config: AudioConfig },
|
StartRecording {
|
||||||
|
config: AudioConfig,
|
||||||
|
},
|
||||||
/// 停止录音
|
/// 停止录音
|
||||||
StopRecording,
|
StopRecording,
|
||||||
/// 开始播放
|
/// 开始播放
|
||||||
StartPlayback { config: AudioConfig },
|
StartPlayback {
|
||||||
|
config: AudioConfig,
|
||||||
|
},
|
||||||
/// 停止播放
|
/// 停止播放
|
||||||
StopPlayback,
|
StopPlayback,
|
||||||
|
|
||||||
// ========== 订阅管理 ==========
|
|
||||||
/// 订阅事件类型
|
|
||||||
Subscribe { event_types: Vec<String> },
|
|
||||||
/// 取消订阅
|
|
||||||
Unsubscribe { event_types: Vec<String> },
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 音频包 ====================
|
|
||||||
|
|
||||||
/// 音频数据包 - UDP 通道传输
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct AudioPacket {
|
|
||||||
/// Opus 编码的音频数据
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 兼容性:旧版 RPC 结果 ====================
|
|
||||||
|
|
||||||
/// 旧版 RPC 结果(保持向后兼容)
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
|
||||||
pub struct RpcResult {
|
|
||||||
pub stdout: String,
|
|
||||||
pub stderr: String,
|
|
||||||
pub code: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<crate::net::command::ShellResponse> for RpcResult {
|
|
||||||
fn from(resp: crate::net::command::ShellResponse) -> Self {
|
|
||||||
Self {
|
|
||||||
stdout: resp.stdout,
|
|
||||||
stderr: resp.stderr,
|
|
||||||
code: resp.exit_code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,29 +139,3 @@ where
|
|||||||
Err(_) => Err(RpcError::Timeout),
|
Err(_) => Err(RpcError::Timeout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::net::command::{CommandResult, ShellResponse};
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_rpc_manager() {
|
|
||||||
let mgr = RpcManager::new();
|
|
||||||
|
|
||||||
let (id, rx) = mgr.register();
|
|
||||||
assert_eq!(mgr.pending_count(), 1);
|
|
||||||
|
|
||||||
let result = CommandResult::Shell(ShellResponse {
|
|
||||||
stdout: "hello".to_string(),
|
|
||||||
stderr: String::new(),
|
|
||||||
exit_code: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
mgr.resolve(id, result.clone());
|
|
||||||
assert_eq!(mgr.pending_count(), 0);
|
|
||||||
|
|
||||||
let received = rx.await.unwrap();
|
|
||||||
assert!(matches!(received, CommandResult::Shell(_)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user