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.