mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
t5s3 standalone clock sync over serial
This commit is contained in:
64
Filter_clock_sync.py
Normal file
64
Filter_clock_sync.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# PlatformIO monitor filter: automatic clock sync for Meck devices
|
||||
#
|
||||
# When a Meck device boots with no valid RTC time, it prints "MECK_CLOCK_REQ"
|
||||
# over serial. This filter watches for that line and responds immediately
|
||||
# with "clock sync <epoch>\r\n", setting the device's real-time clock to
|
||||
# the host computer's current time.
|
||||
#
|
||||
# The sync is completely transparent — the user just sees it happen in the
|
||||
# boot log. If the RTC already has valid time, the device never sends the
|
||||
# request and this filter does nothing.
|
||||
#
|
||||
# Install: place this file in <project>/monitor/filter_clock_sync.py
|
||||
# Enable: add "clock_sync" to monitor_filters in platformio.ini
|
||||
#
|
||||
# Works with: PlatformIO Core >= 6.0
|
||||
|
||||
import time
|
||||
|
||||
from platformio.device.monitor.filters.base import DeviceFilter
|
||||
|
||||
|
||||
class ClockSync(DeviceFilter):
|
||||
NAME = "clock_sync"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._buf = bytearray()
|
||||
self._synced = False
|
||||
|
||||
def rx(self, text):
|
||||
"""Called with each chunk of data received from the device."""
|
||||
if self._synced:
|
||||
return text
|
||||
|
||||
# Accumulate into a line buffer to detect MECK_CLOCK_REQ
|
||||
if isinstance(text, str):
|
||||
self._buf.extend(text.encode("utf-8", errors="replace"))
|
||||
else:
|
||||
self._buf.extend(text)
|
||||
|
||||
if b"MECK_CLOCK_REQ" in self._buf:
|
||||
epoch = int(time.time())
|
||||
response = "clock sync {}\r\n".format(epoch)
|
||||
try:
|
||||
# Write directly to the serial port
|
||||
self.miniterm.serial.write(response.encode("utf-8"))
|
||||
except Exception as e:
|
||||
# Fallback: shouldn't happen, but don't crash the monitor
|
||||
import sys
|
||||
print(
|
||||
"\n[clock_sync] Failed to auto-sync: {}".format(e),
|
||||
file=sys.stderr,
|
||||
)
|
||||
self._synced = True
|
||||
self._buf = bytearray()
|
||||
elif len(self._buf) > 2048:
|
||||
# Prevent unbounded growth — keep tail only
|
||||
self._buf = self._buf[-256:]
|
||||
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
"""Called with each chunk of data sent from terminal to device."""
|
||||
return text
|
||||
@@ -69,6 +69,7 @@ All commands follow a simple pattern: `get` to read, `set` to write.
|
||||
| `get presets` | List all radio presets with parameters |
|
||||
| `get pubkey` | Device public key (hex) |
|
||||
| `get firmware` | Firmware version string |
|
||||
| `clock` | Current RTC time (UTC + epoch) |
|
||||
|
||||
**4G variant only:**
|
||||
|
||||
@@ -294,6 +295,68 @@ To clear a custom APN and revert to auto-detection on next boot:
|
||||
set apn
|
||||
```
|
||||
|
||||
### Clock Sync
|
||||
|
||||
Set the device's real-time clock from a Unix timestamp. This is especially important for the T5S3 E-Paper Pro which has no GPS to auto-set the clock. These are standalone commands (not `get`/`set` prefixed) — matching the same `clock sync` command used on MeshCore repeaters.
|
||||
|
||||
#### View Current Time
|
||||
|
||||
```
|
||||
clock
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
> 2026-03-13 04:22:15 UTC (epoch: 1773554535)
|
||||
```
|
||||
|
||||
If the clock has never been set:
|
||||
|
||||
```
|
||||
> not set (epoch: 0)
|
||||
```
|
||||
|
||||
#### Sync Clock from Serial
|
||||
|
||||
```
|
||||
clock sync 1773554535
|
||||
```
|
||||
|
||||
The value must be a Unix epoch timestamp in the 2024–2036 range.
|
||||
|
||||
**Quick one-liner from your terminal (macOS / Linux / WSL):**
|
||||
|
||||
```
|
||||
echo "clock sync $(date +%s)" > /dev/ttyACM0
|
||||
```
|
||||
|
||||
Or paste directly into the Arduino IDE Serial Monitor:
|
||||
|
||||
```
|
||||
clock sync 1773554535
|
||||
```
|
||||
|
||||
**Tip:** On macOS/Linux, run `date +%s` to get the current epoch. On Windows PowerShell: `[int](Get-Date -UFormat %s)`.
|
||||
|
||||
#### Boot-Time Auto-Sync (T5S3)
|
||||
|
||||
When the T5S3 boots with no valid RTC time and detects a USB serial host is connected, it sends a `MECK_CLOCK_REQ` handshake over serial. If you're using PlatformIO's serial monitor (`pio device monitor`), the built-in `clock_sync` monitor filter responds automatically with the host computer's current time — no user action required. The sync appears transparently in the boot log:
|
||||
|
||||
```
|
||||
MECK_CLOCK_REQ
|
||||
(Waiting 3s for clock sync from host...)
|
||||
> Clock synced to 1773554535
|
||||
```
|
||||
|
||||
If no USB host is connected (e.g. running on battery), the sync window is skipped entirely with no boot delay.
|
||||
|
||||
**Manual fallback:** If you're using a serial terminal that doesn't have the filter (e.g. `screen`, PuTTY), you can paste a `clock sync` command during the 3-second window, or any time after boot:
|
||||
|
||||
```
|
||||
clock sync $(date +%s)
|
||||
```
|
||||
|
||||
### System Commands
|
||||
|
||||
| Command | Description |
|
||||
|
||||
@@ -2290,6 +2290,14 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
char hex[PUB_KEY_SIZE * 2 + 1];
|
||||
mesh::Utils::toHex(hex, self_id.pub_key, PUB_KEY_SIZE);
|
||||
Serial.printf(" pubkey: %s\n", hex);
|
||||
{
|
||||
uint32_t clk = getRTCClock()->getCurrentTime();
|
||||
if (clk > 1704067200UL) {
|
||||
Serial.printf(" clock: %lu (valid)\n", (unsigned long)clk);
|
||||
} else {
|
||||
Serial.printf(" clock: not set\n");
|
||||
}
|
||||
}
|
||||
// List channels
|
||||
Serial.println(" channels:");
|
||||
bool chFound = false;
|
||||
@@ -2694,6 +2702,45 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
Serial.printf(" Error: unknown setting '%s' (try 'help')\n", config);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// CLOCK commands (standalone — matches repeater admin convention)
|
||||
// =====================================================================
|
||||
} else if (memcmp(cli_command, "clock sync ", 11) == 0) {
|
||||
uint32_t epoch = (uint32_t)strtoul(&cli_command[11], nullptr, 10);
|
||||
if (epoch > 1704067200UL && epoch < 2082758400UL) {
|
||||
getRTCClock()->setCurrentTime(epoch);
|
||||
Serial.printf(" > clock synced to %lu\n", (unsigned long)epoch);
|
||||
} else {
|
||||
Serial.println(" Error: invalid epoch (must be 2024-2036 range)");
|
||||
Serial.println(" Hint: on macOS/Linux run: date +%s");
|
||||
}
|
||||
} else if (strcmp(cli_command, "clock sync") == 0) {
|
||||
// Bare "clock sync" without a value — show usage
|
||||
Serial.println(" Usage: clock sync <unix_epoch>");
|
||||
Serial.println(" Hint: clock sync $(date +%s)");
|
||||
} else if (strcmp(cli_command, "clock") == 0) {
|
||||
uint32_t t = getRTCClock()->getCurrentTime();
|
||||
if (t > 1704067200UL) {
|
||||
// Break epoch into human-readable UTC
|
||||
uint32_t ep = t;
|
||||
int s = ep % 60; ep /= 60;
|
||||
int mi = ep % 60; ep /= 60;
|
||||
int h = ep % 24; ep /= 24;
|
||||
int yr = 1970;
|
||||
while (true) { int d = ((yr%4==0&&yr%100!=0)||yr%400==0)?366:365; if(ep<(uint32_t)d) break; ep-=d; yr++; }
|
||||
int mo = 1;
|
||||
while (true) {
|
||||
static const uint8_t dm[]={31,28,31,30,31,30,31,31,30,31,30,31};
|
||||
int d = (mo==2&&((yr%4==0&&yr%100!=0)||yr%400==0))?29:dm[mo-1];
|
||||
if(ep<(uint32_t)d) break; ep-=d; mo++;
|
||||
}
|
||||
int dy = ep + 1;
|
||||
Serial.printf(" > %04d-%02d-%02d %02d:%02d:%02d UTC (epoch: %lu)\n",
|
||||
yr, mo, dy, h, mi, s, (unsigned long)t);
|
||||
} else {
|
||||
Serial.printf(" > not set (epoch: %lu)\n", (unsigned long)t);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// HELP command
|
||||
// =====================================================================
|
||||
@@ -2713,6 +2760,11 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
Serial.println(" int.thresh <0|14+> Interference threshold dB (0=off, 14=typical)");
|
||||
Serial.println(" gps.baud <rate> GPS baud (0=default, reboot to apply)");
|
||||
Serial.println("");
|
||||
Serial.println(" Clock:");
|
||||
Serial.println(" clock Show current RTC time (UTC)");
|
||||
Serial.println(" clock sync <epoch> Set RTC from Unix timestamp");
|
||||
Serial.println(" Hint: clock sync $(date +%s)");
|
||||
Serial.println("");
|
||||
Serial.println(" Compound commands:");
|
||||
Serial.println(" get all Dump all settings");
|
||||
Serial.println(" get radio Show all radio params");
|
||||
|
||||
@@ -1189,14 +1189,57 @@ void setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// RTC diagnostic — verify the auto-discovered RTC is working
|
||||
// RTC diagnostic + boot-time serial clock sync (T5S3 has no GPS)
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
{
|
||||
uint32_t rtcTime = rtc_clock.getCurrentTime();
|
||||
Serial.printf("setup() - RTC time: %lu (valid=%s)\n", rtcTime,
|
||||
rtcTime > 1700000000 ? "YES" : "NO");
|
||||
if (rtcTime < 1700000000) {
|
||||
Serial.println("setup() - RTC has no valid time (will be set by companion app)");
|
||||
// No valid time. If a USB host has the serial port open (Serial
|
||||
// evaluates true on ESP32-S3 native CDC), request an automatic
|
||||
// clock sync. The PlatformIO monitor filter "clock_sync" watches
|
||||
// for MECK_CLOCK_REQ and responds immediately with the host time.
|
||||
// Manual sync is also accepted: type "clock sync <epoch>" in any
|
||||
// serial terminal.
|
||||
if (Serial) {
|
||||
Serial.println("MECK_CLOCK_REQ");
|
||||
Serial.println(" (Waiting 3s for clock sync from host...)");
|
||||
|
||||
char syncBuf[64];
|
||||
int syncPos = 0;
|
||||
unsigned long syncDeadline = millis() + 3000;
|
||||
bool synced = false;
|
||||
|
||||
while (millis() < syncDeadline && !synced) {
|
||||
while (Serial.available() && syncPos < (int)sizeof(syncBuf) - 1) {
|
||||
char c = Serial.read();
|
||||
if (c == '\r' || c == '\n') {
|
||||
if (syncPos > 0) {
|
||||
syncBuf[syncPos] = '\0';
|
||||
if (memcmp(syncBuf, "clock sync ", 11) == 0) {
|
||||
uint32_t epoch = (uint32_t)strtoul(&syncBuf[11], nullptr, 10);
|
||||
if (epoch > 1704067200UL && epoch < 2082758400UL) {
|
||||
rtc_clock.setCurrentTime(epoch);
|
||||
Serial.printf(" > Clock synced to %lu\n", (unsigned long)epoch);
|
||||
synced = true;
|
||||
}
|
||||
}
|
||||
syncPos = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
syncBuf[syncPos++] = c;
|
||||
}
|
||||
if (!synced) delay(10);
|
||||
}
|
||||
if (!synced) {
|
||||
Serial.println(" > No clock sync received, continuing boot");
|
||||
Serial.println(" > Use 'clock sync <epoch>' any time to sync later");
|
||||
}
|
||||
} else {
|
||||
Serial.println("setup() - RTC not set, no serial host detected (skipping sync window)");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -56,7 +56,7 @@ build_src_filter =
|
||||
[esp32_base]
|
||||
extends = arduino_base
|
||||
platform = platformio/espressif32@6.11.0
|
||||
monitor_filters = esp32_exception_decoder
|
||||
monitor_filters = esp32_exception_decoder, clock_sync
|
||||
extra_scripts = merge-bin.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D ESP32_PLATFORM
|
||||
|
||||
@@ -56,6 +56,14 @@ void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
||||
}
|
||||
|
||||
void BaseChatMesh::bootstrapRTCfromContacts() {
|
||||
// If the RTC already has a sane time (e.g. hardware RTC like PCF8563, or
|
||||
// GPS-synced), don't overwrite it with a potentially stale contact lastmod.
|
||||
// This bootstrap is only useful for boards with no hardware RTC at all.
|
||||
uint32_t current = getRTCClock()->getCurrentTime();
|
||||
if (current > 1704067200UL) { // Jan 1 2024 — matches EPOCH_MIN_SANE
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t latest = 0;
|
||||
for (int i = 0; i < num_contacts; i++) {
|
||||
if (contacts[i].lastmod > latest) {
|
||||
|
||||
Reference in New Issue
Block a user