Files
ShowenV2/src/main.rs

179 lines
6.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use anyhow::Result;
use showen_v2::core::config::AppConfig;
use showen_v2::core::plugin_loader::PluginLoader;
use showen_v2::core::service_manager::ServiceManager;
#[cfg(not(test))]
use showen_v2::core::version_manager::VersionManager;
use showen_v2::plugins::{
ai::AiPlugin, ble::BlePlugin, device::DevicePlugin, http::HttpPlugin, live2d::Live2DPlugin,
screen::ScreenPlugin, video::VideoPlugin, wifi::WifiPlugin,
};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
fn main() -> Result<()> {
// 解析命令行参数
let args: Vec<String> = std::env::args().collect();
// 处理 --validate 参数
if args.contains(&"--validate".to_string()) {
let config_path = args
.iter()
.skip_while(|arg| *arg != "--config")
.nth(1)
.cloned()
.unwrap_or_else(|| "configs/dog_state_machine.json".to_string());
println!("验证配置文件: {}", config_path);
let config = AppConfig::from_file(&config_path)?;
config.validate_paths()?;
println!("✓ 配置文件有效");
return Ok(());
}
// 获取配置文件路径
let config_path = args
.iter()
.skip_while(|arg| *arg != "--config")
.nth(1)
.cloned()
.or_else(|| args.get(1).cloned())
.unwrap_or_else(|| "configs/dog_state_machine.json".to_string());
println!("ShowenV2 — 数字生命窗口平台");
println!("加载配置: {}", config_path);
let config = AppConfig::from_file(&config_path)?;
config.validate_paths()?;
// 在 config move 进 ServiceManager 之前,提取 AI 相关配置
let ai_model_store = config.ai.model_store.clone();
let ai_tools_dir = config.ai.tools_dir.clone();
let ai_whisper_lib_dir = config.ai.whisper_lib_dir.clone();
let ai_piper_lib_dir = config.ai.piper_lib_dir.clone();
let ai_piper_config = config.ai.piper_config.clone();
let ai_espeak_data_dir = config.ai.espeak_data_dir.clone();
let ai_tmp_dir = config.ai.tmp_dir.clone();
let ai_context_window = config.ai.context_window;
let mut manager = ServiceManager::new(config);
// 按依赖顺序注册插件
// 独立插件device, screen, wifi, video, ble, ai
// 依赖插件http (依赖 video)
println!("注册插件...");
manager.register(Box::new(DevicePlugin::new_default()));
println!(" ✓ DevicePlugin");
manager.register(Box::new(ScreenPlugin::new()));
println!(" ✓ ScreenPlugin");
manager.register(Box::new(WifiPlugin::new()));
println!(" ✓ WifiPlugin");
manager.register(Box::new(VideoPlugin::new()));
println!(" ✓ VideoPlugin");
manager.register(Box::new(Live2DPlugin::new()));
println!(" ✓ Live2DPlugin");
manager.register(Box::new(BlePlugin::new()));
println!(" ✓ BlePlugin");
// AiPlugin: 需要在注册前创建实例以获取 pipeline 句柄,再注册进 manager
// pipeline 会传给 HttpPlugin建立 HTTP → AI 的直接通道
let ai_plugin = AiPlugin::new_default(
ai_model_store,
ai_tools_dir,
ai_whisper_lib_dir,
ai_piper_lib_dir,
ai_piper_config,
ai_espeak_data_dir,
ai_tmp_dir,
ai_context_window,
);
let ai_pipeline = ai_plugin.pipeline();
let ai_models = ai_plugin.models();
manager.register(Box::new(ai_plugin));
println!(" ✓ AiPlugin");
let mut http_plugin = HttpPlugin::new();
// 注册 AI 句柄到 HttpState在 http init 之前建立连接)
http_plugin.set_ai_pipeline(ai_pipeline);
http_plugin.set_ai_models(ai_models);
manager.register(Box::new(http_plugin));
println!(" ✓ HttpPlugin");
// 加载动态插件
let plugin_store = std::path::Path::new("plugin_store");
if plugin_store.exists() {
println!("扫描动态插件...");
#[cfg(not(test))]
manager.set_version_manager(VersionManager::new(PluginLoader::new(plugin_store)));
let loader = PluginLoader::new(plugin_store);
match loader.load_registry() {
Ok(registry) => {
for (plugin_id, entry) in &registry.plugins {
if !entry.enabled {
println!(" - {plugin_id} (禁用)");
continue;
}
match loader.load_plugin(plugin_id, Some(&entry.active_version)) {
Ok((plugin, manifest)) => {
manager.register_dynamic_with_manifest(
Box::new(plugin),
manifest.error_policy,
entry.max_errors,
manifest.required_capabilities,
manifest.capabilities,
manifest.auto_test,
);
println!("{} v{} (动态)", plugin_id, entry.active_version);
}
Err(e) => {
eprintln!("{} v{} 加载失败: {e}", plugin_id, entry.active_version);
}
}
}
}
Err(e) => {
eprintln!("读取插件注册表失败: {e}");
}
}
}
// 设置 Ctrl+C 信号处理
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let shutdown_tx = manager.sender();
ctrlc::set_handler(move || {
println!("\n收到退出信号,正在关闭...");
r.store(false, Ordering::SeqCst);
// 注入 Shutdown 消息,唤醒主消息循环并触发优雅停机
let _ = shutdown_tx.send(showen_v2::core::message::Envelope {
from: "signal".to_string(),
to: showen_v2::core::message::Destination::Manager,
message: showen_v2::core::message::Message::Shutdown,
});
})?;
println!("启动所有插件...");
manager.start_all()?;
println!("ShowenV2 运行中... (按 Ctrl+C 退出)");
// 运行主循环
while running.load(Ordering::SeqCst) {
manager.run()?;
}
println!("正在停止所有插件...");
manager.stop_all()?;
println!("ShowenV2 已退出");
Ok(())
}