onju-v2/pipeline/protocol.py
justLV b3538493a6 Add modular async pipeline server and ESP32 mDNS fallback
Pipeline: async voice pipeline replacing monolithic threaded server.
ASR, LLM, and TTS are independent pluggable services. ASR calls
external parakeet-asr-server, LLM uses any OpenAI-compatible
endpoint, TTS uses ElevenLabs with pluggable backend interface.

Firmware: add mDNS hostname resolution as fallback when multicast
discovery doesn't work. Resolves configured server_hostname via
MDNS.queryHost() on boot, falls back to multicast if resolution fails.

Also adds test_client.py that emulates an ESP32 device for testing
without hardware (TCP server, Opus decode, mic streaming).
2026-02-07 15:04:12 -08:00

55 lines
1.7 KiB
Python

import asyncio
import logging
import struct
log = logging.getLogger(__name__)
async def send_tcp(ip: str, port: int, header: bytes, data: bytes | None = None, timeout: float = 60):
try:
reader, writer = await asyncio.wait_for(
asyncio.open_connection(ip, port), timeout=5
)
writer.write(header)
if data:
writer.write(data)
await writer.drain()
writer.close()
await writer.wait_closed()
except asyncio.TimeoutError:
log.error(f"TCP timeout to {ip}:{port}")
except Exception as e:
log.error(f"TCP error to {ip}:{port}: {e}")
async def send_audio(ip: str, port: int, opus_payload: bytes, mic_timeout: int = 60, volume: int = 14, fade: int = 6):
# header[0] 0xAA for audio
# header[1:2] mic timeout in seconds (big-endian)
# header[3] volume
# header[4] fade rate
# header[5] compression type (2 = Opus)
header = bytes([
0xAA,
(mic_timeout >> 8) & 0xFF,
mic_timeout & 0xFF,
volume,
fade,
2, # Opus
])
await send_tcp(ip, port, header, opus_payload)
async def send_led_blink(ip: str, port: int, intensity: int, r: int = 255, g: int = 255, b: int = 255, fade: int = 6):
# header[0] 0xCC for LED blink
# header[1] starting intensity
# header[2:4] RGB
# header[5] fade rate
header = bytes([0xCC, intensity, r, g, b, fade])
await send_tcp(ip, port, header, timeout=0.1)
async def send_stop_listening(ip: str, port: int):
# header[0] 0xDD for mic timeout
# header[1:2] timeout = 0 (stop)
header = bytes([0xDD, 0, 0, 0, 0, 0])
await send_tcp(ip, port, header, timeout=0.2)