mirror of
https://github.com/TaterTotterson/microWakeWord-Trainer-Nvidia-Docker.git
synced 2026-06-12 20:10:19 -06:00
firmware url fixes
This commit is contained in:
@@ -1037,9 +1037,9 @@
|
|||||||
|
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button id="tabTrainer" class="tabBtn active" type="button">Trainer</button>
|
<button id="tabTrainer" class="tabBtn active" type="button">Trainer</button>
|
||||||
|
<button id="tabFirmware" class="tabBtn" type="button">Firmware</button>
|
||||||
<button id="tabCaptured" class="tabBtn" type="button">Captured Audio</button>
|
<button id="tabCaptured" class="tabBtn" type="button">Captured Audio</button>
|
||||||
<button id="tabSamples" class="tabBtn" type="button">Samples</button>
|
<button id="tabSamples" class="tabBtn" type="button">Samples</button>
|
||||||
<button id="tabFirmware" class="tabBtn" type="button">Firmware</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="trainerView" class="viewStack stack">
|
<div id="trainerView" class="viewStack stack">
|
||||||
@@ -1418,6 +1418,7 @@
|
|||||||
activeView: "trainer",
|
activeView: "trainer",
|
||||||
};
|
};
|
||||||
let firmwareProfileSaveTimer = null;
|
let firmwareProfileSaveTimer = null;
|
||||||
|
let firmwareProfileReloadTimer = null;
|
||||||
|
|
||||||
function setPill(el, text, cls) {
|
function setPill(el, text, cls) {
|
||||||
el.className = "pill " + (cls || "");
|
el.className = "pill " + (cls || "");
|
||||||
@@ -1838,7 +1839,10 @@
|
|||||||
setPill($("firmwareDetectStatus"), `${list.length} detected`, "ok");
|
setPill($("firmwareDetectStatus"), `${list.length} detected`, "ok");
|
||||||
if (!($("firmwareHost").value || "").trim()) {
|
if (!($("firmwareHost").value || "").trim()) {
|
||||||
$("firmwareDeviceSelect").value = "0";
|
$("firmwareDeviceSelect").value = "0";
|
||||||
applySelectedFirmwareDevice();
|
applySelectedFirmwareDevice().catch((error) => {
|
||||||
|
setPill($("firmwareStatus"), "Device settings failed", "warn");
|
||||||
|
console.warn("Device settings load failed", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1850,15 +1854,20 @@
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applySelectedFirmwareDevice() {
|
async function applySelectedFirmwareDevice() {
|
||||||
const indexText = $("firmwareDeviceSelect").value;
|
const indexText = $("firmwareDeviceSelect").value;
|
||||||
if (indexText === "") return;
|
if (indexText === "") return;
|
||||||
const device = uiState.firmware.devices[Number(indexText)];
|
const device = uiState.firmware.devices[Number(indexText)];
|
||||||
if (!device) return;
|
if (!device) return;
|
||||||
|
await flushFirmwareProfileSave();
|
||||||
$("firmwareHost").value = device.host || "";
|
$("firmwareHost").value = device.host || "";
|
||||||
$("firmwarePort").value = device.port || 3232;
|
$("firmwarePort").value = device.port || 3232;
|
||||||
|
setPill($("firmwareStatus"), "Loading device settings...", "warn");
|
||||||
|
const data = await refreshFirmwareTemplates();
|
||||||
|
if (!Array.isArray(data?.warnings) || !data.warnings.length) {
|
||||||
|
setPill($("firmwareStatus"), "Device settings loaded", "ok");
|
||||||
|
}
|
||||||
syncButtons();
|
syncButtons();
|
||||||
scheduleFirmwareProfileSave();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectedFirmwareTemplate() {
|
function selectedFirmwareTemplate() {
|
||||||
@@ -1922,6 +1931,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`).join("");
|
`).join("");
|
||||||
|
syncRenderedWakeWordSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFirmwareField(field) {
|
function renderFirmwareField(field) {
|
||||||
@@ -1987,7 +1997,14 @@
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyWakeWordSelection(select) {
|
function syncRenderedWakeWordSelection() {
|
||||||
|
const select = document.querySelector("select[data-wake-word-select]");
|
||||||
|
if (select && select.value) {
|
||||||
|
applyWakeWordSelection(select, { silent: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyWakeWordSelection(select, options = {}) {
|
||||||
const option = select?.selectedOptions?.[0];
|
const option = select?.selectedOptions?.[0];
|
||||||
if (!option || !select.value) return;
|
if (!option || !select.value) return;
|
||||||
const wakeWordName = option.dataset.wakeWordName || "";
|
const wakeWordName = option.dataset.wakeWordName || "";
|
||||||
@@ -2000,13 +2017,25 @@
|
|||||||
if (urlInput && wakeWordUrl) {
|
if (urlInput && wakeWordUrl) {
|
||||||
urlInput.value = wakeWordUrl;
|
urlInput.value = wakeWordUrl;
|
||||||
}
|
}
|
||||||
setPill($("firmwareStatus"), "Wake word selected", "ok");
|
if (!options.silent) {
|
||||||
scheduleFirmwareProfileSave();
|
setPill($("firmwareStatus"), "Wake word selected", "ok");
|
||||||
|
scheduleFirmwareProfileSave();
|
||||||
|
}
|
||||||
syncButtons();
|
syncButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function firmwareTemplateQuery() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
const host = ($("firmwareHost").value || "").trim();
|
||||||
|
const port = ($("firmwarePort").value || "3232").trim();
|
||||||
|
if (host) params.set("target_host", host);
|
||||||
|
if (port) params.set("target_port", port);
|
||||||
|
const query = params.toString();
|
||||||
|
return query ? `?${query}` : "";
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshFirmwareTemplates() {
|
async function refreshFirmwareTemplates() {
|
||||||
const data = await api("/api/firmware/templates", { method: "GET" });
|
const data = await api(`/api/firmware/templates${firmwareTemplateQuery()}`, { method: "GET" });
|
||||||
renderFirmwareTemplates(data);
|
renderFirmwareTemplates(data);
|
||||||
if (Array.isArray(data.warnings) && data.warnings.length) {
|
if (Array.isArray(data.warnings) && data.warnings.length) {
|
||||||
setPill($("firmwareStatus"), "Template warning", "warn");
|
setPill($("firmwareStatus"), "Template warning", "warn");
|
||||||
@@ -2062,11 +2091,33 @@
|
|||||||
}, 550);
|
}, 550);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleFirmwareProfileReload() {
|
||||||
|
if (firmwareProfileSaveTimer) {
|
||||||
|
clearTimeout(firmwareProfileSaveTimer);
|
||||||
|
firmwareProfileSaveTimer = null;
|
||||||
|
}
|
||||||
|
if (firmwareProfileReloadTimer) {
|
||||||
|
clearTimeout(firmwareProfileReloadTimer);
|
||||||
|
}
|
||||||
|
firmwareProfileReloadTimer = setTimeout(async () => {
|
||||||
|
firmwareProfileReloadTimer = null;
|
||||||
|
try {
|
||||||
|
await refreshFirmwareTemplates();
|
||||||
|
} catch (error) {
|
||||||
|
setPill($("firmwareStatus"), "Device settings failed", "warn");
|
||||||
|
}
|
||||||
|
}, 650);
|
||||||
|
}
|
||||||
|
|
||||||
function flushFirmwareProfileSave(templateKey = null) {
|
function flushFirmwareProfileSave(templateKey = null) {
|
||||||
if (firmwareProfileSaveTimer) {
|
if (firmwareProfileSaveTimer) {
|
||||||
clearTimeout(firmwareProfileSaveTimer);
|
clearTimeout(firmwareProfileSaveTimer);
|
||||||
firmwareProfileSaveTimer = null;
|
firmwareProfileSaveTimer = null;
|
||||||
}
|
}
|
||||||
|
if (firmwareProfileReloadTimer) {
|
||||||
|
clearTimeout(firmwareProfileReloadTimer);
|
||||||
|
firmwareProfileReloadTimer = null;
|
||||||
|
}
|
||||||
return saveFirmwareProfileNow({ templateKey, quiet: true }).catch(() => null);
|
return saveFirmwareProfileNow({ templateKey, quiet: true }).catch(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2500,11 +2551,11 @@
|
|||||||
|
|
||||||
$("firmwareHost").addEventListener("input", () => {
|
$("firmwareHost").addEventListener("input", () => {
|
||||||
syncButtons();
|
syncButtons();
|
||||||
scheduleFirmwareProfileSave();
|
scheduleFirmwareProfileReload();
|
||||||
});
|
});
|
||||||
$("firmwarePort").addEventListener("input", () => {
|
$("firmwarePort").addEventListener("input", () => {
|
||||||
syncButtons();
|
syncButtons();
|
||||||
scheduleFirmwareProfileSave();
|
scheduleFirmwareProfileReload();
|
||||||
});
|
});
|
||||||
$("firmwareTemplate").addEventListener("change", () => {
|
$("firmwareTemplate").addEventListener("change", () => {
|
||||||
flushFirmwareProfileSave(uiState.firmware.activeTemplateKey);
|
flushFirmwareProfileSave(uiState.firmware.activeTemplateKey);
|
||||||
@@ -2526,7 +2577,12 @@
|
|||||||
syncButtons();
|
syncButtons();
|
||||||
scheduleFirmwareProfileSave();
|
scheduleFirmwareProfileSave();
|
||||||
});
|
});
|
||||||
$("firmwareDeviceSelect").addEventListener("change", applySelectedFirmwareDevice);
|
$("firmwareDeviceSelect").addEventListener("change", () => {
|
||||||
|
applySelectedFirmwareDevice().catch((error) => {
|
||||||
|
setPill($("firmwareStatus"), "Device settings failed", "warn");
|
||||||
|
alert("Device settings failed: " + error.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
$("refreshFirmwareBtn").addEventListener("click", async () => {
|
$("refreshFirmwareBtn").addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await refreshFirmwareDevices();
|
await refreshFirmwareDevices();
|
||||||
|
|||||||
@@ -1488,15 +1488,56 @@ def _load_firmware_profiles() -> Dict[str, Dict[str, str]]:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _save_firmware_profile(template_key: str, values: Dict[str, str]) -> None:
|
def _save_firmware_profile(profile_key: str, values: Dict[str, str]) -> None:
|
||||||
FIRMWARE_PROFILE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
FIRMWARE_PROFILE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
profiles = _load_firmware_profiles()
|
profiles = _load_firmware_profiles()
|
||||||
profiles[template_key] = {str(key): str(value) for key, value in values.items() if str(key)}
|
profiles[profile_key] = {str(key): str(value) for key, value in values.items() if str(key)}
|
||||||
FIRMWARE_PROFILE_FILE.write_text(json.dumps(profiles, indent=2, sort_keys=True), encoding="utf-8")
|
FIRMWARE_PROFILE_FILE.write_text(json.dumps(profiles, indent=2, sort_keys=True), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def _normalize_firmware_profile_update(template_key: str, values: Dict[str, Any]) -> Dict[str, str]:
|
def _firmware_profile_target(raw_host: Any = "", raw_port: Any = "") -> tuple[str, str]:
|
||||||
ctx = _load_firmware_template_context(template_key)
|
host = str(raw_host or "").strip()
|
||||||
|
port = str(raw_port or "").strip()
|
||||||
|
if "://" in host:
|
||||||
|
parsed = urlparse(host)
|
||||||
|
host = parsed.hostname or ""
|
||||||
|
if not port and parsed.port:
|
||||||
|
port = str(parsed.port)
|
||||||
|
host = host.strip().strip("/")
|
||||||
|
if host.count(":") == 1 and not port:
|
||||||
|
maybe_host, maybe_port = host.rsplit(":", 1)
|
||||||
|
if maybe_port.isdigit():
|
||||||
|
host = maybe_host
|
||||||
|
port = maybe_port
|
||||||
|
host = host.strip("[]").strip().lower()
|
||||||
|
if not host:
|
||||||
|
return "", ""
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
parsed_port = int(port or FIRMWARE_DEFAULT_OTA_PORT)
|
||||||
|
if parsed_port == 6053:
|
||||||
|
parsed_port = FIRMWARE_DEFAULT_OTA_PORT
|
||||||
|
port = str(parsed_port)
|
||||||
|
if not port:
|
||||||
|
port = str(FIRMWARE_DEFAULT_OTA_PORT)
|
||||||
|
return host, port
|
||||||
|
|
||||||
|
|
||||||
|
def _firmware_profile_key_for_target(raw_host: Any = "", raw_port: Any = "") -> str:
|
||||||
|
host, port = _firmware_profile_target(raw_host, raw_port)
|
||||||
|
return f"device:{host}:{port}" if host else ""
|
||||||
|
|
||||||
|
|
||||||
|
def _load_firmware_profile(template_key: str, profile_key: str = "") -> Dict[str, str]:
|
||||||
|
profiles = _load_firmware_profiles()
|
||||||
|
profile = profiles.get(profile_key) if profile_key else None
|
||||||
|
if isinstance(profile, dict):
|
||||||
|
return dict(profile)
|
||||||
|
legacy = profiles.get(template_key)
|
||||||
|
return dict(legacy) if isinstance(legacy, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_firmware_profile_update(template_key: str, values: Dict[str, Any], profile_key: str = "") -> Dict[str, str]:
|
||||||
|
ctx = _load_firmware_template_context(template_key, profile_key)
|
||||||
spec = ctx["spec"]
|
spec = ctx["spec"]
|
||||||
profile = ctx.get("profile") if isinstance(ctx.get("profile"), dict) else {}
|
profile = ctx.get("profile") if isinstance(ctx.get("profile"), dict) else {}
|
||||||
substitutions = ctx["substitutions"]
|
substitutions = ctx["substitutions"]
|
||||||
@@ -1527,6 +1568,12 @@ def _normalize_firmware_profile_update(template_key: str, values: Dict[str, Any]
|
|||||||
elif key_text not in normalized:
|
elif key_text not in normalized:
|
||||||
normalized[key_text] = "true" if str(default).lower() == "true" else "false"
|
normalized[key_text] = "true" if str(default).lower() == "true" else "false"
|
||||||
continue
|
continue
|
||||||
|
if key_text == "wake_word_model_url":
|
||||||
|
if key_text in values:
|
||||||
|
normalized[key_text] = _local_trained_wake_word_url(values.get(key_text))
|
||||||
|
elif key_text not in normalized:
|
||||||
|
normalized[key_text] = ""
|
||||||
|
continue
|
||||||
if key_text in values:
|
if key_text in values:
|
||||||
normalized[key_text] = str(values.get(key_text) or "").strip()
|
normalized[key_text] = str(values.get(key_text) or "").strip()
|
||||||
elif key_text not in normalized:
|
elif key_text not in normalized:
|
||||||
@@ -1548,7 +1595,38 @@ def _normalize_firmware_profile_update(template_key: str, values: Dict[str, Any]
|
|||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
def _load_firmware_template_context(template_key: str) -> Dict[str, Any]:
|
def _local_trained_wake_word_url(value: Any) -> str:
|
||||||
|
url = str(value or "").strip()
|
||||||
|
return url if "/api/trained_wake_words/" in url else ""
|
||||||
|
|
||||||
|
|
||||||
|
def _selected_trained_wake_word(
|
||||||
|
trained_wake_words: List[Dict[str, Any]],
|
||||||
|
profile: Dict[str, Any],
|
||||||
|
substitutions: Dict[str, Any],
|
||||||
|
) -> Dict[str, Any] | None:
|
||||||
|
if not trained_wake_words:
|
||||||
|
return None
|
||||||
|
|
||||||
|
saved_choice = str(profile.get("wake_word_choice") or "").strip()
|
||||||
|
current_wake_word_name = str(
|
||||||
|
profile.get("wake_word_name") or _template_default_string(substitutions.get("wake_word_name"))
|
||||||
|
).strip()
|
||||||
|
current_wake_word_url = str(profile.get("wake_word_model_url") or "").strip()
|
||||||
|
|
||||||
|
def match(predicate):
|
||||||
|
return next((row for row in trained_wake_words if predicate(row)), None)
|
||||||
|
|
||||||
|
return (
|
||||||
|
match(lambda row: str(row.get("key") or "") == saved_choice)
|
||||||
|
or match(lambda row: str(row.get("json_url") or "") == current_wake_word_url)
|
||||||
|
or match(lambda row: str(row.get("model_url") or "") == current_wake_word_url)
|
||||||
|
or match(lambda row: str(row.get("wake_word_name") or "") == current_wake_word_name)
|
||||||
|
or trained_wake_words[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_firmware_template_context(template_key: str, profile_key: str = "") -> Dict[str, Any]:
|
||||||
spec = _firmware_template_spec(template_key)
|
spec = _firmware_template_spec(template_key)
|
||||||
raw_text, source_label = _load_firmware_template_text(spec)
|
raw_text, source_label = _load_firmware_template_text(spec)
|
||||||
parsed = yaml.load(raw_text, Loader=_FirmwareYamlLoader)
|
parsed = yaml.load(raw_text, Loader=_FirmwareYamlLoader)
|
||||||
@@ -1564,12 +1642,12 @@ def _load_firmware_template_context(template_key: str) -> Dict[str, Any]:
|
|||||||
"template_doc": parsed,
|
"template_doc": parsed,
|
||||||
"substitutions": dict(substitutions),
|
"substitutions": dict(substitutions),
|
||||||
"sections": _extract_substitution_sections(raw_text),
|
"sections": _extract_substitution_sections(raw_text),
|
||||||
"profile": _load_firmware_profiles().get(str(spec.get("key") or ""), {}),
|
"profile": _load_firmware_profile(str(spec.get("key") or ""), profile_key),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _firmware_template_fields(template_key: str, base_url: str = "") -> List[Dict[str, Any]]:
|
def _firmware_template_fields(template_key: str, base_url: str = "", profile_key: str = "") -> List[Dict[str, Any]]:
|
||||||
ctx = _load_firmware_template_context(template_key)
|
ctx = _load_firmware_template_context(template_key, profile_key)
|
||||||
spec = ctx["spec"]
|
spec = ctx["spec"]
|
||||||
profile = ctx.get("profile") if isinstance(ctx.get("profile"), dict) else {}
|
profile = ctx.get("profile") if isinstance(ctx.get("profile"), dict) else {}
|
||||||
fields: List[Dict[str, Any]] = []
|
fields: List[Dict[str, Any]] = []
|
||||||
@@ -1579,17 +1657,8 @@ def _firmware_template_fields(template_key: str, base_url: str = "") -> List[Dic
|
|||||||
fixed_keys.add(identity_key)
|
fixed_keys.add(identity_key)
|
||||||
hidden_keys = {"ha_voice_ip"} | set(spec.get("auto_keys") or set())
|
hidden_keys = {"ha_voice_ip"} | set(spec.get("auto_keys") or set())
|
||||||
trained_wake_words = _list_trained_wake_words(base_url)
|
trained_wake_words = _list_trained_wake_words(base_url)
|
||||||
current_wake_word_name = str(
|
selected_wake_word_row = _selected_trained_wake_word(trained_wake_words, profile, ctx["substitutions"])
|
||||||
profile.get("wake_word_name") or _template_default_string(ctx["substitutions"].get("wake_word_name"))
|
selected_wake_word = str(selected_wake_word_row.get("key") or "") if selected_wake_word_row else ""
|
||||||
).strip()
|
|
||||||
saved_wake_word_choice = str(profile.get("wake_word_choice") or "").strip()
|
|
||||||
selected_wake_word = next(
|
|
||||||
(row["key"] for row in trained_wake_words if row.get("key") == saved_wake_word_choice),
|
|
||||||
"",
|
|
||||||
) or next(
|
|
||||||
(row["key"] for row in trained_wake_words if row.get("wake_word_name") == current_wake_word_name),
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
wake_picker_added = False
|
wake_picker_added = False
|
||||||
|
|
||||||
for key, raw_value in ctx["substitutions"].items():
|
for key, raw_value in ctx["substitutions"].items():
|
||||||
@@ -1640,8 +1709,12 @@ def _firmware_template_fields(template_key: str, base_url: str = "") -> List[Dic
|
|||||||
placeholder = "Your Wi-Fi SSID"
|
placeholder = "Your Wi-Fi SSID"
|
||||||
description = "Required before build + flash."
|
description = "Required before build + flash."
|
||||||
elif key_text == "wake_word_model_url":
|
elif key_text == "wake_word_model_url":
|
||||||
placeholder = "https://.../wake_word.json"
|
value = str(selected_wake_word_row.get("json_url") or "") if selected_wake_word_row else ""
|
||||||
|
placeholder = "Train or select a local wake word first"
|
||||||
|
description = "Filled from the local trained wake-word picker."
|
||||||
elif key_text == "wake_word_name":
|
elif key_text == "wake_word_name":
|
||||||
|
if selected_wake_word_row:
|
||||||
|
value = str(selected_wake_word_row.get("wake_word_name") or selected_wake_word_row.get("key") or "")
|
||||||
placeholder = "hey_tater"
|
placeholder = "hey_tater"
|
||||||
section = ctx["sections"].get(key_text) or "Firmware"
|
section = ctx["sections"].get(key_text) or "Firmware"
|
||||||
if key_text in {"wake_word_name", "wake_word_model_url"}:
|
if key_text in {"wake_word_name", "wake_word_model_url"}:
|
||||||
@@ -1678,8 +1751,15 @@ def _esphome_pythonpath() -> str:
|
|||||||
return os.pathsep.join(paths)
|
return os.pathsep.join(paths)
|
||||||
|
|
||||||
|
|
||||||
def _render_firmware_config(template_key: str, values: Dict[str, Any], host: str, session_id: str) -> tuple[Path, Dict[str, str]]:
|
def _render_firmware_config(
|
||||||
ctx = _load_firmware_template_context(template_key)
|
template_key: str,
|
||||||
|
values: Dict[str, Any],
|
||||||
|
host: str,
|
||||||
|
session_id: str,
|
||||||
|
port: Any = None,
|
||||||
|
) -> tuple[Path, Dict[str, str]]:
|
||||||
|
profile_key = _firmware_profile_key_for_target(host, port)
|
||||||
|
ctx = _load_firmware_template_context(template_key, profile_key)
|
||||||
spec = ctx["spec"]
|
spec = ctx["spec"]
|
||||||
profile = ctx.get("profile") if isinstance(ctx.get("profile"), dict) else {}
|
profile = ctx.get("profile") if isinstance(ctx.get("profile"), dict) else {}
|
||||||
substitutions = ctx["substitutions"]
|
substitutions = ctx["substitutions"]
|
||||||
@@ -1702,6 +1782,8 @@ def _render_firmware_config(template_key: str, values: Dict[str, Any], host: str
|
|||||||
normalized[key_text] = "true" if bool(raw_value) else "false"
|
normalized[key_text] = "true" if bool(raw_value) else "false"
|
||||||
elif key_text == "ha_voice_ip":
|
elif key_text == "ha_voice_ip":
|
||||||
normalized[key_text] = host
|
normalized[key_text] = host
|
||||||
|
elif key_text == "wake_word_model_url":
|
||||||
|
normalized[key_text] = _local_trained_wake_word_url(raw_value)
|
||||||
else:
|
else:
|
||||||
normalized[key_text] = str(raw_value if raw_value is not None else "").strip() or _template_default_string(
|
normalized[key_text] = str(raw_value if raw_value is not None else "").strip() or _template_default_string(
|
||||||
substitutions.get(key_text)
|
substitutions.get(key_text)
|
||||||
@@ -1714,6 +1796,8 @@ def _render_firmware_config(template_key: str, values: Dict[str, Any], host: str
|
|||||||
missing.append("Wi-Fi password")
|
missing.append("Wi-Fi password")
|
||||||
if not host:
|
if not host:
|
||||||
missing.append("device IP or hostname")
|
missing.append("device IP or hostname")
|
||||||
|
if "wake_word_model_url" in substitutions and not normalized.get("wake_word_model_url"):
|
||||||
|
missing.append("local trained wake word")
|
||||||
if missing:
|
if missing:
|
||||||
raise RuntimeError(f"Missing required firmware values: {', '.join(missing)}.")
|
raise RuntimeError(f"Missing required firmware values: {', '.join(missing)}.")
|
||||||
|
|
||||||
@@ -1728,7 +1812,11 @@ def _render_firmware_config(template_key: str, values: Dict[str, Any], host: str
|
|||||||
session_dir.mkdir(parents=True, exist_ok=True)
|
session_dir.mkdir(parents=True, exist_ok=True)
|
||||||
config_path = session_dir / f"{str(spec.get('key') or template_key)}.yaml"
|
config_path = session_dir / f"{str(spec.get('key') or template_key)}.yaml"
|
||||||
config_path.write_text(yaml.dump(config, Dumper=_FirmwareYamlDumper, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
config_path.write_text(yaml.dump(config, Dumper=_FirmwareYamlDumper, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
||||||
_save_firmware_profile(str(spec.get("key") or template_key), normalized)
|
normalized_host, normalized_port = _firmware_profile_target(host, port)
|
||||||
|
if normalized_host:
|
||||||
|
normalized["__target_host"] = normalized_host
|
||||||
|
normalized["__target_port"] = normalized_port
|
||||||
|
_save_firmware_profile(profile_key or str(spec.get("key") or template_key), normalized)
|
||||||
return config_path, normalized
|
return config_path, normalized
|
||||||
|
|
||||||
|
|
||||||
@@ -1947,7 +2035,7 @@ def _run_firmware_build_flash_background(session_id: str):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config_path, _normalized = _render_firmware_config(template_key, values, host, session_id)
|
config_path, _normalized = _render_firmware_config(template_key, values, host, session_id, port)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_append_firmware_log(session_id, f"✗ Failed to prepare firmware config: {exc}")
|
_append_firmware_log(session_id, f"✗ Failed to prepare firmware config: {exc}")
|
||||||
with FIRMWARE_LOCK:
|
with FIRMWARE_LOCK:
|
||||||
@@ -2518,28 +2606,30 @@ def firmware_devices():
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/api/firmware/templates")
|
@app.get("/api/firmware/templates")
|
||||||
def firmware_templates(request: Request):
|
def firmware_templates(request: Request, target_host: str = "", target_port: str = ""):
|
||||||
templates = []
|
templates = []
|
||||||
warnings = []
|
warnings = []
|
||||||
base_url = _request_base_url(request)
|
base_url = _request_base_url(request)
|
||||||
wake_words = _list_trained_wake_words(base_url)
|
wake_words = _list_trained_wake_words(base_url)
|
||||||
profiles = _load_firmware_profiles()
|
profile_key = _firmware_profile_key_for_target(target_host, target_port)
|
||||||
|
selected_host, selected_port = _firmware_profile_target(target_host, target_port)
|
||||||
for spec in FIRMWARE_TEMPLATE_SPECS:
|
for spec in FIRMWARE_TEMPLATE_SPECS:
|
||||||
key = str(spec.get("key") or "")
|
key = str(spec.get("key") or "")
|
||||||
profile = profiles.get(key, {})
|
profile = _load_firmware_profile(key, profile_key)
|
||||||
target_port = str(profile.get("__target_port") or "")
|
row_target_host = selected_host or str(profile.get("__target_host") or "")
|
||||||
if target_port == "6053":
|
row_target_port = selected_port or str(profile.get("__target_port") or "")
|
||||||
target_port = str(FIRMWARE_DEFAULT_OTA_PORT)
|
if row_target_port == "6053":
|
||||||
|
row_target_port = str(FIRMWARE_DEFAULT_OTA_PORT)
|
||||||
row = {
|
row = {
|
||||||
"value": key,
|
"value": key,
|
||||||
"label": str(spec.get("label") or key),
|
"label": str(spec.get("label") or key),
|
||||||
"source_url": _firmware_raw_url(str(spec.get("path") or "")),
|
"source_url": _firmware_raw_url(str(spec.get("path") or "")),
|
||||||
"target_host": str(profile.get("__target_host") or ""),
|
"target_host": row_target_host,
|
||||||
"target_port": target_port,
|
"target_port": row_target_port,
|
||||||
"fields": [],
|
"fields": [],
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
row["fields"] = _firmware_template_fields(key, base_url)
|
row["fields"] = _firmware_template_fields(key, base_url, profile_key)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
warnings.append(f"{row['label']}: {exc}")
|
warnings.append(f"{row['label']}: {exc}")
|
||||||
templates.append(row)
|
templates.append(row)
|
||||||
@@ -2559,11 +2649,12 @@ def firmware_profile(payload: Dict[str, Any]):
|
|||||||
template_key = str(body.get("template_key") or "").strip()
|
template_key = str(body.get("template_key") or "").strip()
|
||||||
_firmware_template_spec(template_key)
|
_firmware_template_spec(template_key)
|
||||||
values = body.get("values") if isinstance(body.get("values"), dict) else {}
|
values = body.get("values") if isinstance(body.get("values"), dict) else {}
|
||||||
saved = _normalize_firmware_profile_update(template_key, values)
|
profile_key = _firmware_profile_key_for_target(values.get("__target_host"), values.get("__target_port"))
|
||||||
_save_firmware_profile(template_key, saved)
|
saved = _normalize_firmware_profile_update(template_key, values, profile_key)
|
||||||
|
_save_firmware_profile(profile_key or template_key, saved)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse({"ok": False, "error": str(e)}, status_code=400)
|
return JSONResponse({"ok": False, "error": str(e)}, status_code=400)
|
||||||
return {"ok": True, "template_key": template_key, "saved_fields": sorted(saved.keys())}
|
return {"ok": True, "template_key": template_key, "profile_key": profile_key or template_key, "saved_fields": sorted(saved.keys())}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/trained_wake_words/catalog")
|
@app.get("/api/trained_wake_words/catalog")
|
||||||
|
|||||||
Reference in New Issue
Block a user