- Drop extern "C" from weak hooks (UB with C++ reference param) - syncTimeWithNTP returns bool; syncFromNTP uses it (robust success check) - Avoid duplicate NTP sync at boot (wificlass already syncs) - Clamp negative time deltas in 24h timer and JSON status - Cache rtc.now() in loopRTC to avoid I2C race with PN532
134 lines
4.8 KiB
C
134 lines
4.8 KiB
C
// PCF8523-RTC-Modul mit NTP-Sync und Fallback.
|
|
// Header-only nach Projekt-Pattern: Globale Objekte werden hier definiert.
|
|
// Dieser Header darf NUR in master.cpp inkludiert werden.
|
|
#pragma once
|
|
#include <Arduino.h>
|
|
#include <RTClib.h>
|
|
#include <Wire.h>
|
|
|
|
// Globale RTC-Instanz und Status-Flags
|
|
RTC_PCF8523 rtc;
|
|
bool rtcAvailable = false;
|
|
bool ntpEverSynced = false;
|
|
time_t lastNtpSyncEpoch = 0;
|
|
bool lastStaConnected = false;
|
|
time_t cachedRtcEpoch = 0; // letzter rtc.now()-Wert, gelesen aus loopRTC (I2C-Race-Guard)
|
|
|
|
// I2C-Init, PCF8523-Detektion, und Systemzeit-Fallback aus RTC.
|
|
// Soft-Fail: bei nicht gefundener Hardware bleibt rtcAvailable=false.
|
|
void setupRTC() {
|
|
Wire.begin();
|
|
if (!rtc.begin()) {
|
|
Serial.println("[RTC] PCF8523 nicht gefunden — RTC-Funktionen deaktiviert");
|
|
rtcAvailable = false;
|
|
return;
|
|
}
|
|
if (!rtc.initialized() || rtc.lostPower()) {
|
|
Serial.println("[RTC] PCF8523 erkannt, aber Akku-Backup ungültig (lostPower)");
|
|
}
|
|
rtc.start();
|
|
rtcAvailable = true;
|
|
Serial.println("[RTC] PCF8523 initialisiert");
|
|
|
|
// Systemzeit aus RTC vorbelegen — wird ggf. später durch NTP übersteuert.
|
|
// RTC speichert UTC, settimeofday erwartet UTC. Kein Offset nötig.
|
|
DateTime nowRtc = rtc.now();
|
|
uint32_t rtcEpoch = nowRtc.unixtime();
|
|
// Plausibilität: RTC sollte mindestens 2025 zeigen, sonst ist sie ungestellt.
|
|
if (rtcEpoch >= 1735689600UL) {
|
|
struct timeval tv;
|
|
tv.tv_sec = (time_t)rtcEpoch;
|
|
tv.tv_usec = 0;
|
|
settimeofday(&tv, NULL);
|
|
cachedRtcEpoch = (time_t)rtcEpoch;
|
|
Serial.printf("[RTC] Systemzeit aus RTC gesetzt: %lu (UTC)\n",
|
|
(unsigned long)rtcEpoch);
|
|
} else {
|
|
Serial.printf("[RTC] RTC-Zeit unplausibel (%lu) — nicht übernommen\n",
|
|
(unsigned long)rtcEpoch);
|
|
}
|
|
}
|
|
|
|
// Plausibilitäts-Grenzen: 2025-01-01 .. 2100-01-01 (UTC).
|
|
// Verhindert, dass kaputte Timestamps (0, negativ, 1970) die RTC korrumpieren.
|
|
static constexpr uint32_t RTC_MIN_EPOCH = 1735689600UL; // 2025-01-01 00:00:00 UTC
|
|
static constexpr uint32_t RTC_MAX_EPOCH = 4102444800UL; // 2100-01-01 00:00:00 UTC
|
|
|
|
void persistSystemTimeToRTC(time_t t) {
|
|
if (!rtcAvailable) return;
|
|
if ((uint32_t)t < RTC_MIN_EPOCH || (uint32_t)t >= RTC_MAX_EPOCH) {
|
|
Serial.printf("[RTC] persist abgelehnt — Timestamp unplausibel: %ld\n",
|
|
(long)t);
|
|
return;
|
|
}
|
|
rtc.adjust(DateTime((uint32_t)t));
|
|
Serial.printf("[RTC] in RTC geschrieben: %ld (UTC)\n", (long)t);
|
|
}
|
|
|
|
// Weak-Hook-Override aus timesync.h — wird automatisch aufgerufen,
|
|
// sobald irgendwo setSystemTime() Erfolg meldet.
|
|
void onSystemTimeSet(time_t t) {
|
|
persistSystemTimeToRTC(t);
|
|
}
|
|
|
|
// Versucht NTP-Sync via timesync.h. Bei Erfolg: schreibt UTC in RTC,
|
|
// setzt ntpEverSynced=true, aktualisiert lastNtpSyncEpoch.
|
|
// Returns true bei Erfolg.
|
|
bool syncFromNTP() {
|
|
if (!syncTimeWithNTP()) {
|
|
Serial.println("[RTC] NTP-Sync fehlgeschlagen — RTC unverändert");
|
|
return false;
|
|
}
|
|
time_t after = time(NULL);
|
|
if ((uint32_t)after < RTC_MIN_EPOCH) {
|
|
Serial.println("[RTC] NTP-Sync lieferte unplausible Zeit — RTC unverändert");
|
|
return false;
|
|
}
|
|
persistSystemTimeToRTC(after);
|
|
ntpEverSynced = true;
|
|
lastNtpSyncEpoch = after;
|
|
return true;
|
|
}
|
|
|
|
// Aus loop() aufgerufen. Triggert syncFromNTP():
|
|
// - bei steigender Flanke von WiFi.isConnected() (STA-Reconnect)
|
|
// - alle 24h, sobald STA verbunden ist
|
|
// Reine Vergleichsoperationen, NTP-Roundtrip selbst ist blockierend (~ms..5s).
|
|
void loopRTC() {
|
|
// RTC-Lesen passiert NUR aus der main-loop (verhindert I2C-Race mit PN532).
|
|
// appendTimeStatus() liest danach nur den gecachten Wert.
|
|
if (rtcAvailable) {
|
|
cachedRtcEpoch = (time_t)rtc.now().unixtime();
|
|
}
|
|
bool sta = (WiFi.status() == WL_CONNECTED);
|
|
|
|
// Edge: false -> true (frischer STA-Connect)
|
|
if (sta && !lastStaConnected) {
|
|
Serial.println("[RTC] STA-Reconnect erkannt — NTP-Sync");
|
|
syncFromNTP();
|
|
}
|
|
lastStaConnected = sta;
|
|
|
|
// 24h-Periodik (nur wenn STA online)
|
|
if (sta && ntpEverSynced) {
|
|
time_t nowEpoch = time(NULL);
|
|
// Defensive: Clock-Jump rückwärts (z.B. via Browser-Time auf altes Datum)
|
|
// würde das Delta negativ machen. Auf "jetzt" zurücksetzen.
|
|
if (nowEpoch < lastNtpSyncEpoch) lastNtpSyncEpoch = nowEpoch;
|
|
if (nowEpoch - lastNtpSyncEpoch >= 86400) {
|
|
Serial.println("[RTC] 24h-Periodik — NTP-Sync");
|
|
syncFromNTP();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Weak-Hook-Override aus timesync.h — erweitert /api/time um RTC-Status.
|
|
void appendTimeStatus(JsonDocument &doc) {
|
|
doc["rtc_available"] = rtcAvailable;
|
|
doc["rtc_synced_from_ntp"] = ntpEverSynced;
|
|
long ago = ntpEverSynced ? (long)(time(NULL) - lastNtpSyncEpoch) : (long)-1;
|
|
if (ago < 0 && ntpEverSynced) ago = 0; // Clock-Jump rückwärts → 0 statt negativ
|
|
doc["last_ntp_sync_ago_s"] = ago;
|
|
doc["rtc_time_utc"] = rtcAvailable ? (long)cachedRtcEpoch : (long)0;
|
|
}
|