From 05afbb7c25f2e8101802f5b4c4b79796b0923617 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Wed, 16 Sep 2026 14:35:18 -0700 Subject: [PATCH] Open radio test setup one hour early --- docs/_javascript/preset_test.js | 50 +++++++++++++++++++++++-------- docs/_stylesheets/preset_test.css | 4 +++ docs/preset_test.md | 23 +++++++++++--- test/test_preset_test.js | 10 +++++++ 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/docs/_javascript/preset_test.js b/docs/_javascript/preset_test.js index 7fdfefbe..e059e1b8 100644 --- a/docs/_javascript/preset_test.js +++ b/docs/_javascript/preset_test.js @@ -13,6 +13,8 @@ 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500, ]); const SCHEDULE_HORIZON_MS = 0x7fffffff; + const EARLY_JOIN_MS = 60 * 60 * 1000; + const CLOCK_RESET_COMMAND = "clkreboot"; class PresetTestError extends Error {} @@ -121,6 +123,10 @@ return Math.max(0, Math.ceil((config.endMs - nowMs) / 60000)); } + function immediateAvailable(config, nowMs) { + return nowMs >= config.startMs - EARLY_JOIN_MS && nowMs < config.endMs; + } + function scheduleAvailability(config, nowMs) { if (nowMs >= config.startMs) { return { available: false, reason: "The start time has passed; use the immediate option." }; @@ -302,6 +308,7 @@ setCommand(root, "stock-cancel-during", staticCommands.stockCancelDuring); setCommand(root, "companion-cancel-before", staticCommands.companionCancelBefore); setCommand(root, "companion-cancel-during", staticCommands.companionCancelDuring); + setCommand(root, "reset-clock", CLOCK_RESET_COMMAND); setCommand( root, "share-url", @@ -321,16 +328,28 @@ function render() { const nowMs = Date.now(); const phase = phaseAt(config, nowMs); + const immediateOpen = immediateAvailable(config, nowMs); const status = root.querySelector('[data-role="status"]'); const schedule = scheduleAvailability(config, nowMs); const commands = commandsFor(config, nowMs); status.dataset.state = phase; if (phase === "before") { - status.textContent = "Scheduled"; - setText(root, '[data-role="countdown-label"]', "Starts in"); - setText(root, '[data-role="countdown"]', formatCountdown(config.startMs - nowMs)); - setText(root, '[data-role="countdown-detail"]', "Do not use immediate TempRadio yet."); + if (immediateOpen) { + status.textContent = "Setup window open"; + setText(root, '[data-role="countdown-label"]', "Test starts in"); + setText(root, '[data-role="countdown"]', formatCountdown(config.startMs - nowMs)); + setText(root, '[data-role="countdown-detail"]', "Immediate TempRadio commands are enabled."); + } else { + status.textContent = "Scheduled"; + setText(root, '[data-role="countdown-label"]', "Commands open in"); + setText( + root, + '[data-role="countdown"]', + formatCountdown(config.startMs - EARLY_JOIN_MS - nowMs) + ); + setText(root, '[data-role="countdown-detail"]', "The setup window opens one hour before the test."); + } } else if (phase === "active") { status.textContent = "Test live"; setText(root, '[data-role="countdown-label"]', "Ends in"); @@ -343,25 +362,27 @@ setText(root, '[data-role="countdown-detail"]', "Temporary radios should be back on saved settings."); } - if (phase === "active") { + if (immediateOpen) { setCommand(root, "stock-now", commands.stockNow); setCommand(root, "companion-now", commands.companionNow); } else { const unavailable = phase === "before" - ? "Available when the test starts — use Option 2 to schedule now." + ? "Available one hour before the test — use Option 2 to schedule now." : "Test window ended — do not start TempRadio."; setCommand(root, "stock-now", unavailable); setCommand(root, "companion-now", unavailable); } - setCommandEnabled(root, "stock-now", phase === "active"); - setCommandEnabled(root, "companion-now", phase === "active"); - setMaterialCommandCopyEnabled(root, "stock-now", phase === "active"); - setMaterialCommandCopyEnabled(root, "companion-now", phase === "active"); + setCommandEnabled(root, "stock-now", immediateOpen); + setCommandEnabled(root, "companion-now", immediateOpen); + setMaterialCommandCopyEnabled(root, "stock-now", immediateOpen); + setMaterialCommandCopyEnabled(root, "companion-now", immediateOpen); setText( root, '[data-role="stock-now-note"]', - phase === "before" - ? "Available when the test starts; schedule it now with Option 2." + phase === "before" && !immediateOpen + ? "Available one hour before the test; schedule it now with Option 2." + : phase === "before" + ? "The setup window is open; the timeout includes the hour before the official start." : phase === "active" ? "The final argument is the live minutes remaining until the common end." : "The test window has ended." @@ -380,7 +401,7 @@ render(); if (typeof global.MutationObserver === "function") { const materialCopyObserver = new global.MutationObserver(function () { - const enabled = phaseAt(config, Date.now()) === "active"; + const enabled = immediateAvailable(config, Date.now()); setMaterialCommandCopyEnabled(root, "stock-now", enabled); setMaterialCommandCopyEnabled(root, "companion-now", enabled); }); @@ -393,10 +414,13 @@ DEFAULTS: DEFAULTS, VALID_BANDWIDTHS: VALID_BANDWIDTHS, SCHEDULE_HORIZON_MS: SCHEDULE_HORIZON_MS, + EARLY_JOIN_MS: EARLY_JOIN_MS, + CLOCK_RESET_COMMAND: CLOCK_RESET_COMMAND, PresetTestError: PresetTestError, configFromSearch: configFromSearch, phaseAt: phaseAt, remainingMinutes: remainingMinutes, + immediateAvailable: immediateAvailable, scheduleAvailability: scheduleAvailability, commandsFor: commandsFor, formatCountdown: formatCountdown, diff --git a/docs/_stylesheets/preset_test.css b/docs/_stylesheets/preset_test.css index 0913991c..30df5b77 100644 --- a/docs/_stylesheets/preset_test.css +++ b/docs/_stylesheets/preset_test.css @@ -163,6 +163,10 @@ background: var(--md-default-bg-color); } +.preset-test-card--wide { + grid-column: 1 / -1; +} + .preset-test-card h3 { margin-top: 0; } diff --git a/docs/preset_test.md b/docs/preset_test.md index 21347414..b1cadb1b 100644 --- a/docs/preset_test.md +++ b/docs/preset_test.md @@ -99,6 +99,20 @@ clock clock + +
+

If the clock is ahead and cannot move backward

+

+ If setting the correct time returns ERR: clock cannot go backwards, + run the command below. It resets the clock to an older fallback date and + reboots the node immediately, so it sends no reply. After the node reconnects, + copy and run the fresh local clock command above, verify with clock, + and then add the tempradioat schedule again. The reboot clears any + pending scheduled entries. +

+
clkreboot
+ +

@@ -109,11 +123,12 @@ clock

-

Option 1: switch when the test begins

+

Option 1: switch during the setup window

- Open this page after the start time. Its timeout shrinks so every node - returns at the same end time. These copy buttons remain disabled before - the window to prevent an early switch. + The immediate commands become available one hour before the official + start. Their timeout shrinks so every node still returns at the same end + time. Before that setup window, the command text and copy controls stay + unavailable.

diff --git a/test/test_preset_test.js b/test/test_preset_test.js index 7cf3dc7a..11e81c85 100644 --- a/test/test_preset_test.js +++ b/test/test_preset_test.js @@ -21,12 +21,22 @@ assert.strictEqual(defaults.endEpoch, config.endEpoch); assert.strictEqual(defaults.freq, config.freq); const before = Date.parse("2026-09-21T16:59:00-07:00"); +const beforeSetup = Date.parse("2026-09-21T15:59:59-07:00"); +const setupOpens = Date.parse("2026-09-21T16:00:00-07:00"); const active = Date.parse("2026-09-21T17:00:30-07:00"); const ended = Date.parse("2026-09-23T17:00:00-07:00"); assert.strictEqual(tool.phaseAt(config, before), "before"); assert.strictEqual(tool.phaseAt(config, active), "active"); assert.strictEqual(tool.phaseAt(config, ended), "ended"); +assert.strictEqual(tool.EARLY_JOIN_MS, 60 * 60 * 1000); +assert.strictEqual(tool.CLOCK_RESET_COMMAND, "clkreboot"); +assert.strictEqual(tool.immediateAvailable(config, beforeSetup), false); +assert.strictEqual(tool.immediateAvailable(config, setupOpens), true); +assert.strictEqual(tool.immediateAvailable(config, before), true); +assert.strictEqual(tool.immediateAvailable(config, active), true); +assert.strictEqual(tool.immediateAvailable(config, ended), false); assert.strictEqual(tool.remainingMinutes(config, active), 2880); +assert.strictEqual(tool.remainingMinutes(config, setupOpens), 2940); const commands = tool.commandsFor(config, active); assert.strictEqual(commands.stockNow, "tempradio 910.1,500,8,7,2880");