feat: add secondary radio status reporting and startup initialization improvements
- Expose secondary multicaster status in /status endpoint with dedicated "secondary" block containing runtime and persisted settings - Initialize Radio 2 checkbox state from backend streaming status to reflect active state on frontend load - Add I2C register verification after write operations with i2cget to confirm configuration - Set ADC mixer level to 60% on startup via amixer command - Preserve backward compatibility by
This commit is contained in:
@@ -100,6 +100,10 @@ except Exception:
|
||||
# Define is_streaming early from the fetched status for use throughout the UI
|
||||
is_streaming = bool(saved_settings.get("is_streaming", False))
|
||||
|
||||
# Extract secondary status, if provided by the backend /status endpoint.
|
||||
secondary_status = saved_settings.get("secondary") or {}
|
||||
secondary_is_streaming = bool(saved_settings.get("secondary_is_streaming", secondary_status.get("is_streaming", False)))
|
||||
|
||||
st.title("Auracast Audio Mode Control")
|
||||
|
||||
def render_stream_controls(status_streaming: bool, start_label: str, stop_label: str, mode_label: str):
|
||||
@@ -476,9 +480,13 @@ else:
|
||||
|
||||
# --- Radio 2 controls ---
|
||||
st.subheader("Radio 2")
|
||||
# If the backend reports that the secondary radio is currently streaming,
|
||||
# initialize the checkbox to checked so the UI reflects the active state
|
||||
# when the frontend is loaded.
|
||||
radio2_enabled_default = secondary_is_streaming
|
||||
radio2_enabled = st.checkbox(
|
||||
"Enable Radio 2",
|
||||
value=False,
|
||||
value=radio2_enabled_default,
|
||||
help="Activate a second analog radio with its own quality and timing settings."
|
||||
)
|
||||
|
||||
|
||||
@@ -144,23 +144,94 @@ _stream_lock = asyncio.Lock() # serialize initialize/stop_audio on API side
|
||||
|
||||
|
||||
async def _init_i2c_on_startup() -> None:
|
||||
cmds = [
|
||||
["i2cset", "-f", "-y", "1", "0x4a", "0x00", "0x00"],
|
||||
["i2cset", "-f", "-y", "1", "0x4a", "0x06", "0x10"],
|
||||
["i2cset", "-f", "-y", "1", "0x4a", "0x07", "0x10"],
|
||||
# Table of (register, expected_value)
|
||||
dev_add = "0x4a"
|
||||
reg_table = [
|
||||
("0x00", "0x00"),
|
||||
("0x06", "0x10"),
|
||||
("0x07", "0x10"),
|
||||
]
|
||||
for cmd in cmds:
|
||||
for reg, expected in reg_table:
|
||||
write_cmd = ["i2cset", "-f", "-y", "1", dev_add, reg, expected]
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
*write_cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
log.warning("i2cset failed (%s): rc=%s stderr=%s", " ".join(cmd), proc.returncode, (stderr or b"").decode(errors="ignore").strip())
|
||||
log.warning(
|
||||
"i2cset failed (%s): rc=%s stderr=%s",
|
||||
" ".join(write_cmd),
|
||||
proc.returncode,
|
||||
(stderr or b"").decode(errors="ignore").strip(),
|
||||
)
|
||||
# If the write failed, skip verification for this register
|
||||
continue
|
||||
except Exception as e:
|
||||
log.warning("Exception running i2cset (%s): %s", " ".join(cmd), e, exc_info=True)
|
||||
log.warning("Exception running i2cset (%s): %s", " ".join(write_cmd), e, exc_info=True)
|
||||
continue
|
||||
|
||||
# Verify configured register with i2cget
|
||||
read_cmd = ["i2cget", "-f", "-y", "1", dev_add, reg]
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*read_cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
log.warning(
|
||||
"i2cget failed (%s): rc=%s stderr=%s",
|
||||
" ".join(read_cmd),
|
||||
proc.returncode,
|
||||
(stderr or b"").decode(errors="ignore").strip(),
|
||||
)
|
||||
continue
|
||||
|
||||
value = (stdout or b"").decode(errors="ignore").strip()
|
||||
if value != expected:
|
||||
log.error(
|
||||
"I2C register verify failed: addr=0x4a reg=%s expected=%s got=%s",
|
||||
reg,
|
||||
expected,
|
||||
value,
|
||||
)
|
||||
else:
|
||||
log.info(
|
||||
"I2C register verified: addr=0x4a reg=%s value=%s",
|
||||
reg,
|
||||
value,
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning("Exception running i2cget (%s): %s", " ".join(read_cmd), e, exc_info=True)
|
||||
|
||||
|
||||
async def _set_adc_level_on_startup() -> None:
|
||||
"""Ensure ADC mixer level is set at startup.
|
||||
|
||||
Runs: amixer -c 2 set 'ADC' x%
|
||||
"""
|
||||
cmd = ["amixer", "-c", "2", "set", "ADC", "60%"]
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
log.warning(
|
||||
"amixer ADC level command failed (rc=%s): %s",
|
||||
proc.returncode,
|
||||
(stderr or b"" ).decode(errors="ignore").strip(),
|
||||
)
|
||||
else:
|
||||
log.info("amixer ADC level set successfully: %s", (stdout or b"" ).decode(errors="ignore").strip())
|
||||
except Exception as e:
|
||||
log.warning("Exception running amixer ADC level command: %s", e, exc_info=True)
|
||||
|
||||
|
||||
async def _stop_all() -> bool:
|
||||
@@ -187,6 +258,16 @@ async def _status_primary() -> dict:
|
||||
return {'is_initialized': False, 'is_streaming': False}
|
||||
return multicaster1.get_status()
|
||||
|
||||
async def _status_secondary() -> dict:
|
||||
"""Return runtime status for the SECONDARY multicaster.
|
||||
|
||||
Mirrors _status_primary but for multicaster2 so that /status can expose
|
||||
both primary and secondary state to the frontend.
|
||||
"""
|
||||
if multicaster2 is None:
|
||||
return {'is_initialized': False, 'is_streaming': False}
|
||||
return multicaster2.get_status()
|
||||
|
||||
async def _stream_lc3(audio_data: dict[str, str], bigs_template: list) -> None:
|
||||
if multicaster1 is None:
|
||||
raise HTTPException(status_code=500, detail='Auracast endpoint was never intialized')
|
||||
@@ -348,8 +429,23 @@ async def send_audio(audio_data: dict[str, str]):
|
||||
@app.get("/status")
|
||||
async def get_status():
|
||||
"""Gets current status (worker) merged with persisted settings cache."""
|
||||
status = await _status_primary()
|
||||
status.update(load_stream_settings())
|
||||
primary_runtime = await _status_primary()
|
||||
primary_persisted = load_stream_settings() or {}
|
||||
|
||||
# Preserve existing top-level shape for primary for compatibility
|
||||
status: dict = {}
|
||||
status.update(primary_runtime)
|
||||
status.update(primary_persisted)
|
||||
|
||||
# Attach secondary block with its own runtime + persisted settings
|
||||
secondary_runtime = await _status_secondary()
|
||||
secondary_persisted = load_stream_settings2() or {}
|
||||
secondary: dict = {}
|
||||
secondary.update(secondary_runtime)
|
||||
secondary.update(secondary_persisted)
|
||||
status["secondary"] = secondary
|
||||
status["secondary_is_streaming"] = bool(secondary.get("is_streaming", False))
|
||||
|
||||
return status
|
||||
|
||||
async def _autostart_from_settings():
|
||||
@@ -538,6 +634,8 @@ async def _autostart_from_settings():
|
||||
channel_names = settings.get('channel_names') or ["Broadcast0"]
|
||||
program_info = settings.get('program_info') or channel_names
|
||||
languages = settings.get('languages') or ["deu"]
|
||||
big_ids = settings.get('big_ids') or []
|
||||
big_addrs = settings.get('big_random_addresses') or []
|
||||
stream_password = settings.get('stream_password')
|
||||
original_ts = settings.get('timestamp')
|
||||
previously_streaming = bool(settings.get('is_streaming'))
|
||||
@@ -689,6 +787,8 @@ async def _startup_autostart_event():
|
||||
# Hydrate settings cache once to avoid disk I/O during /status
|
||||
_init_settings_cache_from_disk()
|
||||
await _init_i2c_on_startup()
|
||||
# Ensure ADC mixer level is set at startup
|
||||
await _set_adc_level_on_startup()
|
||||
refresh_pw_cache()
|
||||
log.info("[STARTUP] Scheduling autostart task")
|
||||
asyncio.create_task(_autostart_from_settings())
|
||||
|
||||
Reference in New Issue
Block a user