This commit is contained in:
Alex
2026-04-11 15:51:24 -05:00
parent 14809d0194
commit 10e39dd0f1
2 changed files with 46 additions and 29 deletions

View File

@@ -1,13 +1,18 @@
# HeadMic - Vixy's Ears # HeadMic - Vixy's Ears
# For Raspberry Pi 5 (head-vixy) # For Raspberry Pi 5 (head-vixy)
# Python 3.13 compatibility
setuptools>=75.0.0
# Web framework # Web framework
fastapi>=0.104.0 fastapi>=0.104.0
uvicorn>=0.24.0 uvicorn>=0.24.0
python-multipart>=0.0.6
# Audio # Audio
pyaudio>=0.2.13 pyaudio>=0.2.13
webrtcvad>=2.0.10 webrtcvad>=2.0.10
numpy>=1.24.0
# Wake word detection # Wake word detection
pvporcupine>=3.0.0 pvporcupine>=3.0.0

View File

@@ -28,15 +28,16 @@ PID = 0x001A
CTRL_REQUEST_TYPE_OUT = usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE if PYUSB_AVAILABLE else 0 CTRL_REQUEST_TYPE_OUT = usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE if PYUSB_AVAILABLE else 0
CTRL_REQUEST_TYPE_IN = usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE if PYUSB_AVAILABLE else 0 CTRL_REQUEST_TYPE_IN = usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE if PYUSB_AVAILABLE else 0
# Resource IDs # Resource IDs (wIndex in USB control transfer)
GPO_RESID = 20 GPO_RESID = 20
# Parameter indices (within resource) # Command IDs (wValue in USB control transfer)
DOA_VALUE_IDX = 18 # returns (angle 0-359, vad 0/1) DOA_VALUE_CMD = 18 # returns (angle 0-359, vad 0/1)
LED_EFFECT_IDX = 0 # 0=off, 1=breath, 2=rainbow, 3=solid, 4=doa, 5=ring LED_EFFECT_CMD = 12 # 0=off, 1=breath, 2=rainbow, 3=solid, 4=doa, 5=ring
LED_BRIGHTNESS_IDX = 1 LED_BRIGHTNESS_CMD = 13
LED_COLOR_IDX = 3 # single uint32 color LED_COLOR_CMD = 15 # single uint32 color
LED_RING_COLOR_IDX = 5 # 12 × uint32 LED_DOA_COLOR_CMD = 16 # two uint32 values: base + indicator
LED_RING_COLOR_CMD = 17 # 12 × uint32
class XVF3800: class XVF3800:
@@ -48,29 +49,41 @@ class XVF3800:
self.bus = usb_device.bus self.bus = usb_device.bus
self.address = usb_device.address self.address = usb_device.address
def _read(self, resid: int, param_idx: int, length: int) -> bytes: def _read(self, resid: int, cmdid: int, length: int) -> bytes:
"""Read parameter via USB control transfer.""" """Read parameter via USB control transfer.
wValue = (resid << 8) | param_idx wValue = cmdid | 0x80 (read flag), wIndex = resid."""
try: try:
data = self.dev.ctrl_transfer(CTRL_REQUEST_TYPE_IN, 0, wValue, 0, length, timeout=1000) data = self.dev.ctrl_transfer(
CTRL_REQUEST_TYPE_IN, 0,
0x80 | cmdid, # wValue: cmdid with read bit
resid, # wIndex: resource ID
length * 4, # bytes to read (length is in words)
timeout=1000
)
return bytes(data) return bytes(data)
except Exception as e: except Exception as e:
logger.debug("USB read error (resid=%d, param=%d): %s", resid, param_idx, e) logger.debug("USB read error (resid=%d, cmd=%d): %s", resid, cmdid, e)
return b"" return b""
def _write(self, resid: int, param_idx: int, data: bytes): def _write(self, resid: int, cmdid: int, data: bytes):
"""Write parameter via USB control transfer.""" """Write parameter via USB control transfer.
wValue = (resid << 8) | param_idx wValue = cmdid, wIndex = resid."""
try: try:
self.dev.ctrl_transfer(CTRL_REQUEST_TYPE_OUT, 0, wValue, 0, data, timeout=1000) self.dev.ctrl_transfer(
CTRL_REQUEST_TYPE_OUT, 0,
cmdid, # wValue: command ID
resid, # wIndex: resource ID
data,
timeout=1000
)
except Exception as e: except Exception as e:
logger.debug("USB write error (resid=%d, param=%d): %s", resid, param_idx, e) logger.debug("USB write error (resid=%d, cmd=%d): %s", resid, cmdid, e)
# --- DoA --- # --- DoA ---
def read_doa(self) -> tuple[int, bool]: def read_doa(self) -> tuple[int, bool]:
"""Read Direction of Arrival. Returns (angle 0-359, vad True/False).""" """Read Direction of Arrival. Returns (angle 0-359, vad True/False)."""
data = self._read(GPO_RESID, DOA_VALUE_IDX, 4) data = self._read(GPO_RESID, DOA_VALUE_CMD, 2) # 2 uint16 words
if len(data) < 4: if len(data) < 4:
return 0, False return 0, False
angle, vad = struct.unpack_from("<HH", data) angle, vad = struct.unpack_from("<HH", data)
@@ -79,29 +92,28 @@ class XVF3800:
# --- LEDs --- # --- LEDs ---
def led_off(self): def led_off(self):
self._write(GPO_RESID, LED_EFFECT_IDX, struct.pack("<I", 0)) self._write(GPO_RESID, LED_EFFECT_CMD, struct.pack("<I", 0))
def led_solid(self, color: int): def led_solid(self, color: int):
"""Solid color on all LEDs. color is 0xRRGGBB.""" """Solid color on all LEDs. color is 0xRRGGBB."""
self._write(GPO_RESID, LED_COLOR_IDX, struct.pack("<I", color)) self._write(GPO_RESID, LED_COLOR_CMD, struct.pack("<I", color))
self._write(GPO_RESID, LED_EFFECT_IDX, struct.pack("<I", 3)) self._write(GPO_RESID, LED_EFFECT_CMD, struct.pack("<I", 3))
def led_breath(self, color: int, brightness: int = 128): def led_breath(self, color: int, brightness: int = 128):
"""Breathing effect.""" """Breathing effect."""
self._write(GPO_RESID, LED_COLOR_IDX, struct.pack("<I", color)) self._write(GPO_RESID, LED_COLOR_CMD, struct.pack("<I", color))
self._write(GPO_RESID, LED_BRIGHTNESS_IDX, struct.pack("<I", brightness)) self._write(GPO_RESID, LED_BRIGHTNESS_CMD, struct.pack("<I", brightness))
self._write(GPO_RESID, LED_EFFECT_IDX, struct.pack("<I", 1)) self._write(GPO_RESID, LED_EFFECT_CMD, struct.pack("<I", 1))
def led_doa(self, base_color: int = 0x003333, doa_color: int = 0x00FFFF): def led_doa(self, base_color: int = 0x003333, doa_color: int = 0x00FFFF):
"""DoA indicator mode — shows beam direction on LED ring.""" """DoA indicator mode — shows beam direction on LED ring."""
# LED_DOA_COLOR takes two uint32 values: base + indicator
data = struct.pack("<II", base_color, doa_color) data = struct.pack("<II", base_color, doa_color)
self._write(GPO_RESID, 4, data) # param 4 = LED_DOA_COLOR self._write(GPO_RESID, LED_DOA_COLOR_CMD, data)
self._write(GPO_RESID, LED_EFFECT_IDX, struct.pack("<I", 4)) self._write(GPO_RESID, LED_EFFECT_CMD, struct.pack("<I", 4))
def led_rainbow(self, brightness: int = 128): def led_rainbow(self, brightness: int = 128):
self._write(GPO_RESID, LED_BRIGHTNESS_IDX, struct.pack("<I", brightness)) self._write(GPO_RESID, LED_BRIGHTNESS_CMD, struct.pack("<I", brightness))
self._write(GPO_RESID, LED_EFFECT_IDX, struct.pack("<I", 2)) self._write(GPO_RESID, LED_EFFECT_CMD, struct.pack("<I", 2))
class XVF3800Manager: class XVF3800Manager: