first commit
This commit is contained in:
203
archive/claw_mic.py
Normal file
203
archive/claw_mic.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
import speech_recognition as sr
|
||||
import pygame
|
||||
import io
|
||||
from ctypes import *
|
||||
|
||||
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
|
||||
|
||||
# ALSA 에러 로그 숨기기
|
||||
ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int, c_char_p)
|
||||
def py_error_handler(filename, line, function, err, fmt): pass
|
||||
c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)
|
||||
try:
|
||||
asound = cdll.LoadLibrary('libasound.so.2')
|
||||
asound.snd_lib_error_set_handler(c_error_handler)
|
||||
except:
|
||||
pass
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ─── 환경 설정 ───────────────────────────────────────────────
|
||||
VM_IP = "192.168.0.149"
|
||||
OPENCLAW_PORT = 18789
|
||||
|
||||
# 오픈클로 Gateway RPC 엔드포인트
|
||||
# chat.send: 텍스트 메시지 전송
|
||||
CHAT_URL = f"http://{VM_IP}:{OPENCLAW_PORT}/api/v1/chat/completions"
|
||||
# 오디오 첨부 전송 (media.audio enabled 시 Gemini가 STT 처리)
|
||||
AUDIO_URL = f"http://{VM_IP}:{OPENCLAW_PORT}/v1/audio/transcriptions"
|
||||
|
||||
# TTS: openedai-speech 또는 오픈클로 내장 TTS
|
||||
TTS_URL = f"http://{VM_IP}:{OPENCLAW_PORT}/v1/audio/speech"
|
||||
|
||||
WAKE_WORD = "애미나이"
|
||||
MIC_INDEX = 3
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
pygame.mixer.init()
|
||||
|
||||
def send_audio_to_openclaw(audio_data) -> str:
|
||||
"""
|
||||
WAV를 오픈클로에 전송.
|
||||
media.audio.enabled + google provider 설정 시
|
||||
오픈클로가 Gemini로 STT 후 LLM 답변까지 처리.
|
||||
반환값: 오픈클로 텍스트 응답
|
||||
"""
|
||||
try:
|
||||
wav_bytes = audio_data.get_wav_data()
|
||||
files = {
|
||||
'audio': ('cmd.wav', io.BytesIO(wav_bytes), 'audio/wav')
|
||||
}
|
||||
logger.info("오픈클로로 오디오 전송 중...")
|
||||
resp = requests.post(AUDIO_URL, files=files, timeout=30)
|
||||
resp.raise_for_status()
|
||||
logger.info(f"응답 코드: {resp.status_code}")
|
||||
|
||||
# 오픈클로 응답이 JSON인 경우
|
||||
try:
|
||||
data = resp.json()
|
||||
# 오픈클로 응답 구조에 따라 키 조정
|
||||
return data.get("reply") or data.get("message") or data.get("text") or str(data)
|
||||
except Exception:
|
||||
# 텍스트 응답인 경우
|
||||
return resp.text
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error(f"연결 실패 - {VM_IP}:{OPENCLAW_PORT} 확인 필요")
|
||||
return ""
|
||||
except Exception as e:
|
||||
logger.error(f"오디오 전송 실패: {e}")
|
||||
return ""
|
||||
|
||||
def send_text_to_openclaw(text: str) -> str:
|
||||
"""텍스트를 오픈클로 chat API로 전송 (오디오 전송 안 될 때 폴백)"""
|
||||
try:
|
||||
payload = {"message": text}
|
||||
resp = requests.post(CHAT_URL, json=payload, timeout=30)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return data.get("reply") or data.get("message") or data.get("text") or str(data)
|
||||
except Exception as e:
|
||||
logger.error(f"텍스트 전송 실패: {e}")
|
||||
return ""
|
||||
|
||||
def speak_edge_tts(text: str):
|
||||
"""
|
||||
오픈클로 내장 TTS (Edge TTS, API 키 불필요) 사용.
|
||||
안 되면 Google gTTS로 폴백.
|
||||
"""
|
||||
try:
|
||||
payload = {
|
||||
"model": "tts-1",
|
||||
"input": text,
|
||||
"voice": "alloy",
|
||||
"response_format": "mp3"
|
||||
}
|
||||
resp = requests.post(TTS_URL, json=payload, timeout=15)
|
||||
if resp.status_code == 200:
|
||||
audio_buf = io.BytesIO(resp.content)
|
||||
pygame.mixer.music.load(audio_buf, "mp3")
|
||||
pygame.mixer.music.play()
|
||||
while pygame.mixer.music.get_busy():
|
||||
time.sleep(0.1)
|
||||
logger.info("TTS 재생 완료")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning(f"오픈클로 TTS 실패, gTTS 시도: {e}")
|
||||
|
||||
# gTTS 폴백 (로컬 설치: pip install gtts)
|
||||
try:
|
||||
from gtts import gTTS
|
||||
tts = gTTS(text=text, lang='ko')
|
||||
tts.save("/tmp/reply.mp3")
|
||||
pygame.mixer.music.load("/tmp/reply.mp3")
|
||||
pygame.mixer.music.play()
|
||||
while pygame.mixer.music.get_busy():
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
logger.error(f"gTTS도 실패: {e}")
|
||||
|
||||
def main():
|
||||
recognizer = sr.Recognizer()
|
||||
recognizer.energy_threshold = 400
|
||||
recognizer.pause_threshold = 1.0
|
||||
|
||||
# 마이크 목록 출력
|
||||
mics = sr.Microphone.list_microphone_names()
|
||||
logger.info(f"사용 가능한 마이크 ({len(mics)}개):")
|
||||
for i, mic in enumerate(mics):
|
||||
logger.info(f" {i}: {mic}")
|
||||
|
||||
# 안전하게 마이크 열기
|
||||
mic = None
|
||||
for test_index in [MIC_INDEX, 0, 1, 2, 3]: # 여러 인덱스 시도
|
||||
try:
|
||||
logger.info(f"마이크 {test_index} 테스트 중...")
|
||||
mic = sr.Microphone(device_index=test_index)
|
||||
with mic as source:
|
||||
test_audio = recognizer.listen(source, timeout=1, phrase_time_limit=1)
|
||||
logger.info(f"✅ 마이크 {test_index} 성공!")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"❌ 마이크 {test_index} 실패: {e}")
|
||||
continue
|
||||
|
||||
if mic is None:
|
||||
logger.error("사용 가능한 마이크를 찾을 수 없습니다. 프로그램 종료.")
|
||||
return
|
||||
|
||||
# 소음 보정 (실패해도 무시)
|
||||
try:
|
||||
with mic as source:
|
||||
recognizer.adjust_for_ambient_noise(source, duration=1)
|
||||
logger.info(f"energy_threshold: {recognizer.energy_threshold:.0f}")
|
||||
except Exception as e:
|
||||
logger.warning(f"소음 보정 실패 (무시): {e}")
|
||||
|
||||
logger.info(f"==== '{WAKE_WORD}' 대기 중 ====")
|
||||
|
||||
while True:
|
||||
try:
|
||||
with mic as source:
|
||||
logger.debug("마이크 듣는 중...")
|
||||
audio = recognizer.listen(source, timeout=None, phrase_time_limit=3)
|
||||
|
||||
try:
|
||||
text = recognizer.recognize_google(audio, language='ko-KR')
|
||||
logger.info(f"🎤 인식: '{text}'")
|
||||
|
||||
if WAKE_WORD in text:
|
||||
logger.info("🔔 호출어 감지!")
|
||||
speak_edge_tts("네, 말씀하세요")
|
||||
|
||||
# 명령어 녹음
|
||||
with mic as source:
|
||||
logger.info("명령 녹음 중...")
|
||||
cmd_audio = recognizer.listen(source, timeout=5, phrase_time_limit=10)
|
||||
|
||||
# 오픈클로 전송
|
||||
reply = send_audio_to_openclaw(cmd_audio)
|
||||
if reply:
|
||||
speak_edge_tts(reply)
|
||||
else:
|
||||
speak_edge_tts("응답을 받지 못했어요.")
|
||||
|
||||
except sr.UnknownValueError:
|
||||
pass # 조용히 무시
|
||||
except sr.RequestError as e:
|
||||
logger.error(f"Google STT 오류: {e}")
|
||||
|
||||
except sr.WaitTimeoutError:
|
||||
pass # 타임아웃 무시
|
||||
except Exception as e:
|
||||
logger.error(f"루프 오류: {e}")
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user