feat(hass): 新增小爱音箱接入 Home Assistant Assist 的完整示例
- 新增完整的 Home Assistant 集成示例,支持文本对话与上下文会话 - 实现小爱音箱事件解析与 TTS 播报,支持实时打断功能 - 添加连续会话管理,支持区域上下文注入和会话超时结束 - 提供完整的配置系统、类型定义和错误处理 - 包含 Rust 扩展模块用于小爱音箱通信,支持音频流处理 - 添加单元测试、集成测试脚本和 Docker 部署支持 - 提供详细的使用文档和配置说明
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from hass.typing import XiaoAiConfig
|
||||
from hass.xiaoai_bridge import XiaoAiBridge
|
||||
|
||||
|
||||
class FakeServer:
|
||||
def __init__(self):
|
||||
self._fns = {}
|
||||
self.shell_calls = []
|
||||
|
||||
def register_fn(self, name, fn):
|
||||
self._fns[name] = fn
|
||||
|
||||
async def run_shell(self, script, timeout):
|
||||
self.shell_calls.append((script, timeout))
|
||||
return '{"stdout":"ok","stderr":"","exit_code":0}'
|
||||
|
||||
async def start_server(self):
|
||||
return None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bridge_interrupts_before_dispatch(monkeypatch):
|
||||
fake_server = FakeServer()
|
||||
|
||||
import hass.xiaoai_bridge as module
|
||||
|
||||
monkeypatch.setattr(module, "_LOGGER", module._LOGGER)
|
||||
|
||||
cfg = XiaoAiConfig(interrupt_on_new_input=True, shell_timeout_ms=123)
|
||||
bridge = XiaoAiBridge(cfg)
|
||||
bridge._open_xiaoai_server = fake_server
|
||||
bridge._loop = asyncio.get_running_loop()
|
||||
|
||||
called = []
|
||||
|
||||
async def on_text(input_data):
|
||||
called.append(input_data)
|
||||
|
||||
bridge.set_on_text(on_text)
|
||||
bridge._ready.set()
|
||||
bridge._register_callbacks()
|
||||
|
||||
event = {
|
||||
"event": "instruction",
|
||||
"data": {
|
||||
"ClientIP": "192.168.1.101",
|
||||
"NewLine": json.dumps(
|
||||
{
|
||||
"header": {"namespace": "SpeechRecognizer", "name": "RecognizeResult"},
|
||||
"payload": {
|
||||
"is_final": True,
|
||||
"results": [{"text": "打开客厅灯"}],
|
||||
},
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_server._fns["on_event"](json.dumps(event, ensure_ascii=False))
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
assert len(fake_server.shell_calls) == 1
|
||||
assert "ubus call mibrain ai_service" in fake_server.shell_calls[0][0]
|
||||
assert len(called) == 1
|
||||
assert called[0].text == "打开客厅灯"
|
||||
assert called[0].client_ip == "192.168.1.101"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bridge_extracts_missing_ip_as_none():
|
||||
fake_server = FakeServer()
|
||||
cfg = XiaoAiConfig(interrupt_on_new_input=False)
|
||||
bridge = XiaoAiBridge(cfg)
|
||||
bridge._open_xiaoai_server = fake_server
|
||||
bridge._loop = asyncio.get_running_loop()
|
||||
|
||||
captured = []
|
||||
|
||||
async def on_text(input_data):
|
||||
captured.append(input_data)
|
||||
|
||||
bridge.set_on_text(on_text)
|
||||
bridge._ready.set()
|
||||
bridge._register_callbacks()
|
||||
|
||||
event = {
|
||||
"event": "instruction",
|
||||
"data": {
|
||||
"NewLine": json.dumps(
|
||||
{
|
||||
"header": {"namespace": "SpeechRecognizer", "name": "RecognizeResult"},
|
||||
"payload": {
|
||||
"is_final": True,
|
||||
"results": [{"text": "查询天气"}],
|
||||
},
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_server._fns["on_event"](json.dumps(event, ensure_ascii=False))
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
assert len(captured) == 1
|
||||
assert captured[0].client_ip is None
|
||||
assert fake_server.shell_calls == []
|
||||
Reference in New Issue
Block a user