From a7876fb47279775ecbb0db3c673950c11318543c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Fri, 26 Sep 2025 13:11:14 +0800 Subject: [PATCH] add config --- SYSTEMD_SETUP.md | 115 +++++++++++++++++++++++++++++++++++++++ config.json | 4 +- control_system.py | 97 ++++++++++++++++++++++++++++++--- deploy-service.sh | 78 ++++++++++++++++++++++++++ local-voice-user.service | 16 ++++++ local-voice.service | 13 ++++- test-audio.py | 77 ++++++++++++++++++++++++++ 7 files changed, 389 insertions(+), 11 deletions(-) create mode 100644 SYSTEMD_SETUP.md create mode 100755 deploy-service.sh create mode 100644 local-voice-user.service create mode 100755 test-audio.py diff --git a/SYSTEMD_SETUP.md b/SYSTEMD_SETUP.md new file mode 100644 index 0000000..4f84ed0 --- /dev/null +++ b/SYSTEMD_SETUP.md @@ -0,0 +1,115 @@ +# Local Voice Systemd 服务配置指南 + +## 问题解决方案 + +### 核心问题 +- **直接运行**: 使用PulseAudio,自动转换16000Hz采样率 ✅ +- **systemd运行**: 直接访问USB设备,只支持48000/44100Hz ❌ + +### 解决策略 +通过配置systemd服务访问用户会话的PulseAudio,利用其采样率转换能力。 + +## 部署步骤 + +### 1. 上传文件到树莓派 +```bash +scp local-voice.service local-voice-user.service deploy-service.sh test-audio.py zhuchaowe@192.168.101.212:~/Local-Voice/ +``` + +### 2. 运行部署脚本 +```bash +cd ~/Local-Voice +./deploy-service.sh +``` + +### 3. 测试音频访问 +```bash +python3 test-audio.py +``` + +### 4. 启动服务 (推荐使用用户级服务) +```bash +# 用户级服务 (推荐) +systemctl --user start local-voice-user.service +systemctl --user enable local-voice-user.service + +# 查看日志 +journalctl --user -u local-voice-user.service -f + +# 如果用户级服务不工作,尝试系统级服务 +sudo systemctl start local-voice.service +sudo systemctl enable local-voice.service + +# 查看系统服务日志 +sudo journalctl -u local-voice.service -f +``` + +## 服务配置说明 + +### 系统级服务 (local-voice.service) +- 适用于传统systemd环境 +- 添加了PulseAudio环境变量 +- 包含音频设备权限 + +### 用户级服务 (local-voice-user.service) +- 推荐方案,更好的音频访问 +- 自动继承用户会话环境 +- 无需额外环境变量配置 + +## 故障排除 + +### 音频设备不可用 +1. 检查pipewire-pulse服务: + ```bash + systemctl --user status pipewire-pulse + systemctl --user start pipewire-pulse + ``` + +2. 检查音频设备权限: + ```bash + groups # 确认在audio组中 + ``` + +3. 测试直接运行: + ```bash + python3 multiprocess_recorder.py --enable-nfc + ``` + +### 权限问题 +如果遇到权限错误,确保用户在audio组中: +```bash +sudo usermod -a -G audio $USER +# 重新登录或重启系统 +``` + +### 环境变量问题 +如果用户级服务正常但系统级服务有问题,可能需要: +1. 检查环境变量是否正确设置 +2. 确认pipewire-pulse服务在系统启动时运行 +3. 考虑使用用户级服务作为主要方案 + +## 验证步骤 + +1. **环境测试**: `python3 test-audio.py` +2. **服务启动**: `systemctl --user start local-voice-user.service` +3. **日志监控**: `journalctl --user -u local-voice-user.service -f` +4. **功能测试**: 使用NFC卡片触发录音功能 + +## 备用方案 + +如果上述方案不工作,可以考虑: + +### 方案A: 使用screen会话 +```bash +screen -dmS local-voice python3 ~/Local-Voice/multiprocess_recorder.py --enable-nfc +screen -r local-voice # 查看输出 +``` + +### 方案B: 使用cron启动 +在crontab中添加: +``` +@reboot cd ~/Local-Voice && python3 multiprocess_recorder.py --enable-nfc +``` + +### 方案C: 修改代码支持多种采样率 +如果需要修改代码支持48000Hz采样率,请告知。 \ No newline at end of file diff --git a/config.json b/config.json index 0d61366..d086b5c 100644 --- a/config.json +++ b/config.json @@ -22,7 +22,9 @@ "enable_llm": true, "enable_tts": true, "character": "libai", - "max_tokens": 50 + "max_tokens": 50, + "enable_chat_memory": true, + "max_history_length": 5 }, "detection": { "zcr_min": 2400, diff --git a/control_system.py b/control_system.py index 090e338..a1afbf3 100644 --- a/control_system.py +++ b/control_system.py @@ -91,6 +91,18 @@ class ControlSystem: 'failed_processing': 0 } + # 聊天历史记录 + self.chat_history = [] + # 从配置中读取聊天历史设置 + self.enable_chat_memory = self.config.get('processing', {}).get('enable_chat_memory', True) + self.max_history_length = self.config.get('processing', {}).get('max_history_length', 5) + + # 显示聊天记忆状态 + if self.enable_chat_memory: + print(f"💬 聊天记忆功能已启用,最多保存 {self.max_history_length} 轮对话") + else: + print("💬 聊天记忆功能已禁用") + # 运行状态 self.running = True @@ -997,11 +1009,28 @@ class ControlSystem: "Authorization": f"Bearer {self.api_config['llm']['api_key']}" } + # 构建消息列表 messages = [ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": text} + { + "role": "system", + "content": system_prompt + } ] + # 如果启用聊天记忆,添加历史对话记录 + if self.enable_chat_memory: + for history_item in self.chat_history: + messages.extend([ + {"role": "user", "content": history_item["user"]}, + {"role": "assistant", "content": history_item["assistant"]} + ]) + + # 添加当前用户消息 + messages.append({ + "role": "user", + "content": text + }) + data = { "model": self.api_config['llm']['model'], "messages": messages, @@ -1020,7 +1049,14 @@ class ControlSystem: result = response.json() if 'choices' in result and len(result['choices']) > 0: content = result['choices'][0]['message']['content'] - return content.strip() + filtered_content = self._filter_parentheses_content(content.strip()) + + # 保存对话到历史记录 + if self.enable_chat_memory: + self._add_to_chat_history(text, filtered_content) + print(f"💬 对话已保存到历史记录 (当前历史长度: {len(self.chat_history)})") + + return filtered_content print(f"❌ LLM API调用失败: {response.status_code}") return None @@ -1059,13 +1095,23 @@ class ControlSystem: { "role": "system", "content": system_prompt - }, - { - "role": "user", - "content": text } ] + # 如果启用聊天记忆,添加历史对话记录 + if self.enable_chat_memory: + for history_item in self.chat_history: + messages.extend([ + {"role": "user", "content": history_item["user"]}, + {"role": "assistant", "content": history_item["assistant"]} + ]) + + # 添加当前用户消息 + messages.append({ + "role": "user", + "content": text + }) + data = { "model": self.api_config['llm']['model'], "messages": messages, @@ -1181,6 +1227,12 @@ class ControlSystem: print(f"🎵 发送剩余句子: {filtered_sentence}") self._send_streaming_text_to_output_process(filtered_sentence) + # 保存完整回复到历史记录 + if accumulated_text and self.enable_chat_memory: + filtered_response = self._filter_parentheses_content(accumulated_text.strip()) + self._add_to_chat_history(text, filtered_response) + print(f"💬 对话已保存到历史记录 (当前历史长度: {len(self.chat_history)})") + # 通知输出进程LLM生成已完成 self._notify_llm_complete() @@ -1651,6 +1703,10 @@ class ControlSystem: print(f"🔄 角色已切换: {old_character} -> {character_name}") + # 清空聊天历史 + self.clear_chat_history() + print(f"🔄 角色切换,聊天历史已清空") + # 播放新角色打招呼 print("🎭 播放新角色打招呼...") greeting_success = self.play_greeting() @@ -1756,6 +1812,33 @@ class ControlSystem: "current_character": self.nfc_manager.get_current_character(), "running": self.nfc_manager.running } + + def _add_to_chat_history(self, user_message, assistant_response): + """添加对话到历史记录""" + import time + + # 如果历史记录超过最大长度,移除最早的记录 + if len(self.chat_history) >= self.max_history_length: + self.chat_history.pop(0) + + # 添加新的对话记录 + self.chat_history.append({ + "user": user_message, + "assistant": assistant_response, + "timestamp": time.time() + }) + + def clear_chat_history(self): + """清空聊天历史""" + self.chat_history = [] + print("💬 聊天历史已清空") + + def get_chat_history_summary(self): + """获取聊天历史摘要""" + if not self.chat_history: + return "暂无聊天历史" + + return f"当前有 {len(self.chat_history)} 轮对话记录" def main(): """主函数""" diff --git a/deploy-service.sh b/deploy-service.sh new file mode 100755 index 0000000..de0589b --- /dev/null +++ b/deploy-service.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Local Voice 服务部署脚本 +# 用于解决systemd环境下的音频设备访问问题 + +echo "🔧 Local Voice 服务部署脚本" +echo "==================================" + +# 获取用户ID +USER_ID=$(id -u) +USERNAME=$(whoami) +echo "👤 当前用户: $USERNAME (UID: $USER_ID)" + +# 备份原有服务文件 +echo "💾 备份原有服务文件..." +if systemctl list-unit-files | grep -q "local-voice.service"; then + sudo systemctl stop local-voice.service 2>/dev/null + sudo systemctl disable local-voice.service 2>/dev/null + sudo cp /etc/systemd/system/local-voice.service /etc/systemd/system/local-voice.service.backup 2>/dev/null || true + echo "✅ 原有服务已备份" +fi + +# 安装systemd服务 +echo "📦 安装systemd服务..." +sudo cp local-voice.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable local-voice.service +echo "✅ 系统级服务已安装" + +# 安装用户级服务 +echo "🏠 安装用户级服务..." +mkdir -p ~/.config/systemd/user/ +cp local-voice-user.service ~/.config/systemd/user/ +systemctl --user daemon-reload +systemctl --user enable local-voice-user.service +echo "✅ 用户级服务已安装" + +# 确保pipewire-pulse运行 +echo "🎵 检查音频服务..." +if ! systemctl --user is-active --quiet pipewire-pulse; then + echo "🔧 启动pipewire-pulse服务..." + systemctl --user start pipewire-pulse + systemctl --user enable pipewire-pulse + echo "✅ pipewire-pulse已启动" +else + echo "✅ pipewire-pulse正在运行" +fi + +# 启用用户服务 lingering (允许用户服务在登出后继续运行) +echo "🔄 启用用户服务lingering..." +sudo loginctl enable-linger $USERNAME +echo "✅ 用户服务lingering已启用" + +# 测试建议 +echo "" +echo "🎯 测试建议:" +echo "1. 先测试用户级服务: systemctl --user start local-voice-user.service" +echo "2. 查看用户服务日志: journalctl --user -u local-voice-user.service -f" +echo "3. 如果用户级服务正常,再考虑使用系统级服务" +echo "" +echo "🔍 故障排除:" +echo "- 如果用户级服务正常: 建议使用 systemctl --user start local-voice-user.service" +echo "- 如果系统级服务正常: 使用 sudo systemctl start local-voice.service" +echo "" +echo "📊 当前音频设备状态:" +python3 -c " +import pyaudio +p = pyaudio.PyAudio() +print('可用音频设备:') +for i in range(p.get_device_count()): + info = p.get_device_info_by_index(i) + if info['maxInputChannels'] > 0: + print(f' 设备 {i}: {info[\"name\"]} (默认采样率: {info[\"defaultSampleRate\"]}Hz)') +p.terminate() +" + +echo "" +echo "✅ 部署完成!" \ No newline at end of file diff --git a/local-voice-user.service b/local-voice-user.service new file mode 100644 index 0000000..d97a8fd --- /dev/null +++ b/local-voice-user.service @@ -0,0 +1,16 @@ +[Unit] +Description=Local Voice Multiprocess Recorder with NFC (User Service) +After=network.target sound.target pipewire-pulse.service +Wants=network.target sound.target pipewire-pulse.service + +[Service] +Type=simple +WorkingDirectory=/home/zhuchaowe/Local-Voice +ExecStart=/usr/bin/python3 /home/zhuchaowe/Local-Voice/multiprocess_recorder.py --enable-nfc +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/local-voice.service b/local-voice.service index e5411db..f2a21fe 100644 --- a/local-voice.service +++ b/local-voice.service @@ -1,17 +1,24 @@ [Unit] Description=Local Voice Multiprocess Recorder with NFC -After=network.target -Wants=network.target +After=network.target sound.target +Wants=network.target sound.target [Service] Type=simple User=zhuchaowe -WorkingDirectory=/home/zhuchaowe +Group=audio +WorkingDirectory=/home/zhuchaowe/Local-Voice +Environment="PULSE_SERVER=unix:/run/user/%U/pulse/native" +Environment="XDG_RUNTIME_DIR=/run/user/%U" +Environment="DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%U/bus" ExecStart=/usr/bin/python3 /home/zhuchaowe/Local-Voice/multiprocess_recorder.py --enable-nfc Restart=always RestartSec=10 StandardOutput=journal StandardError=journal +# 添加设备访问权限 +DeviceAllow=/dev/snd/* +DevicePolicy=auto [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/test-audio.py b/test-audio.py new file mode 100755 index 0000000..056c74e --- /dev/null +++ b/test-audio.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +音频设备测试脚本 +用于测试不同环境下的音频设备访问 +""" + +import pyaudio +import sys +import os + +def test_audio_access(): + print("🔍 音频设备访问测试") + print("=" * 50) + + try: + p = pyaudio.PyAudio() + print("✅ PyAudio 初始化成功") + + # 显示所有音频设备 + print("\n📋 可用音频设备:") + for i in range(p.get_device_count()): + info = p.get_device_info_by_index(i) + if info['maxInputChannels'] > 0: + print(f" 设备 {i}: {info['name']}") + print(f" 输入通道: {info['maxInputChannels']}") + print(f" 默认采样率: {info['defaultSampleRate']}") + + # 测试16000Hz采样率 + print(f"\n🎵 测试16000Hz采样率访问:") + + # 测试默认设备 + try: + stream = p.open( + format=pyaudio.paInt16, + channels=1, + rate=16000, + input=True, + frames_per_buffer=1024 + ) + print("✅ 默认设备支持16000Hz") + stream.close() + except Exception as e: + print(f"❌ 默认设备不支持16000Hz: {e}") + + # 测试pulse设备 + try: + stream = p.open( + format=pyaudio.paInt16, + channels=1, + rate=16000, + input=True, + frames_per_buffer=1024, + input_device_index=6 # pulse设备 + ) + print("✅ Pulse设备支持16000Hz") + stream.close() + except Exception as e: + print(f"❌ Pulse设备不支持16000Hz: {e}") + + p.terminate() + print("\n✅ 音频测试完成") + + except Exception as e: + print(f"❌ 音频测试失败: {e}") + sys.exit(1) + +def check_environment(): + print("\n🌍 环境信息:") + print(f"用户: {os.getenv('USER', 'Unknown')}") + print(f"用户ID: {os.getuid()}") + print(f"PULSE_SERVER: {os.getenv('PULSE_SERVER', 'Not set')}") + print(f"XDG_RUNTIME_DIR: {os.getenv('XDG_RUNTIME_DIR', 'Not set')}") + print(f"DBUS_SESSION_BUS_ADDRESS: {os.getenv('DBUS_SESSION_BUS_ADDRESS', 'Not set')}") + +if __name__ == "__main__": + check_environment() + test_audio_access() \ No newline at end of file