Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/AmsToMqttBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ ADC_MODE(ADC_VCC);
#include "PulseMeterCommunicator.h"

#include "Uptime.h"
#include "NtpStatus.h"

#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
Expand Down Expand Up @@ -699,6 +700,8 @@ void setup() {
ea.setPriceService(ps);
ws.setup(&config, &gpioConfig, &meterState, &ds, &ea, &rtp, &updater);

ntpRegisterSyncCallback();

UiConfig ui;
if(config.getUiConfig(ui)) {
if(strlen(ui.language) == 0) {
Expand Down
17 changes: 16 additions & 1 deletion src/AmsWebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "CustomDefaults.h"
#include "AmsWebHeaders.h"
#include "FirmwareVersion.h"
#include "NtpStatus.h"
#include "base64.h"
#include "hexutils.h"
#include "AmsJsonGenerator.h"
Expand Down Expand Up @@ -312,6 +313,10 @@ uint8_t AmsWebServer::mqttHandlerState(AmsMqttHandler* h) {
return h->lastError() == 0 ? 2 : 3;
}

// SNTP resyncs roughly hourly; flag the NTP service as degraded if no sync has
// landed in this long, allowing a couple of missed cycles before warning.
#define NTP_STALE_AFTER_SECONDS 10800

String AmsWebServer::buildServicesJson() {
String out = "";
char entry[320];
Expand Down Expand Up @@ -387,7 +392,17 @@ String AmsWebServer::buildServicesJson() {
NtpConfig ntp;
if(config->getNtpConfig(ntp) && ntp.enable) {
const char* server = strlen(ntp.server) > 0 ? ntp.server : "pool.ntp.org";
uint8_t s = time(nullptr) > FirmwareVersion::BuildEpoch ? 1 : 2;
// A set-but-stale clock (NTP stopped resyncing) silently corrupts
// day-boundary accounting, so flag staleness rather than only
// reporting whether the clock was ever set.
uint64_t lastSync = ntpLastSyncMillis();
uint8_t s;
if(lastSync == 0) {
s = 2; // No SNTP sync since boot yet
} else {
uint32_t ageSec = (uint32_t) ((millis64() - lastSync) / 1000);
s = ageSec > NTP_STALE_AFTER_SECONDS ? 2 : 1;
}
snprintf_P(entry, sizeof(entry), PSTR("{\"k\":\"ntp\",\"s\":%d,\"e\":0,\"d\":\"%s\"}"),
s, server);
if(!out.isEmpty()) out += ",";
Expand Down
40 changes: 40 additions & 0 deletions src/NtpStatus.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

#include "NtpStatus.h"
#include "Uptime.h"

#if defined(ESP8266)
#include <coredecls.h>
#elif defined(ESP32)
#include <esp_sntp.h>
#endif

static uint64_t lastSyncMillis = 0;

uint64_t ntpLastSyncMillis() {
return lastSyncMillis;
}

#if defined(ESP8266)
// from_sntp is false when the clock is set manually (e.g. from the meter
// timestamp), so we only record actual SNTP syncs here.
static void onTimeSync(bool from_sntp) {
if(from_sntp) lastSyncMillis = millis64();
}
#elif defined(ESP32)
static void onTimeSync(struct timeval* tv) {
lastSyncMillis = millis64();
}
#endif

void ntpRegisterSyncCallback() {
#if defined(ESP8266)
settimeofday_cb(onTimeSync);
#elif defined(ESP32)
sntp_set_time_sync_notification_cb(onTimeSync);
#endif
}
20 changes: 20 additions & 0 deletions src/NtpStatus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

#ifndef _NTPSTATUS_H
#define _NTPSTATUS_H

#include <stdint.h>

// Registers the platform SNTP sync-notification callback. Call once during setup.
void ntpRegisterSyncCallback();

// millis64() value captured at the last successful SNTP sync, or 0 if no SNTP
// sync has happened since boot. Used to detect a clock that is set but stale
// (NTP stopped resyncing), which silently corrupts day-boundary accounting.
uint64_t ntpLastSyncMillis();

#endif
Loading