diff --git a/Source/Teensy/MinimalBoot/Common/Common_Defs.h b/Source/Teensy/MinimalBoot/Common/Common_Defs.h index 7feae537..76ed8058 100644 --- a/Source/Teensy/MinimalBoot/Common/Common_Defs.h +++ b/Source/Teensy/MinimalBoot/Common/Common_Defs.h @@ -51,6 +51,7 @@ //synch with win app: //all commands must start with 0x64 #define SetColorToken 0x6422 +#define SetRTCToken 0x6474 //dt #define LaunchFileToken 0x6444 #define PingToken 0x6455 #define PauseSIDToken 0x6466 //df diff --git a/Source/Teensy/MinimalBoot/Min_SerUSBIO.ino b/Source/Teensy/MinimalBoot/Min_SerUSBIO.ino index 74563626..a73531c9 100644 --- a/Source/Teensy/MinimalBoot/Min_SerUSBIO.ino +++ b/Source/Teensy/MinimalBoot/Min_SerUSBIO.ino @@ -52,6 +52,9 @@ FLASHMEM void ServiceSerial(Stream *ThisCmdChannel) SendU16(FWMinimalToken); return; } + //left unread, the time bytes would each run as a command of their own: a 0x64 can reset or launch + uint32_t RefusedTime; + if (inVal == SetRTCToken) GetUInt(&RefusedTime, 4); SendU16(FailToken); CmdChannel->print("Busy!\n"); return; diff --git a/Source/Teensy/RemoteControl.ino b/Source/Teensy/RemoteControl.ino index e3076195..f6e281d3 100644 --- a/Source/Teensy/RemoteControl.ino +++ b/Source/Teensy/RemoteControl.ino @@ -111,6 +111,22 @@ FLASHMEM bool SetColorRef() return true; } +// Command: +// Set the TR's real-time clock, which the TR+'s battery keeps through power-off +// +// Workflow: +// Receive <-- SetRTCToken Token 0x6474 +// Receive <-- UTC time, seconds since 1970 (4 bytes, big endian) +// Send --> AckToken 0x64CC or FailToken 0x9B7F +FLASHMEM bool SetRTC() +{ + uint32_t SecsSince1970; + if (!GetUInt(&SecsSince1970, 4)) return false; + + Teensy3Clock.set(SecsSince1970); + return true; +} + // Command: // Set sub-song number of currently loaded SID // diff --git a/Source/Teensy/SerUSBIO.ino b/Source/Teensy/SerUSBIO.ino index 92480e73..eff5e2d4 100644 --- a/Source/Teensy/SerUSBIO.ino +++ b/Source/Teensy/SerUSBIO.ino @@ -612,6 +612,9 @@ FLASHMEM void ProcessCommand() if (CurrentIOHandler != IOH_TeensyROM && !ASIDAllowed) { + //left unread, the time bytes would each run as a command of their own: 'e' resets EEPROM + uint32_t RefusedTime; + if (TokenVal == SetRTCToken) GetUInt(&RefusedTime, 4); SendU16(FailToken); CmdChannel->print("Busy!\n"); return; @@ -661,6 +664,10 @@ FLASHMEM void ProcessCommand() if(SetColorRef()) SendU16(AckToken); else SendU16(FailToken); break; + case SetRTCToken: //Set the real-time clock + if(SetRTC()) SendU16(AckToken); + else SendU16(FailToken); + break; case DebugToken: //'dg'Test/debug //for (int a=0; a<256; a++) CmdChannel->printf("\n%3d, // %3d '%c'", ToPETSCII(a), a, a); //Printf_dbg("isFab2x: %d\n", isFab2x()); diff --git a/docs/ControlComms.md b/docs/ControlComms.md index 5e9d3d5a..1d5c2abd 100644 --- a/docs/ControlComms.md +++ b/docs/ControlComms.md @@ -118,6 +118,7 @@ A C64 reset the TeensyROM doesn't see (reset detect disabled in Settings) doesn' |GetDirectoryToken | 0x64DD | List directory contents (JSON format - deprecated) | |GetDirNDJSONToken | 0x64DE | List directory contents (NDJSON format) | |SetColorToken | 0x6422 | Set a TR UI color value | +|SetRTCToken | 0x6474 | Set the real-time clock | |PauseSIDToken | 0x6466 | Pause SID playback | |SetSIDSongToken | 0x6488 | Set sub-song number of currently loaded SID | |SIDSpeedLinToken | 0x6499 | Set SID playback speed (linear equation) | @@ -348,6 +349,20 @@ Sets one of the colors in the TR UI. Color is stored in EEPROM and IO space; the --- +### Set Real-Time Clock +Sets the TR's real-time clock. The time is UTC; the TR applies its time zone setting when it shows or passes on the time. On a TR+ with a battery, the clock keeps the time through power-off. The menu's clock shows the new time after its next reset. + +**Workflow:** +| Direction | Data | +|---|---| +| Receive | `SetRTCToken` — `0x6474` | +| Receive | UTC time in seconds since 1970-01-01 (4 bytes, big endian) | +| Send | `AckToken 0x64CC` or `FailToken 0x9B7F` | + +**Handler:** `SetRTC()` + +--- + ### Pause SID Pauses/Resumes SID playback by sending an interrupt request to the C64. diff --git a/tools/bench/README.md b/tools/bench/README.md index 2e5844ec..b3734be8 100644 --- a/tools/bench/README.md +++ b/tools/bench/README.md @@ -56,6 +56,7 @@ whatever node appeared beside it. | `probe.py` | Which image is running -- main, minimal or silent -- and its build banner. | | `ls.py [path] [drive]` | List a directory, to see that a push landed where it was aimed (first 1000 entries). | | `reset.py` | Reset the C64 to the menu, and the board out of the minimal image. | +| `setclock.py [UTC time]` | Set the TR's real-time clock, to this computer's clock or an ISO 8601 UTC time such as `2026-10-16T17:00:00`. Only at the TR menu. | | `hostops.py` | Installing and removing a host, and `run_step` -- driving anything that ends in a reboot and reading which of the two shapes came back. Also the phrases a reboot is judged by: `INSTALLED`, `REMOVED` and `FINISHED_NORMALLY`. Shared by the `host*.py` scripts and by `exttest.py`, whose exit status is `finished_normally()`'s answer, so a phrase edited here moves that verdict; and by `test_bounds.py`, which patches its timeouts to test them, and `test_protocol.py`, which pins the phrases against the firmware's own `VmFail::describe()`. | | `trlink.py` | The shared library the above are built on. | | `protocol.py` | Every value that goes on the wire, named once. | diff --git a/tools/bench/protocol.py b/tools/bench/protocol.py index a8f38414..81032a05 100644 --- a/tools/bench/protocol.py +++ b/tools/bench/protocol.py @@ -21,6 +21,7 @@ POST_FILE = 0x64BB DELETE_FILE = 0x64CF GET_DIR_NDJSON = 0x64DE +SET_RTC = 0x6474 # Replies. ACK = 0x64CC @@ -49,6 +50,7 @@ 'PostFileToken': POST_FILE, 'DeleteFileToken': DELETE_FILE, 'GetDirNDJSONToken': GET_DIR_NDJSON, + 'SetRTCToken': SET_RTC, 'AckToken': ACK, 'FailToken': FAIL, 'FWFullToken': FW_FULL, diff --git a/tools/bench/setclock.py b/tools/bench/setclock.py new file mode 100755 index 00000000..4e91ee62 --- /dev/null +++ b/tools/bench/setclock.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +"""Set the TR's real-time clock. setclock.py [UTC time] + +The time is UTC, to the second: this computer's clock, or an ISO 8601 time +such as 2026-10-16T17:00:00. The board refuses it with "Busy!" unless it is +sitting at its own menu. +""" +import datetime, math, sys +from protocol import SET_RTC, to_board +from trlink import Link + +if len(sys.argv) > 1: + when = datetime.datetime.fromisoformat(sys.argv[1]) + if when.tzinfo is None: + when = when.replace(tzinfo=datetime.timezone.utc) + when = when.astimezone(datetime.timezone.utc) +else: + when = datetime.datetime.now(datetime.timezone.utc) +secs = math.floor(when.timestamp()) +if not 0 <= secs < 1 << 32: + raise SystemExit(f'{when:%Y-%m-%d %H:%M:%S} UTC is outside 1970-2106, the range four bytes of seconds hold') +with Link() as tr: + tr.drain(0.3) + tr.wr(to_board(SET_RTC) + to_board(secs, 4)) + tr.ack('set clock') +print('clock set to', when.strftime('%Y-%m-%d %H:%M:%S UTC'))