🕰️ ساعت اینترنتی با ESP32: گرفتن ساعت دقیق از NTP به وقت تهران

✍️ تیم ووکوی فارسی ·

ساعت ساختن با میکروکنترلر معمولاً دو دردسر دارد: باید یک‌بار ساعت را دستی تنظیم کنی و بعد از چند روز می‌بینی چند دقیقه عقب افتاده است. ESP32 چون به اینترنت وصل می‌شود، این مشکل را ندارد: می‌تواند ساعت دقیق را از سرورهای NTP بگیرد. در این آموزش یک ساعت NTP با ESP32 می‌سازیم که با configTime به وقت تهران تنظیم می‌شود، ساعت و تاریخ را با getLocalTime می‌خواند و هر ثانیه در سریال مانیتور چاپ می‌کند.

💡 گرفتن ساعت از NTP به اینترنت واقعی نیاز دارد، پس این کد برای برد واقعی است. منطق ساعت را می‌شود در شبیه‌ساز هم تمرین کرد؛ کدش پایین‌تر آمده است.

NTP چطور کار می‌کند؟

NTP (Network Time Protocol) یک روش ساده است: برد از یک سرور زمان می‌پرسد «ساعت چند است؟» و سرور ساعت جهانی (UTC) را با دقت چند میلی‌ثانیه برمی‌گرداند. pool.ntp.org مجموعه‌ای از هزاران سرور داوطلب است که خودش نزدیک‌ترین را انتخاب می‌کند. ESP32 کتابخانه‌ی NTP را درون خودش دارد و لازم نیست چیزی نصب کنی. بعد از اولین جواب، ساعت داخلی برد خودش جلو می‌رود و تقریباً هر یک ساعت یک‌بار دوباره با سرور هماهنگ می‌شود.

منطقه‌ی زمانی تهران

سرور همیشه ساعت جهانی را می‌دهد و ما باید فاصله‌ی ایران را اضافه کنیم. تابع configTime سه چیز می‌گیرد: فاصله با UTC بر حسب ثانیه، مقدار ساعت تابستانی بر حسب ثانیه، و یک تا سه سرور.

پس دستور ما می‌شود configTime(12600, 0, "pool.ntp.org"). روش دیگر، رشته‌ی منطقه‌ی زمانی استاندارد است: configTzTime("<+0330>-3:30", "pool.ntp.org"). علامت منفی در این قالب عجیب به نظر می‌رسد ولی درست است؛ در استاندارد POSIX منفی یعنی شرق گرینویچ.

قطعه‌ها

کد کامل

// ESP32 internet clock: NTP time in Tehran (UTC+3:30, no daylight saving).
#include <WiFi.h>
#include <time.h>

const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASSWORD";

const long TEHRAN_OFFSET = 12600;   // 3.5 hours in seconds
const int DST_OFFSET = 0;           // Iran has no daylight saving since 2022
const char* days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println(" OK");

  configTime(TEHRAN_OFFSET, DST_OFFSET, "pool.ntp.org", "time.google.com");

  struct tm now;
  bool ok = false;
  Serial.print("Syncing time");
  for (int i = 0; i < 6 && !ok; i++) {
    ok = getLocalTime(&now);         // each try waits up to 5 s
    if (!ok) Serial.print(".");
  }
  Serial.println(ok ? " done" : " failed: no internet or NTP blocked?");
}

void loop() {
  static unsigned long last = 0;
  if (millis() - last < 1000) return; // once per second, without delay()
  last = millis();

  struct tm now;
  if (!getLocalTime(&now)) {
    Serial.println("Time not available yet");
    return;
  }

  char line[32];
  strftime(line, sizeof(line), "%Y-%m-%d  %H:%M:%S", &now);
  Serial.print(line);
  Serial.print("  ");
  Serial.println(days[now.tm_wday]);

  if (now.tm_min == 0 && now.tm_sec == 0) {
    Serial.println("Top of the hour!");
  }
}

توضیح بخش‌های مهم

فیلدهای struct tm

فیلدمعنینکته
tm_hour، tm_min، tm_secساعت، دقیقه، ثانیهساعت از ۰ تا ۲۳
tm_mdayروز ماهاز ۱ تا ۳۱
tm_monماه میلادیاز ۰ شروع می‌شود؛ برای عدد ماه یکی اضافه کن
tm_yearسالاز ۱۹۰۰ شمرده می‌شود؛ ۱۹۰۰ را اضافه کن
tm_wdayروز هفته۰ یعنی یکشنبه و ۶ یعنی شنبه

اگر یک عدد ساده برای ثبت زمان لازم داری، time(nullptr) تعداد ثانیه‌های گذشته از اول ۱۹۷۰ (Unix time) را می‌دهد. برای ذخیره‌ی داده‌ها یا فرستادن به سرور، این عدد از متن تاریخ راحت‌تر است. تاریخ شمسی هم از همین تاریخ میلادی با یک تابع تبدیل جلالی ساخته می‌شود که کتابخانه‌های آماده‌ی زیادی برایش هست.

نمایش روی صفحه و کار بدون اینترنت

چاپ در سریال مانیتور برای شروع کافی است، ولی ساعت واقعی صفحه می‌خواهد: یک نمایشگر سون‌سگمنت TM1637 یا یک OLED کوچک. اگر برد ممکن است بدون اینترنت روشن شود، NTP به‌تنهایی کافی نیست؛ ترکیب درست این است که ساعت را از NTP بگیری و در یک ماژول ساعت DS3231 بنویسی تا با باتری سکه‌ای خودش جلو برود. ساعت داخلی ESP32 در دیپ اسلیپ هم کار می‌کند، پس برد خوابیده بعد از بیدار شدن ساعت را دارد.

تمرین منطق ساعت در شبیه‌ساز

وای‌فای شبیه‌ساز به شبکه‌ی Wokwi-GUEST وصل می‌شود ولی به اینترنت واقعی و سرور NTP نمی‌رسد، پس getLocalTime آن‌جا جواب نمی‌دهد. ولی منطق ساعت را می‌شود جدا تمرین کرد: یک زمان شروع دستی، و شمردن ثانیه‌ها با millis. این کد روی آردوینو اونو و ESP32 کار می‌کند؛ روی برد واقعی بعداً به‌جای زمان شروع دستی، ساعت NTP را می‌گذاری.

// A clock that counts from a start time using millis().
// Runs in the simulator on Arduino Uno (free) or ESP32.
long startH = 14;   // set the start time by hand
long startM = 30;
long startS = 0;

void print2(long v) {
  if (v < 10) Serial.print("0");
  Serial.print(v);
}

void setup() {
  Serial.begin(115200);
}

void loop() {
  static unsigned long last = 0;
  if (millis() - last < 1000) return;
  last = millis();

  long total = startH * 3600 + startM * 60 + startS + millis() / 1000;
  total = total % 86400;   // wrap around at midnight
  long h = total / 3600;
  long m = (total / 60) % 60;
  long s = total % 60;

  print2(h);
  Serial.print(":");
  print2(m);
  Serial.print(":");
  print2(s);
  Serial.println();
}

نسخه‌ی کامل‌تر یک ساعت دیجیتال با نمایشگر در تینکرکد فارسی آماده است: پروژه‌ی ساعت دیجیتال که روی آردوینو اونو رایگان اجرا می‌شود و درس ساعت دیجیتال قدم‌به‌قدم توضیحش می‌دهد. نسخه‌ی با ماژول ساعت واقعی هم درس ساعت واقعی با RTC است؛ خواندنش رایگان است و اجرای ماژول RTC و برد ESP32 در شبیه‌ساز اشتراک Plus می‌خواهد.

اشکال‌های رایج

برای یادآوری اصول اتصال، وصل شدن ESP32 به وای‌فای را بخوان و اگر می‌خواهی ساعت را کنار دمای هوا روی یک صفحه‌ی وب نشان بدهی، به گرفتن داده از API با HTTPClient و پروژه‌های وای‌فای و اینترنت اشیا سر بزن.

منطق ساعت را در شبیه‌ساز امتحان کن ⏰

سؤال‌های رایج

عدد منطقه‌ی زمانی تهران در configTime چیست؟

ایران سه ساعت و نیم از ساعت جهانی جلوتر است، پس عدد اول ۱۲۶۰۰ ثانیه است. چون از سال ۱۴۰۱ ساعت رسمی ایران تابستان‌ها جلو کشیده نمی‌شود، عدد دوم (ساعت تابستانی) صفر است: configTime(12600, 0, "pool.ntp.org").

آیا ساعت NTP در شبیه‌ساز کار می‌کند؟

نه. وای‌فای شبیه‌ساز به شبکه‌ی Wokwi-GUEST وصل می‌شود ولی به اینترنت واقعی نمی‌رسد، پس سرور NTP جواب نمی‌دهد. منطق ساعت، یعنی شمردن ثانیه‌ها با millis و نمایش آن، را می‌توانی در شبیه‌ساز تمرین کنی.

اگر اینترنت بعد از همگام‌سازی قطع شود، ساعت چه می‌شود؟

ساعت داخلی ESP32 به کارش ادامه می‌دهد و فقط در روز چند ثانیه جلو یا عقب می‌افتد. هر وقت اینترنت برگردد، ESP32 خودش دوباره همگام می‌شود. ولی اگر برد ری‌استارت شود و اینترنت نباشد، ساعت از دست می‌رود؛ برای آن حالت ماژول DS3231 اضافه کن.

مقاله‌های مرتبط

🌍

گرفتن داده از اینترنت با ESP32: HTTPClient، خواندن JSON از API و HTTPS

🧠

FreeRTOS و دو هسته‌ی ESP32: اجرای چند کار هم‌زمان با xTaskCreatePinnedToCore

📡

ESP32 به وای‌فای وصل نمی‌شود؟ عیب‌یابی قدم به قدم با یک کد تشخیص

🐍

آموزش میکروپایتون از صفر: متغیر، شرط، حلقه، تابع، لیست و کار با پایه‌های برد

همه‌ی آموزش‌های ESP32 و اینترنت اشیا ← · همه‌ی مقاله‌ها