Add multi-decoder split and pit timing support

This commit is contained in:
larssand
2026-08-25 19:45:40 +02:00
parent 6f02e955a5
commit 8ea8b3a84e
20 changed files with 876 additions and 147 deletions

View File

@@ -43,7 +43,10 @@ export function renderSettings(deps) {
} = deps;
const ammcStatus = ammc.status || {};
const ammcOutput = Array.isArray(ammcStatus.lastOutput) ? ammcStatus.lastOutput : [];
const suggestedWsUrl = getManagedWsUrl();
const suggestedWsUrl = getManagedWsUrl("main");
const suggestedSplitWsUrl = getManagedWsUrl("split");
const suggestedPitEntryWsUrl = getManagedWsUrl("pit_entry");
const suggestedPitExitWsUrl = getManagedWsUrl("pit_exit");
const running = Boolean(ammcStatus.running);
const speechVoices = getSpeechVoices();
const speechVoiceOptions = speechVoices
@@ -54,7 +57,13 @@ export function renderSettings(deps) {
<section class="panel">
<div class="panel-header"><h3>${t("settings.decoder")}</h3></div>
<form id="settingsForm" class="panel-body form-grid cols-3 settings-grid">
<input name="wsUrl" value="${escapeHtml(state.settings.wsUrl)}" placeholder="ws://127.0.0.1:9000" />
<p class="hint form-grid-full">${t("settings.decoder_roles_hint")}</p>
<label><span>${t("settings.main_ws_url")}</span><input name="wsUrl" value="${escapeHtml(state.settings.wsUrl)}" placeholder="ws://127.0.0.1:9000" /></label>
<label class="toggle"><input type="checkbox" name="splitDecoderEnabled" ${state.settings.splitDecoderEnabled ? "checked" : ""} /><span>${t("settings.enable_split_decoder")}</span></label>
<label><span>${t("settings.split_ws_url")}</span><input name="splitWsUrl" value="${escapeHtml(state.settings.splitWsUrl || "")}" placeholder="ws://127.0.0.1:9001" /></label>
<label class="toggle"><input type="checkbox" name="pitDecoderEnabled" ${state.settings.pitDecoderEnabled ? "checked" : ""} /><span>${t("settings.enable_pit_decoders")}</span></label>
<label><span>${t("settings.pit_entry_ws_url")}</span><input name="pitEntryWsUrl" value="${escapeHtml(state.settings.pitEntryWsUrl || "")}" placeholder="ws://127.0.0.1:9002" /></label>
<label><span>${t("settings.pit_exit_ws_url")}</span><input name="pitExitWsUrl" value="${escapeHtml(state.settings.pitExitWsUrl || "")}" placeholder="ws://127.0.0.1:9003" /></label>
<input name="backendUrl" value="${escapeHtml(
state.settings.backendUrl || getDefaultBackendUrl()
)}" placeholder="http://127.0.0.1:8081" />
@@ -217,6 +226,7 @@ export function renderSettings(deps) {
<section class="panel mt-16">
<div class="panel-header"><h3>${t("settings.managed_ammc")}</h3></div>
<form id="ammcForm" class="panel-body form-grid cols-2 settings-grid">
<p class="hint form-grid-full">${t("settings.managed_channels_hint")}</p>
<label class="toggle">
<input type="checkbox" name="managedEnabled" ${ammc.config.managedEnabled ? "checked" : ""} />
<span>${t("settings.enable_managed")}</span>
@@ -225,11 +235,21 @@ export function renderSettings(deps) {
<input type="checkbox" name="autoStart" ${ammc.config.autoStart ? "checked" : ""} />
<span>${t("settings.auto_start_ammc")}</span>
</label>
<input name="decoderHost" value="${escapeHtml(ammc.config.decoderHost || "")}" placeholder="${t("settings.decoder_host")}" />
<input name="wsPort" value="${escapeHtml(String(ammc.config.wsPort || 9000))}" placeholder="9000" />
<input name="executablePath" value="${escapeHtml(ammc.config.executablePath || "")}" placeholder="${t("settings.executable_path")}" />
<input name="workingDirectory" value="${escapeHtml(ammc.config.workingDirectory || "")}" placeholder="${t("settings.working_dir")}" />
<input name="extraArgs" value="${escapeHtml(ammc.config.extraArgs || "")}" placeholder="${t("settings.extra_args")}" />
<label><span>${t("timing.main_decoder")} - ${t("settings.decoder_host")}</span><input name="decoderHost" value="${escapeHtml(ammc.config.decoderHost || "")}" placeholder="192.168.1.10" /></label>
<label><span>${t("timing.main_decoder")} - ${t("settings.ws_port")}</span><input name="wsPort" value="${escapeHtml(String(ammc.config.wsPort || 9000))}" placeholder="9000" /></label>
<label class="toggle"><input type="checkbox" name="splitEnabled" ${ammc.config.splitEnabled ? "checked" : ""} /><span>${t("settings.enable_split_decoder")}</span></label>
<span></span>
<label><span>${t("settings.split_decoder_host")}</span><input name="splitDecoderHost" value="${escapeHtml(ammc.config.splitDecoderHost || "")}" placeholder="192.168.1.11" /></label>
<label><span>${t("timing.split_decoder")} - ${t("settings.ws_port")}</span><input name="splitWsPort" value="${escapeHtml(String(ammc.config.splitWsPort || 9001))}" placeholder="9001" /></label>
<label class="toggle"><input type="checkbox" name="pitEnabled" ${ammc.config.pitEnabled ? "checked" : ""} /><span>${t("settings.enable_pit_decoders")}</span></label>
<span></span>
<label><span>${t("settings.pit_entry_decoder_host")}</span><input name="pitEntryDecoderHost" value="${escapeHtml(ammc.config.pitEntryDecoderHost || "")}" placeholder="192.168.1.12" /></label>
<label><span>${t("timing.pit_entry_decoder")} - ${t("settings.ws_port")}</span><input name="pitEntryWsPort" value="${escapeHtml(String(ammc.config.pitEntryWsPort || 9002))}" placeholder="9002" /></label>
<label><span>${t("settings.pit_exit_decoder_host")}</span><input name="pitExitDecoderHost" value="${escapeHtml(ammc.config.pitExitDecoderHost || "")}" placeholder="192.168.1.13" /></label>
<label><span>${t("timing.pit_exit_decoder")} - ${t("settings.ws_port")}</span><input name="pitExitWsPort" value="${escapeHtml(String(ammc.config.pitExitWsPort || 9003))}" placeholder="9003" /></label>
<label><span>${t("settings.executable_path")}</span><input name="executablePath" value="${escapeHtml(ammc.config.executablePath || "")}" placeholder="${t("settings.executable_path")}" /></label>
<label><span>${t("settings.working_dir")}</span><input name="workingDirectory" value="${escapeHtml(ammc.config.workingDirectory || "")}" placeholder="${t("settings.working_dir")}" /></label>
<label><span>${t("settings.extra_args")}</span><input name="extraArgs" value="${escapeHtml(ammc.config.extraArgs || "")}" placeholder="${t("settings.extra_args")}" /></label>
<div class="actions">
<button class="btn btn-primary" type="submit">${t("settings.save_ammc")}</button>
<button id="ammcRefresh" class="btn" type="button">${t("settings.refresh_ammc")}</button>
@@ -250,12 +270,14 @@ export function renderSettings(deps) {
<p>${ammcStatus.executableExists ? t("settings.executable_found") : t("settings.executable_missing")}</p>
<p>${t("settings.decoder_host")}: <strong>${escapeHtml(String(ammc.config.decoderHost || "-"))}</strong></p>
<p>${t("settings.ws_port")}: <strong>${escapeHtml(String(ammc.config.wsPort || 9000))}</strong></p>
${ammc.config.splitEnabled ? `<p>${t("timing.split_decoder")}: <strong>${ammcStatus.split?.running ? t("settings.running") : t("settings.stopped")}</strong> • ${escapeHtml(String(ammc.config.splitDecoderHost || "-"))}:${escapeHtml(String(ammc.config.splitWsPort || 9001))}${ammcStatus.split?.lastError ? ` • <span class="error">${escapeHtml(ammcStatus.split.lastError)}</span>` : ""}</p>` : ""}
${ammc.config.pitEnabled ? `<p>${t("timing.pit_entry_decoder")}: <strong>${ammcStatus.pitEntry?.running ? t("settings.running") : t("settings.stopped")}</strong> • ${escapeHtml(String(ammc.config.pitEntryDecoderHost || "-"))}:${escapeHtml(String(ammc.config.pitEntryWsPort || 9002))}${ammcStatus.pitEntry?.lastError ? ` • <span class="error">${escapeHtml(ammcStatus.pitEntry.lastError)}</span>` : ""}</p><p>${t("timing.pit_exit_decoder")}: <strong>${ammcStatus.pitExit?.running ? t("settings.running") : t("settings.stopped")}</strong> • ${escapeHtml(String(ammc.config.pitExitDecoderHost || "-"))}:${escapeHtml(String(ammc.config.pitExitWsPort || 9003))}${ammcStatus.pitExit?.lastError ? ` • <span class="error">${escapeHtml(ammcStatus.pitExit.lastError)}</span>` : ""}</p>` : ""}
<p>${t("settings.last_error")}: ${escapeHtml(ammc.lastError || ammcStatus.lastError || "-")}</p>
<p>${t("settings.backend_url")}: <strong>${escapeHtml(getBackendUrl())}</strong></p>
<p>WebSocket URL: <strong>${escapeHtml(suggestedWsUrl)}</strong></p>
<pre class="log-box">${escapeHtml(
ammcOutput.length
? ammcOutput.map((entry) => `[${new Date(entry.ts).toLocaleTimeString()}] ${entry.stream}: ${entry.line}`).join("\n")
? ammcOutput.map((entry) => `[${new Date(entry.ts).toLocaleTimeString()}] ${entry.role || "main"}/${entry.stream}: ${entry.line}`).join("\n")
: "-"
)}</pre>
</div>
@@ -328,6 +350,11 @@ export function renderSettings(deps) {
e.preventDefault();
const form = new FormData(e.currentTarget);
state.settings.wsUrl = String(form.get("wsUrl") || "").trim();
state.settings.splitDecoderEnabled = form.get("splitDecoderEnabled") === "on";
state.settings.splitWsUrl = String(form.get("splitWsUrl") || "").trim();
state.settings.pitDecoderEnabled = form.get("pitDecoderEnabled") === "on";
state.settings.pitEntryWsUrl = String(form.get("pitEntryWsUrl") || "").trim();
state.settings.pitExitWsUrl = String(form.get("pitExitWsUrl") || "").trim();
state.settings.backendUrl = String(form.get("backendUrl") || "").trim();
state.settings.autoReconnect = form.get("autoReconnect") === "on";
applyAudioFormValues();
@@ -543,6 +570,14 @@ export function renderSettings(deps) {
autoStart: form.get("autoStart") === "on",
decoderHost: String(form.get("decoderHost") || "").trim(),
wsPort: Number(form.get("wsPort") || 9000),
splitEnabled: form.get("splitEnabled") === "on",
splitDecoderHost: String(form.get("splitDecoderHost") || "").trim(),
splitWsPort: Number(form.get("splitWsPort") || 9001),
pitEnabled: form.get("pitEnabled") === "on",
pitEntryDecoderHost: String(form.get("pitEntryDecoderHost") || "").trim(),
pitEntryWsPort: Number(form.get("pitEntryWsPort") || 9002),
pitExitDecoderHost: String(form.get("pitExitDecoderHost") || "").trim(),
pitExitWsPort: Number(form.get("pitExitWsPort") || 9003),
executablePath: String(form.get("executablePath") || "").trim(),
workingDirectory: String(form.get("workingDirectory") || "").trim(),
extraArgs: String(form.get("extraArgs") || "").trim(),
@@ -565,6 +600,14 @@ export function renderSettings(deps) {
autoStart: form.get("autoStart") === "on",
decoderHost: String(form.get("decoderHost") || "").trim(),
wsPort: Number(form.get("wsPort") || 9000),
splitEnabled: form.get("splitEnabled") === "on",
splitDecoderHost: String(form.get("splitDecoderHost") || "").trim(),
splitWsPort: Number(form.get("splitWsPort") || 9001),
pitEnabled: form.get("pitEnabled") === "on",
pitEntryDecoderHost: String(form.get("pitEntryDecoderHost") || "").trim(),
pitEntryWsPort: Number(form.get("pitEntryWsPort") || 9002),
pitExitDecoderHost: String(form.get("pitExitDecoderHost") || "").trim(),
pitExitWsPort: Number(form.get("pitExitWsPort") || 9003),
executablePath: String(form.get("executablePath") || "").trim(),
workingDirectory: String(form.get("workingDirectory") || "").trim(),
extraArgs: String(form.get("extraArgs") || "").trim(),
@@ -580,7 +623,12 @@ export function renderSettings(deps) {
});
document.getElementById("ammcUseServerWs")?.addEventListener("click", () => {
state.settings.wsUrl = getManagedWsUrl();
state.settings.wsUrl = suggestedWsUrl;
state.settings.splitDecoderEnabled = Boolean(ammc.config.splitEnabled);
state.settings.splitWsUrl = suggestedSplitWsUrl;
state.settings.pitDecoderEnabled = Boolean(ammc.config.pitEnabled);
state.settings.pitEntryWsUrl = suggestedPitEntryWsUrl;
state.settings.pitExitWsUrl = suggestedPitExitWsUrl;
saveState();
renderView();
});