Bir ESP32 NTP MQTT zaman damgası, bir ölçümün broker'a ne zaman ulaştığını değil, gerçekte ne zaman alındığını kaydeder. Bu eğitimde ESP32 saatini SNTP üzerinden eşitleyecek, UTC zaman damgalarını ISO 8601 ve epoch milisaniye olarak biçimlendirecek ve bunları JSON içinde broker.connectmq.com'a yayınlayacaksınız; basit uygulamaları bozan uç durumlar da buna dahil.
MQTT Mesajları Neden Kendi Zaman Damgalarına İhtiyaç Duyar
MQTT pakete bir zaman damgası koymaz. Bir subscriber mesajın ne zaman geldiğini bilir, ancak bu, sensörün okunduğu andan çok farklı olabilir:
- Cihaz bir Wi-Fi kesintisi sırasında ölçümleri tamponladı ve daha sonra toplu halde yayınladı.
- Kalıcı bir oturum, çevrimdışı olan bir subscriber'a kuyruktaki QoS 1 mesajlarını teslim etti.
- Retained bir mesaj, yayınlanmasından saatler sonra yeni bir subscriber'a teslim edildi.
- Ağ gecikmesi ve yeniden denemeler, hızlı olaylar için önem taşıyan sapmalar ekler.
Dashboard'unuz veya veritabanınız varış zamanını kullanıyorsa dört durumun tamamı yanlış grafikler üretir. Çözüm basittir: her ölçümü, UTC'ye eşitlenmiş bir saat kullanarak alındığı anda cihazın damgalamasına izin verin. ESP32'nin bir gerçek zamanlı sayacı vardır, ancak çoğu kartta pille desteklenen bir saat yoktur; bu yüzden her açılıştan sonra NTP onu düzeltene kadar 1 Ocak 1970'ten başlar.
Zaman Damgası Biçimini Seçmek
| Biçim | Örnek | Boyut | En uygun kullanım |
|---|---|---|---|
| Milisaniyeli ISO 8601 UTC | 2026-10-16T09:41:07.382Z |
24 karakter | İnsan tarafından okunabilir loglar, çoğu JSON tüketicisi |
| Epoch saniye | 1792143667 |
10 hane | Kompakt payload'lar, kaba telemetri |
| Epoch milisaniye | 1792143667382 |
13 hane | Zaman serisi veritabanları, olay sıralama |
| Ofsetli yerel saat | 2026-10-16T11:41:07+02:00 |
25 karakter | Tek bir tesise bağlı ekranlar |
Hat üzerinde UTC kullanın. Yerel saate dönüşümü, izleyicinin saat diliminin bilindiği dashboard'da yapın. Cihazlardaki yerel zaman damgaları, yaz saati değişikliklerinde yılda iki kez yinelenen veya eksik saatlere yol açar. Aşağıdaki sketch hem ISO 8601'i hem de epoch milisaniyeyi yayınlar; böylece sonraki aşamada seçim yapabilirsiniz.
Ön Koşullar
- Arduino core 3.x ile bir ESP32 kartı (bu sketch core 2.x ile de çalışır).
- Nick O'Leary'nin PubSubClient kütüphanesi.
- Ağınızda NTP için dışa giden UDP 123 portuna izin verilmesi.
- Test için
mosquitto_sub.
ESP32 NTP MQTT Zaman Damgası Sketch'i
#include <WiFi.h>
#include <PubSubClient.h>
#include <time.h>
#include <sys/time.h>
#include "esp_sntp.h"
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* TOPIC = "connectmq/demo/ntp8r2c/telemetry"; // kimliği benzersiz yapın
const time_t VALID_EPOCH = 1700000000; // daha eski bir değer "henüz eşitlenmedi" demektir
WiFiClient net;
PubSubClient mqtt(net);
String clientId;
volatile bool timeSynced = false;
unsigned long lastPublish = 0;
void onTimeSync(struct timeval* tv) {
timeSynced = true;
Serial.printf("NTP sync at epoch %ld\n", (long)tv->tv_sec);
}
// Örneğin 2026-10-16T09:41:07.382Z yazar, saat geçerli değilse false döndürür
bool isoTimestamp(char* buf, size_t len, uint64_t* epochMs) {
struct timeval tv;
gettimeofday(&tv, nullptr);
if (tv.tv_sec < VALID_EPOCH) return false;
struct tm utc;
gmtime_r(&tv.tv_sec, &utc);
size_t n = strftime(buf, len, "%Y-%m-%dT%H:%M:%S", &utc);
snprintf(buf + n, len - n, ".%03ldZ", (long)(tv.tv_usec / 1000));
*epochMs = (uint64_t)tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
return true;
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(300);
Serial.printf("Wi-Fi connected, IP %s\n", WiFi.localIP().toString().c_str());
}
void startNtp() {
sntp_set_time_sync_notification_cb(onTimeSync);
configTime(0, 0, "pool.ntp.org", "time.cloudflare.com", "time.google.com");
// İlk eşitleme için en fazla 15 sn bekle, ama asla sonsuza kadar bloklama
struct tm tmp;
if (getLocalTime(&tmp, 15000)) {
Serial.println("Clock is valid");
} else {
Serial.println("NTP not reachable yet, will publish with timeSynced=false");
}
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(clientId.c_str())) {
Serial.println("MQTT connected");
} else {
Serial.printf("MQTT failed, state %d\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
clientId = "esp32-ntp-" + String((uint32_t)ESP.getEfuseMac(), HEX);
connectWifi();
startNtp();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setBufferSize(512);
connectMqtt();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (millis() - lastPublish < 5000) return;
lastPublish = millis();
// 1. Ölçümü ve zaman damgasını birlikte al
float temperature = 21.5f + (esp_random() % 300) / 100.0f;
char iso[32];
uint64_t epochMs = 0;
bool valid = isoTimestamp(iso, sizeof(iso), &epochMs);
// 2. Payload'ı oluştur
char payload[200];
if (valid) {
snprintf(payload, sizeof(payload),
"{\"ts\":\"%s\",\"tsMs\":%llu,\"timeSynced\":true,\"temperature\":%.2f}",
iso, (unsigned long long)epochMs, temperature);
} else {
snprintf(payload, sizeof(payload),
"{\"uptimeMs\":%lu,\"timeSynced\":false,\"temperature\":%.2f}",
millis(), temperature);
}
// 3. Yayınla
mqtt.publish(TOPIC, payload);
Serial.println(payload);
}
Sonucu görmek için bilgisayarınızdan subscribe olun:
mosquitto_sub -h broker.connectmq.com -p 1883 -i ts-viewer-8r2c -t "connectmq/demo/ntp8r2c/#" -v
Her satır {"ts":"2026-10-16T09:41:07.382Z","tsMs":1792143667382,"timeSynced":true,"temperature":22.71} gibi görünür. Broker'ın herkese açık alanı herkese açıktır ve test amaçlıdır; bu yüzden yalnızca demo değerleri yayınlayın. Sınırları herkese açık MQTT test broker'ı sayfasında listelenmiştir.
ESP32'de SNTP Nasıl Çalışır
configTime(0, 0, ...), lwIP SNTP istemcisini en fazla üç sunucu ve sıfır ofsetle başlatır; böylece sistem saati UTC'de çalışır. İlk yanıttan sonra ESP32 zamanı dahili zamanlayıcısıyla tutar ve varsayılan olarak saatte bir periyodik olarak yeniden eşitler. Bunu sntp_set_sync_interval(ms) ile değiştirebilirsiniz; birkaç dakikadan kısa bir süre telemetri için gereksizdir ve herkese açık NTP havuzlarına yük bindirir.
getLocalTime(&tm, timeoutMs) yıl makul bir değere ulaşana kadar bekler; bu, açılışta kullanışlı bir bloklayan kontroldür. sntp_set_time_sync_notification_cb ile kaydedilen callback her başarılı eşitlemede tetiklenir; sapmayı loglamak veya bir "saat tamam" durumu yayınlamak istiyorsanız işe yarar. Alttaki sistem saati API'si ESP-IDF sistem saati rehberinde açıklanmıştır.
Eşitlemeler arasındaki saat sapması
ESP32'nin ana kristali yeterince hassastır; saatlik eşitlemeler sıradan telemetri için sapmayı bir saniyenin oldukça altında tutar. Deep sleep farklıdır: varsayılan olarak RTC zamanlayıcısı çok daha fazla sapan dahili bir RC osilatörüyle çalışır. Cihazınız uzun süre uyuyorsa her uyanıştan sonra yeniden eşitleyin veya harici bir 32 kHz kristal takın.
Eşitlenmemiş Zaman Aralığını Yönetmek
En yaygın zaman damgası hatası, açılıştan hemen sonra 1970-01-01T00:00:12Z yayınlamaktır. Sketch bundan üç şekilde kaçınır:
- Biçimlendirmeden önce saati bilinen yakın tarihli bir epoch değeriyle (
VALID_EPOCH) karşılaştırır. - Saat geçerli değilse, tüketicinin ne yapacağına karar verebilmesi için
uptimeMsvetimeSynced:falsegönderir. - NTP erişilemez durumdaysa sonsuza kadar bloklamaz; böylece cihaz yine de raporlama yapar.
Bir tüketici, eşitlenmemiş mesajlar için kendi varış zamanına geri dönebilir veya onları atabilir. Cihazın asla bir zaman uydurmasına izin vermeyin.
Cihazda Yerel Saati Göstermek
Bazen cihazın kendisinin yerel saate ihtiyacı olur; örneğin bir röleyi 07:00'de anahtarlamak veya küçük bir ekranda saati göstermek için. Payload'ı UTC'de tutun ve yalnızca bu yerel kullanımlar için dönüştürün. ESP32'deki C kütüphanesi, yaz saati kurallarını da içeren POSIX saat dilimi string'lerini anlar:
// Otomatik yaz saatiyle Orta Avrupa Saati
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
tzset();
struct tm local;
time_t now = time(nullptr);
localtime_r(&now, &local);
char buf[32];
strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &local); // örn. 2026-10-16T11:41:07+0200
Ana sketch'teki gmtime_r, TZ değişkenini yok sayar; bu yüzden yerel gösterim için saat dilimi ayarlamak yayınladığınız UTC zaman damgalarını değiştirmez. Ofsetleri elle hesaplamak yerine bölgeniz için POSIX string'ini araştırın; sabit bir +02:00 ofseti Avrupa'nın çoğunda yılın yarısında yanlıştır.
Tamponlama ve Görevlerle Zaman Damgaları
Ölçümler kuyruğa alınmaya başladığında zaman damgaları vazgeçilmez hale gelir. Örneklemeyi ve yayını ESP32 FreeRTOS MQTT eğitimindeki gibi ayırıyorsanız, epochMs değerini alınma anında kuyruktaki struct'a kaydedin ve JSON'u yalnızca publish görevinde biçimlendirin. Böylece bir kesintiden sonra boşaltılan birikmiş veriler her grafikte doğru konuma yerleşir. Daha büyük payload'lar için JSON'u bir kütüphaneyle oluşturmak snprintf'ten daha az hataya açıktır; ESP32'de MQTT ile ArduinoJson yazısına bakın.
Olay tarzı mesajlar da bundan faydalanır. Gerçek bir zaman damgası içeren retained bir "son görülme" değeri, her yeni subscriber'a bir şeyin en son tam olarak ne zaman gerçekleştiğini söyler; PIR hareket sensörü projesi son hareket topic'i için bu deseni kullanır.
Sorun Giderme Kontrol Listesi
| Belirti | Olası neden | Çözüm |
|---|---|---|
| Payload'larda 1970 yılı | İlk eşitlemeden önce yayınlandı | Biçimlendirmeden önce VALID_EPOCH kontrolü yapın |
getLocalTime her zaman zaman aşımına uğruyor |
UDP 123 engelli | LAN'ınızda bir NTP sunucusu kullanın veya BT ekibinden izin isteyin |
| Zaman damgaları bir saat kaymış | Saat dilimi iki kez uygulanmış | Cihazı UTC'de tutun, yalnızca dashboard'larda dönüştürün |
Milisaniye her zaman .000 |
gettimeofday() yerine time() ile biçimlendirilmiş |
struct timeval kullanın |
| Deep sleep sonrası yanlış saat | Uyku sırasında RTC sapması | Uyanıştan sonra yeniden eşitleyin veya 32 kHz kristal ekleyin |
%llu anlamsız değer yazdırıyor |
Yanlış tür dönüşümü | unsigned long long türüne dönüştürün |
Production Kurulumu
Testin ötesine geçtiğinizde app.connectmq.com adresinde bir broker oluşturun, ISRG Root X1 sertifikasıyla 8883 portunda WiFiClientSecure kullanmaya geçin ve portaldan aldığınız YOUR_CLIENT_ID, YOUR_USERNAME ve YOUR_PASSWORD ile bağlanın. Eşitlenmiş saatin TLS için de iyi bir uygulama olduğunu unutmayın: sertifika geçerlilik kontrolleri ve log korelasyonu doğru bir saate dayanır. ConnectMQ portalının canlı topic gezgini, zaman damgalı mesajlarınızı geldikleri anda gösterir; bu da ts değeri geride kalan bir cihazı fark etmeyi kolaylaştırır.
ConnectMQ ile Başlayın
Doğru cihaz zaman damgaları ile güvenilir bir broker bir araya geldiğinde güvenebileceğiniz verilere sahip olursunuz. ConnectMQ; TLS, MQTT 3.1.1 ve 5.0 ile kredi kartı gerektirmeyen ücretsiz bir Serverless plan sunan bir yönetilen MQTT broker hizmetidir. Bir broker oluşturun, bu sketch'i kimlik bilgilerinizle yükleyin ve bunun üzerine inşa edilen sensör projeleri için ESP32 IoT eğitimi serisinin geri kalanını keşfedin.
Sık Sorulan Sorular
MQTT broker mesajlara zaman damgası ekler mi?
MQTT protokolünde zaman damgası alanı yoktur; bu yüzden subscriber'lar yalnızca varış zamanını bilir. Alınma zamanını, eşitlenmiş bir cihaz saatinden payload'a veya bir MQTT 5 user property'sine koyun.
ISO 8601 mi yoksa epoch milisaniye mi göndermeliyim?
UTC'de ISO 8601 okunabilirdir ve yaygın biçimde ayrıştırılır; epoch milisaniye ise kompakttır ve doğal olarak sıralanır. Birçok proje geliştirme sırasında ikisini birden gönderir ve production'da birini tutar.
ESP32 NTP ile ne sıklıkta yeniden eşitlenmeli?
Varsayılan saatlik yeniden eşitleme çoğu telemetri için yeterlidir. Uzun deep sleep sürelerinden sonra yeniden eşitleyin, çünkü uyku saati ana kristalden daha fazla sapar.
Cihazımın NTP için internet erişimi yoksa ne olur?
Yerel bir gateway veya router üzerinde bir NTP sunucusu çalıştırın ve configTime ayarını ona yönlendirin. Hiçbir zaman kaynağı yoksa uptime yayınlayın ve zaman damgasını varışta tüketicinin vermesine izin verin.



