Add Pi Zero headless serial bridge with AP portal and daily RTC-based logs

This commit is contained in:
2026-02-11 21:16:23 +01:00
parent 7f0e872942
commit 6ee36ee45b
23 changed files with 1173 additions and 0 deletions

68
src/rtc_sync.py Normal file
View File

@@ -0,0 +1,68 @@
import os
import subprocess
from datetime import datetime, timezone
from typing import Tuple
import ntplib
from app_state import AppState
class RTCAndNTPManager:
def __init__(self, state: AppState, rtc_device: str = "/dev/rtc0", ntp_server: str = "pool.ntp.org") -> None:
self.state = state
self.rtc_device = rtc_device
self.ntp_server = ntp_server
def _run(self, args, timeout: int = 12) -> subprocess.CompletedProcess:
return subprocess.run(args, capture_output=True, text=True, timeout=timeout, check=False)
def rtc_available(self) -> bool:
return os.path.exists(self.rtc_device)
def sync_from_rtc(self) -> Tuple[bool, str]:
if not self.rtc_available():
return False, f"RTC not found at {self.rtc_device}"
proc = self._run(["hwclock", "-s", "--utc"], timeout=10)
if proc.returncode == 0:
self.state.update_status("System time loaded from RTC", "")
return True, "RTC -> system time ok"
return False, (proc.stderr or proc.stdout or "hwclock -s failed").strip()
def sync_ntp_to_system(self, timeout: int = 6) -> Tuple[bool, str]:
try:
client = ntplib.NTPClient()
response = client.request(self.ntp_server, version=3, timeout=timeout)
ts = float(response.tx_time)
dt_utc = datetime.fromtimestamp(ts, tz=timezone.utc)
iso_utc = dt_utc.strftime("%Y-%m-%d %H:%M:%S")
proc = self._run(["date", "-u", "-s", iso_utc], timeout=10)
if proc.returncode != 0:
return False, (proc.stderr or proc.stdout or "date -s failed").strip()
self.state.update_status("System time synced via NTP", "")
return True, f"NTP sync ok ({iso_utc} UTC)"
except Exception as exc:
return False, f"NTP sync failed: {exc}"
def write_system_time_to_rtc(self) -> Tuple[bool, str]:
if not self.rtc_available():
return False, f"RTC not found at {self.rtc_device}"
proc = self._run(["hwclock", "-w", "--utc"], timeout=10)
if proc.returncode == 0:
self.state.update_status("RTC updated from system time", "")
return True, "system -> RTC ok"
return False, (proc.stderr or proc.stdout or "hwclock -w failed").strip()
def sync_ntp_and_rtc(self) -> Tuple[bool, str]:
ok_ntp, msg_ntp = self.sync_ntp_to_system()
if not ok_ntp:
return False, msg_ntp
ok_rtc, msg_rtc = self.write_system_time_to_rtc()
if not ok_rtc:
return False, f"NTP ok, RTC write failed: {msg_rtc}"
return True, "NTP + RTC sync successful"