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() {