[fix](trx-frontend-http): show scheduler interleave timing

Replace the misleading scheduler task countdown with the actual time-span interleave switch timing in the main controls row.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-13 01:08:31 +01:00
parent be4844c710
commit 4cac91e36a
3 changed files with 78 additions and 19 deletions
@@ -267,7 +267,7 @@
<div class="scheduler-release-wrap">
<button id="scheduler-release-btn" type="button">Release to Scheduler</button>
<div id="scheduler-release-status" class="scheduler-release-status">Scheduler is controlling the rig.</div>
<div id="scheduler-cycle-status" class="scheduler-cycle-status">Next scheduler cycle in 30s.</div>
<div id="scheduler-cycle-status" class="scheduler-cycle-status">Interleaving: --</div>
</div>
</div>
</div>
@@ -15,6 +15,7 @@
let currentConfig = null;
let bookmarkList = []; // [{id, name, freq_hz, mode}, ...]
let statusInterval = null;
let interleaveTicker = null;
// -------------------------------------------------------------------------
// Init
@@ -24,6 +25,7 @@
currentRigId = rigId || null;
if (currentRigId) loadScheduler();
startStatusPolling();
startInterleaveTicker();
}
function destroyScheduler() {
@@ -31,6 +33,10 @@
clearInterval(statusInterval);
statusInterval = null;
}
if (interleaveTicker) {
clearInterval(interleaveTicker);
interleaveTicker = null;
}
}
// -------------------------------------------------------------------------
@@ -40,6 +46,7 @@
const nextRigId = rigId || null;
if (nextRigId === currentRigId) return;
currentRigId = nextRigId;
renderSchedulerInterleaveStatus();
if (!currentRigId) return;
loadScheduler();
pollStatus();
@@ -104,9 +111,11 @@
bookmarkList = Array.isArray(bms) ? bms : [];
populateTsBookmarkSelect();
renderScheduler();
renderSchedulerInterleaveStatus();
})
.catch(function (e) {
console.error("scheduler load failed", e);
renderSchedulerInterleaveStatus();
});
}
@@ -119,6 +128,74 @@
pollStatus();
}
function startInterleaveTicker() {
if (interleaveTicker) clearInterval(interleaveTicker);
interleaveTicker = setInterval(renderSchedulerInterleaveStatus, 1000);
renderSchedulerInterleaveStatus();
}
function schedulerUtcSeconds() {
return Math.floor(Date.now() / 1000);
}
function schedulerUtcMinuteInfo() {
const secs = schedulerUtcSeconds();
const secsIntoDay = ((secs % 86400) + 86400) % 86400;
return {
minuteOfDay: Math.floor(secsIntoDay / 60),
secondOfMinute: secsIntoDay % 60,
};
}
function schedulerEntryIsActive(entry, nowMin) {
const start = Number(entry && entry.start_min);
const end = Number(entry && entry.end_min);
if (!Number.isFinite(start) || !Number.isFinite(end)) return false;
if (start === end) return true;
if (start < end) return nowMin >= start && nowMin < end;
return nowMin >= start || nowMin < end;
}
function schedulerInterleaveSummary(config) {
if (!config || config.mode !== "time_span") return "Interleaving: off";
const entries = Array.isArray(config.entries) ? config.entries : [];
const minuteInfo = schedulerUtcMinuteInfo();
const nowMin = minuteInfo.minuteOfDay;
const active = entries.filter(function (entry) {
return schedulerEntryIsActive(entry, nowMin);
});
if (active.length <= 1) return "Interleaving: off";
const defaultInterleave = Number(config.interleave_min);
const durations = active.map(function (entry) {
const own = Number(entry && entry.interleave_min);
if (Number.isFinite(own) && own > 0) return Math.floor(own);
if (Number.isFinite(defaultInterleave) && defaultInterleave > 0) return Math.floor(defaultInterleave);
return 0;
});
const cycleMin = durations.reduce(function (sum, value) { return sum + value; }, 0);
if (!(cycleMin > 0)) return "Interleaving: off";
const posMin = minuteInfo.minuteOfDay % cycleMin;
let cumulative = 0;
let currentDuration = 0;
for (let i = 0; i < durations.length; i += 1) {
cumulative += durations[i];
if (posMin < cumulative) {
currentDuration = durations[i];
break;
}
}
if (!(currentDuration > 0)) return "Interleaving: off";
const remainingMin = cumulative - posMin;
const remainingSec = Math.max(1, (remainingMin * 60) - minuteInfo.secondOfMinute);
return "Interleaving: next switch in " + remainingSec + "s (" + cycleMin + " min cycle)";
}
function renderSchedulerInterleaveStatus() {
const el = document.getElementById("scheduler-cycle-status");
if (!el) return;
el.textContent = schedulerInterleaveSummary(currentConfig);
}
function pollStatus() {
const rig = currentRigId;
if (!rig) return;
@@ -13,18 +13,6 @@ let vchanChannels = [];
let vchanActiveId = null;
let schedulerReleaseState = null;
let schedulerReleasePollTimer = null;
let schedulerCycleCountdownTimer = null;
function schedulerNextCycleSeconds() {
const periodMs = 30000;
const remMs = periodMs - (Date.now() % periodMs);
const secs = Math.ceil(remMs / 1000);
return Math.max(1, Math.min(30, secs));
}
function schedulerCycleSummaryText() {
return `Next scheduler cycle in ${schedulerNextCycleSeconds()}s.`;
}
function vchanFmtFreq(hz) {
if (!Number.isFinite(hz) || hz <= 0) return "--";
@@ -59,14 +47,12 @@ function schedulerReleaseSummaryText(state) {
function vchanRenderSchedulerRelease() {
const btn = document.getElementById("scheduler-release-btn");
const status = document.getElementById("scheduler-release-status");
const cycleStatus = document.getElementById("scheduler-cycle-status");
if (!btn || !status) return;
const currentReleased = !!(schedulerReleaseState && schedulerReleaseState.current_session_released);
btn.disabled = !vchanSessionId || currentReleased;
btn.classList.toggle("active", !currentReleased);
btn.textContent = "Release to Scheduler";
status.textContent = schedulerReleaseSummaryText(schedulerReleaseState);
if (cycleStatus) cycleStatus.textContent = schedulerCycleSummaryText();
}
async function vchanPollSchedulerRelease() {
@@ -90,10 +76,6 @@ function vchanStartSchedulerReleasePolling() {
clearInterval(schedulerReleasePollTimer);
}
schedulerReleasePollTimer = setInterval(vchanPollSchedulerRelease, 10000);
if (schedulerCycleCountdownTimer) {
clearInterval(schedulerCycleCountdownTimer);
}
schedulerCycleCountdownTimer = setInterval(vchanRenderSchedulerRelease, 1000);
}
async function vchanToggleSchedulerRelease() {