diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index a66e6a7..3fd7f8c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -267,7 +267,7 @@
Scheduler is controlling the rig.
-
Next scheduler cycle in 30s.
+
Interleaving: --
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js index e783e1e..f409c84 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js @@ -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; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js index 6e9360b..b07c86d 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js @@ -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() {