feat: 开放小爱音箱接入小智 AI 演示源代码
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
use open_xiaoai::services::connect::message::MessageManager;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyBytes;
|
||||
use server::AppServer;
|
||||
|
||||
pub mod macros;
|
||||
pub mod python;
|
||||
pub mod server;
|
||||
|
||||
#[pyfunction]
|
||||
fn on_output_data(py: Python, data: Py<PyBytes>) -> PyResult<Bound<PyAny>> {
|
||||
let bytes = data.as_bytes(py).to_vec();
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
||||
let _ = MessageManager::instance()
|
||||
.send_stream("play", bytes, None)
|
||||
.await;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn start_server(py: Python) -> PyResult<Bound<PyAny>> {
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, async {
|
||||
AppServer::run().await;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn open_xiaoai_server(_py: Python, m: Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(start_server, &m)?)?;
|
||||
m.add_function(wrap_pyfunction!(on_output_data, &m)?)?;
|
||||
crate::python::init_module(&m)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/// 提供一个简便的日志记录宏,将消息传递给 Python 端
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```
|
||||
/// pylog!("这是一条日志消息");
|
||||
/// pylog!("带有变量的日志: {}", variable);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pylog {
|
||||
($($arg:tt)*) => {
|
||||
crate::python::PythonManager::instance().log(format!($($arg)*));
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use pyo3::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::sync::{Arc, LazyLock, RwLock};
|
||||
|
||||
pub struct PythonManager {
|
||||
functions: Arc<RwLock<HashMap<String, PyObject>>>,
|
||||
}
|
||||
|
||||
static INSTANCE: LazyLock<PythonManager> = LazyLock::new(PythonManager::new);
|
||||
|
||||
impl PythonManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
functions: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static Self {
|
||||
&INSTANCE
|
||||
}
|
||||
|
||||
fn register_fn(&self, key: &str, function: PyObject) -> PyResult<()> {
|
||||
let mut functions = self.functions.write().unwrap();
|
||||
functions.insert(key.to_string(), function);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister_fn(&self, key: &str) -> PyResult<()> {
|
||||
let mut functions = self.functions.write().unwrap();
|
||||
functions.remove(key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn call_fn(&self, key: &str, arg: Option<PyObject>) -> PyResult<PyObject> {
|
||||
let functions = self.functions.read().unwrap();
|
||||
if let Some(function) = functions.get(key) {
|
||||
Python::with_gil(|py| match arg {
|
||||
None => function.call0(py),
|
||||
Some(arg) => function.call1(py, (arg,)),
|
||||
})
|
||||
} else {
|
||||
Err(pyo3::exceptions::PyKeyError::new_err(format!(
|
||||
"未找到函数: {}",
|
||||
key
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log(&self, text: String) {
|
||||
let _ = self.eval(&format!("print('{}')", text));
|
||||
}
|
||||
|
||||
pub fn eval(&self, script: &str) -> PyResult<()> {
|
||||
Python::with_gil(|py| {
|
||||
let c_script = CString::new(script)?;
|
||||
py.run(&c_script, None, None)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn register_fn(key: &str, function: PyObject) -> PyResult<()> {
|
||||
PythonManager::instance().register_fn(key, function)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn unregister_fn(key: &str) -> PyResult<()> {
|
||||
PythonManager::instance().unregister_fn(key)
|
||||
}
|
||||
|
||||
pub fn init_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(register_fn, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(unregister_fn, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
use open_xiaoai::base::{AppError, VERSION};
|
||||
use open_xiaoai::services::connect::data::{Event, Request, Response, Stream};
|
||||
use open_xiaoai::services::connect::handler::MessageHandler;
|
||||
use open_xiaoai::services::connect::message::{MessageManager, WsStream};
|
||||
use open_xiaoai::services::connect::rpc::RPC;
|
||||
use open_xiaoai::services::speaker::SpeakerManager;
|
||||
use open_xiaoai::utils::task::TaskManager;
|
||||
use pyo3::types::PyBytes;
|
||||
use pyo3::Python;
|
||||
use serde_json::json;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_tungstenite::accept_async;
|
||||
|
||||
use crate::python::PythonManager;
|
||||
|
||||
pub struct AppServer;
|
||||
|
||||
async fn test() -> Result<(), AppError> {
|
||||
SpeakerManager::play_text("已连接").await?;
|
||||
|
||||
let _ = RPC::instance()
|
||||
.call_remote("start_recording", None, None)
|
||||
.await;
|
||||
|
||||
let _ = RPC::instance().call_remote("start_play", None, None).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl AppServer {
|
||||
pub async fn connect(stream: TcpStream) -> Result<WsStream, AppError> {
|
||||
let ws_stream = accept_async(stream).await?;
|
||||
Ok(WsStream::Server(ws_stream))
|
||||
}
|
||||
|
||||
pub async fn run() {
|
||||
let addr = "0.0.0.0:4399";
|
||||
let listener = TcpListener::bind(&addr)
|
||||
.await
|
||||
.expect(format!("❌ 绑定地址失败: {}", &addr).as_str());
|
||||
crate::pylog!("✅ 已启动: {:?}", addr);
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
// 同一时刻只处理一个连接
|
||||
AppServer::handle_connection(stream, addr).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(stream: TcpStream, addr: std::net::SocketAddr) {
|
||||
let Ok(ws_stream) = AppServer::connect(stream).await else {
|
||||
crate::pylog!("❌ 连接异常: {}", addr);
|
||||
return;
|
||||
};
|
||||
crate::pylog!("✅ 已连接: {:?}", addr);
|
||||
AppServer::init(ws_stream).await;
|
||||
if let Err(e) = MessageManager::instance().process_messages().await {
|
||||
crate::pylog!("❌ 消息处理异常: {}", e);
|
||||
}
|
||||
AppServer::dispose().await;
|
||||
crate::pylog!("❌ 已断开连接");
|
||||
}
|
||||
|
||||
async fn init(ws_stream: WsStream) {
|
||||
MessageManager::instance().init(ws_stream).await;
|
||||
MessageHandler::<Event>::instance()
|
||||
.set_handler(on_event)
|
||||
.await;
|
||||
MessageHandler::<Stream>::instance()
|
||||
.set_handler(on_stream)
|
||||
.await;
|
||||
|
||||
let rpc = RPC::instance();
|
||||
rpc.add_command("get_version", get_version).await;
|
||||
|
||||
let test = tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
let _ = test().await;
|
||||
});
|
||||
TaskManager::instance().add("test", test).await;
|
||||
}
|
||||
|
||||
async fn dispose() {
|
||||
MessageManager::instance().dispose().await;
|
||||
TaskManager::instance().dispose("test").await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_version(_: Request) -> Result<Response, AppError> {
|
||||
let data = json!(VERSION.to_string());
|
||||
Ok(Response::from_data(data))
|
||||
}
|
||||
|
||||
async fn on_stream(stream: Stream) -> Result<(), AppError> {
|
||||
let Stream { tag, bytes, .. } = stream;
|
||||
match tag.as_str() {
|
||||
"record" => {
|
||||
let data = Python::with_gil(|py| PyBytes::new(py, &bytes).into());
|
||||
PythonManager::instance().call_fn("on_input_data", Some(data))?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn on_event(event: Event) -> Result<(), AppError> {
|
||||
crate::pylog!("🔥 收到 Event: {:?}", event);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user