refactor: 重构 Open-XiaoAI Client V2

This commit is contained in:
Del Wang
2026-01-04 11:24:37 +08:00
parent 052ce06355
commit cce3dd630a
17 changed files with 3408 additions and 774 deletions
+97 -10
View File
@@ -1,7 +1,19 @@
//! # Client Demo
//!
//! 演示客户端的主要功能:
//! - 自动服务发现
//! - 响应 RPC 调用
//! - 音频录制和播放
//! - 事件处理
#[cfg(target_os = "linux")]
use std::sync::Arc;
#[cfg(target_os = "linux")]
use xiao::app::client::Client;
use xiao::app::client::{Client, ClientConfig};
#[cfg(target_os = "linux")]
use xiao::net::command::Command;
#[cfg(target_os = "linux")]
use xiao::net::event::NotificationLevel;
#[cfg(not(target_os = "linux"))]
fn main() {
@@ -11,23 +23,98 @@ fn main() {
#[cfg(target_os = "linux")]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Arc::new(Client::new());
let c = client.clone();
println!("╔═══════════════════════════════════════════════════════╗");
println!("║ XiaoAi Audio Client v{}", env!("CARGO_PKG_VERSION"));
println!("╚═══════════════════════════════════════════════════════╝");
println!();
// 创建客户端(可以自定义配置)
let config = ClientConfig {
model: "Open-XiaoAi-Demo".to_string(),
serial_number: get_mac_address(),
heartbeat_interval: 10,
timeout: 60,
};
let client = Arc::new(Client::new(config));
// 启动事件监听器
let event_client = client.clone();
tokio::spawn(async move {
if let Err(e) = c.run().await {
let mut rx = event_client.subscribe_events();
while let Ok(event) = rx.recv().await {
println!("📨 [ServerEvent] {:?}", event);
}
});
// 启动客户端主循环
let run_client = client.clone();
tokio::spawn(async move {
if let Err(e) = run_client.run().await {
eprintln!("Client error: {}", e);
}
});
// Wait for connection
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
println!("Client is running, searching for server...\n");
println!("Testing RPC call to server...");
match client.call("hello", vec!["world".to_string()]).await {
Ok(res) => println!("Server RPC response: {}", res.stdout),
Err(e) => eprintln!("Server RPC call failed: {}", e),
// 等待连接
loop {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
if client.is_connected().await {
break;
}
}
println!("\n═══════════════════════════════════════════════════════");
println!("Connected to server!");
println!("Running client-side tests...\n");
// 1. 测试向服务器发送 Ping
println!("1️⃣ Testing Ping to server...");
match client.execute(Command::ping()).await {
Ok(result) => println!(" ✅ Pong received: {:?}", result),
Err(e) => println!(" ❌ Ping failed: {}", e),
}
// 2. 测试获取服务器信息
println!("\n2️⃣ Testing GetInfo from server...");
match client.execute(Command::GetInfo).await {
Ok(result) => println!(" ✅ Server info: {:?}", result),
Err(e) => println!(" ❌ GetInfo failed: {}", e),
}
// 3. 发送客户端事件
println!("\n3️⃣ Sending alert event to server...");
match client
.send_alert(NotificationLevel::Info, "Client started successfully!")
.await
{
Ok(_) => println!(" ✅ Alert sent"),
Err(e) => println!(" ❌ Failed to send alert: {}", e),
}
println!("\n═══════════════════════════════════════════════════════");
println!("✅ Client tests completed!");
println!("\nClient is now ready to receive commands from server.");
println!("Press Ctrl+C to exit.\n");
// 保持运行,等待服务器命令
tokio::signal::ctrl_c().await?;
println!("\nShutting down client...");
client.shutdown();
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()
}
+122 -25
View File
@@ -1,47 +1,144 @@
//! # Server Demo
//!
//! 演示服务端的主要功能:
//! - 多客户端管理
//! - RPC 调用
//! - 音频录制和播放
//! - 事件广播
use std::sync::Arc;
use xiao::app::server::Server;
use xiao::audio::config::AudioConfig;
use xiao::net::command::Command;
use xiao::net::event::{NotificationLevel, ServerEvent};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("╔═══════════════════════════════════════════════════════╗");
println!("║ XiaoAi Audio Server v{}", env!("CARGO_PKG_VERSION"));
println!("╚═══════════════════════════════════════════════════════╝");
println!();
let server = Arc::new(Server::new().await?);
let s = server.clone();
// 启动服务器
tokio::spawn(async move {
s.run(8080).await.unwrap();
if let Err(e) = s.run(8080).await {
eprintln!("Server error: {}", e);
}
});
println!("Server is running. Waiting for a client to connect...");
// 启动事件监听器
let event_server = server.clone();
tokio::spawn(async move {
let mut rx = event_server.event_bus().subscribe();
while let Some((addr, event)) = rx.recv().await {
match event {
ServerEvent::ClientJoined { model, .. } => {
println!("📱 [Event] Client joined: {} ({:?})", model, addr);
}
ServerEvent::ClientLeft { model, .. } => {
println!("📴 [Event] Client left: {} ({:?})", model, addr);
}
_ => {}
}
}
});
println!("Server is running on port 8080");
println!("Waiting for clients to connect...\n");
// 主循环:等待客户端并执行测试
loop {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let clients = server.get_clients().await;
if !clients.is_empty() {
let addr = clients[0];
println!("Client connected: {}. Starting tests...", addr);
println!("1. Testing Shell RPC...");
let res = server
.call(addr, "shell", vec!["echo 'Hello from Mars!'".to_string()])
.await?;
println!(
"RPC Result: stdout={}, code={}",
res.stdout.trim(),
res.code
);
println!("2. Testing Audio Recording (10s)...");
server.start_record(addr, AudioConfig::voice_16k()).await?;
tokio::time::sleep(std::time::Duration::from_secs(12)).await;
println!("3. Testing Audio Playback (from temp/test.wav)...");
server.start_play(addr, "temp/test.wav").await?;
break;
if clients.is_empty() {
continue;
}
let addr = clients[0];
println!("\n═══════════════════════════════════════════════════════");
println!("Client connected: {}", addr);
println!("Running demo tests...\n");
// 1. 测试 Ping
println!("1️⃣ Testing Ping...");
match server.execute(addr, Command::ping()).await {
Ok(result) => println!(" ✅ Ping result: {:?}", result),
Err(e) => println!(" ❌ Ping failed: {}", e),
}
// 2. 测试获取设备信息
println!("\n2️⃣ Testing GetInfo...");
match server.execute(addr, Command::GetInfo).await {
Ok(result) => println!(" ✅ Device info: {:?}", result),
Err(e) => println!(" ❌ GetInfo failed: {}", e),
}
// 3. 测试 Shell 命令
println!("\n3️⃣ Testing Shell RPC...");
match server.shell(addr, "echo 'Hello from XiaoAi!'").await {
Ok(resp) => {
println!(" ✅ stdout: {}", resp.stdout.trim());
println!(" ✅ exit_code: {}", resp.exit_code);
}
Err(e) => println!(" ❌ Shell failed: {}", e),
}
// 4. 测试事件广播
println!("\n4️⃣ Broadcasting notification event...");
server
.broadcast_event(ServerEvent::Notification {
level: NotificationLevel::Info,
title: "Test".to_string(),
message: "This is a test notification from server".to_string(),
})
.await;
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. 测试音频播放(如果有测试文件)
println!("\n6️⃣ Testing Audio Playback...");
if std::path::Path::new("temp/test.wav").exists() {
match server.start_play(addr, "temp/test.wav").await {
Ok(_) => {
println!(" ▶️ Playback started...");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
server.stop_play(addr).await?;
println!(" ⏹️ Playback stopped");
}
Err(e) => println!(" ❌ Playback failed: {}", e),
}
} else {
println!(" ⚠️ No test file found at temp/test.wav, skipping...");
}
println!("\n═══════════════════════════════════════════════════════");
println!("✅ All tests completed!");
println!("\nServer status:");
println!(" • Connected clients: {}", server.client_count());
println!(" • Uptime: {} seconds", server.uptime_secs());
println!("\nPress Ctrl+C to exit.");
break;
}
println!("Tests completed. Press Ctrl+C to exit.");
// 等待退出信号
tokio::signal::ctrl_c().await?;
println!("\nShutting down server...");
server.shutdown();
Ok(())
}