Update __main__.py
Adds XVF3800-specific initialization flow during controller startup. Ensures the XVF3800 preflight runs at the right time so audio initialization sees the correct device state.
This commit is contained in:
@@ -3,6 +3,8 @@ import argparse
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -151,6 +153,9 @@ async def main() -> None:
|
|||||||
# --- 2. Load Preferences ---
|
# --- 2. Load Preferences ---
|
||||||
preferences = _load_preferences(config)
|
preferences = _load_preferences(config)
|
||||||
|
|
||||||
|
# --- 2b. XVF3800 Startup Workarounds (optional) ---
|
||||||
|
_xvf3800_startup_preflight(config)
|
||||||
|
|
||||||
# --- 3. Find Microphone ---
|
# --- 3. Find Microphone ---
|
||||||
mic = _get_microphone(config)
|
mic = _get_microphone(config)
|
||||||
|
|
||||||
@@ -264,15 +269,111 @@ def _load_preferences(config: Config) -> Preferences:
|
|||||||
|
|
||||||
return preferences
|
return preferences
|
||||||
|
|
||||||
|
|
||||||
|
def _xvf3800_startup_preflight(config: Config) -> None:
|
||||||
|
"""
|
||||||
|
Best-effort XVF3800 USB preflight.
|
||||||
|
|
||||||
|
Some platforms (notably certain SBC USB controllers) can leave the XVF3800
|
||||||
|
capture endpoint "silent" until the device is rebooted.
|
||||||
|
|
||||||
|
If an XVF3800 is configured (LED/button/audio), we optionally:
|
||||||
|
1) issue an XVF3800 REBOOT
|
||||||
|
2) wait for USB re-enumeration
|
||||||
|
3) set AUDIO_MGR_OP_L and AUDIO_MGR_OP_R to (7, 3) to force both channels
|
||||||
|
to the ASR3 output (as discovered via xvf_host.py testing)
|
||||||
|
|
||||||
|
This uses PyUSB directly (no dependency on xvf_host.py).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Determine whether XVF3800 is in use at all
|
||||||
|
led_cfg = getattr(config, "led", None)
|
||||||
|
btn_cfg = getattr(config, "button", None)
|
||||||
|
aud_cfg = getattr(config, "audio", None)
|
||||||
|
|
||||||
|
uses_xvf = False
|
||||||
|
if led_cfg and getattr(led_cfg, "led_type", "").lower() == "xvf3800":
|
||||||
|
uses_xvf = True
|
||||||
|
if btn_cfg and getattr(btn_cfg, "enabled", False) and getattr(btn_cfg, "mode", "").lower() == "xvf3800":
|
||||||
|
uses_xvf = True
|
||||||
|
if aud_cfg and isinstance(getattr(aud_cfg, "input_device", None), str) and "xvf3800" in aud_cfg.input_device.lower():
|
||||||
|
uses_xvf = True
|
||||||
|
|
||||||
|
if not uses_xvf:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Env toggles (default enabled)
|
||||||
|
do_reboot = os.environ.get("LVA_XVF3800_STARTUP_REBOOT", "1").strip().lower() not in ("0", "false", "no", "off")
|
||||||
|
do_route = os.environ.get("LVA_XVF3800_STARTUP_SET_ASR3", "1").strip().lower() not in ("0", "false", "no", "off")
|
||||||
|
do_save = os.environ.get("LVA_XVF3800_STARTUP_SAVE_CONFIG", "0").strip().lower() in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
if not (do_reboot or do_route):
|
||||||
|
return
|
||||||
|
|
||||||
|
from .xvf3800_led_backend import XVF3800USBDevice
|
||||||
|
|
||||||
|
_LOGGER.info("XVF3800 startup preflight: begin (reboot=%s, set_asr3=%s, save=%s)", do_reboot, do_route, do_save)
|
||||||
|
|
||||||
|
if do_reboot:
|
||||||
|
try:
|
||||||
|
dev = XVF3800USBDevice()
|
||||||
|
_LOGGER.info("XVF3800 startup preflight: issuing REBOOT to USB device")
|
||||||
|
dev.reboot()
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
dev.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Wait for USB to cycle
|
||||||
|
XVF3800USBDevice.wait_for_reenumeration(timeout_s=12.0, settle_s=1.0)
|
||||||
|
|
||||||
|
if do_route:
|
||||||
|
dev2 = XVF3800USBDevice()
|
||||||
|
try:
|
||||||
|
_LOGGER.info("XVF3800 startup preflight: setting AUDIO_MGR_OP_L and AUDIO_MGR_OP_R to (7, 3)")
|
||||||
|
dev2.set_audio_mgr_op_l(7, 3)
|
||||||
|
dev2.set_audio_mgr_op_r(7, 3)
|
||||||
|
if do_save:
|
||||||
|
_LOGGER.info("XVF3800 startup preflight: saving configuration to flash")
|
||||||
|
dev2.save_configuration()
|
||||||
|
finally:
|
||||||
|
dev2.close()
|
||||||
|
|
||||||
|
_LOGGER.info("XVF3800 startup preflight: done")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
_LOGGER.warning("XVF3800 startup preflight failed (continuing): %s", e)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _get_microphone(config: Config):
|
def _get_microphone(config: Config):
|
||||||
"""Finds and returns the microphone specified in the config."""
|
"""Finds and returns the microphone specified in the config."""
|
||||||
mic = None
|
mic = None
|
||||||
if config.audio.input_device is not None:
|
input_spec = getattr(config.audio, "input_device", None)
|
||||||
|
|
||||||
|
# If the user specified an index, honor it.
|
||||||
|
if input_spec is not None:
|
||||||
try:
|
try:
|
||||||
input_device_idx = int(config.audio.input_device)
|
input_device_idx = int(input_spec)
|
||||||
mic = sc.all_microphones(include_loopback=False)[input_device_idx]
|
mic = sc.all_microphones(include_loopback=False)[input_device_idx]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
mic = sc.get_microphone(config.audio.input_device, include_loopback=False)
|
# If the user specified a name, retry a bit (helps after USB re-enumeration / slow PipeWire startup).
|
||||||
|
want_name = str(input_spec)
|
||||||
|
is_xvf = "xvf3800" in want_name.lower()
|
||||||
|
deadline = time.time() + (20.0 if is_xvf else 5.0)
|
||||||
|
|
||||||
|
last_err: Optional[Exception] = None
|
||||||
|
while time.time() < deadline:
|
||||||
|
try:
|
||||||
|
mic = sc.get_microphone(want_name, include_loopback=False)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
last_err = e
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
if mic is None and last_err is not None:
|
||||||
|
_LOGGER.warning("Failed to open configured mic %r after retries: %s", want_name, last_err)
|
||||||
else:
|
else:
|
||||||
mic = sc.default_microphone()
|
mic = sc.default_microphone()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user