ESP32でNTPClientライブラリを使って現在時刻を取得する方法

はじめに
みなさん、マイコンボードでの現在時刻の取得ってどのようになさってますでしょうか? Arduino UNOなどのインターネットに繋がらないデバイスでしたら、リアルタイムクロック(水晶発振器内蔵)を使って時間管理します。
ESP32を使う場合ならWiFiでインターネット接続して、NTPサーバーで時刻合わせが便利でしょう。ここではNTPClientライブラリを使ってESP32で現在時刻を取得する方法をご紹介いたします。また、NTPClientライブラリではできない、UNIXエポックタイムから
2022-10-31T19:00:48Z
のような時間フォーマットに変換するプログラムもご紹介いたします。
開発環境
この記事の執筆時では、次の開発環境でプログラムを実行テストしました。
項目 | 値 |
---|---|
Arduino | ESP32-DevKitC |
IDE | Platform IO |
ファームウェア | arduino-esp32#2.0.2 |
NTPClient | 3.2.1 |
NTPClientライブラリのインストール
下記ページで公開されているNTPClientライブラリを使います。
https://github.com/arduino-libraries/NTPClient
ライブラリマネージャーなどからインストールします。私の場合は
NTPClient.h
と NTPClient.cpp
ファイルを直接プロジェクト配下に置きました。
ESP32でNTPClientライブラリを使って現在時刻を取得する
それではさっそく、NTPClientを使って現在時刻を取得してみましょう。次のソースコードは、ESP32でNTPClientライブラリを使って現在時刻を取得するプログラムです。
#include <WiFi.h>
#include <WiFiUdp.h>
#include "Arduino.h"
#include "NTPClient.h"
#ifndef WIFI_SSID
#define WIFI_SSID "xxxxx" // WiFi SSID (2.4GHz only)
#endif
#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "xxxxx" // WiFiパスワード
#endif
;
WiFiUDP udp(udp, "ntp.nict.jp", 32400,
NTPClient ntp60000); // udp, ServerName, timeOffset, updateInterval
void setup() {
.begin(115200);
Serial
.mode(WIFI_STA);
WiFi(500);
delay.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi
while (WiFi.status() != WL_CONNECTED) {
(500);
delay.print(".");
Serial}
.begin();
ntp}
void loop() {
.update();
ntp
unsigned long epochTime = ntp.getEpochTime();
= ntp.getFormattedTime(); // hh:mm:ss
String formattedTime
.print(epochTime);
Serial.print(" ");
Serial.println(formattedTime);
Serial
(1000);
delay}
端末で実行すると次のようにシリアルモニタに表示されるはずです。
1667244750 19:32:30
1667244751 19:32:31
1667244752 19:32:32
1667244753 19:32:33
1667244754 19:32:34
1667244755 19:32:35
NTPClient ntp(udp, "ntp.nict.jp", 32400, 60000);
では、UDPクライアントを突っ込み、NTPサーバーを日本の ntp.nict.jp
に指定、日本の時差32400秒(9時間 x
3600秒)でオフセット指定、最後にNTPサーバーへ接続して時間を修正する更新頻度(60秒に1回)を指定してます。メインループ
loop()
内で必ず ntp.update()
ハンドラを回します。
次の画像のように NTPClient.cpp
ファイルに
DEBUG_NTPClient
のマクロ定義を追加すれば、NTPサーバーにアクセスされているかどうかデバッグできます。
NTPサーバーへ接続できれば、 getEpochTime()
で正確なエポック秒(UNIX時間)を取得できます。エポック秒とはご存じの通り、1970年1月1日午前0時0分0秒を0秒とした経過秒数ですよね。C言語のtime_t
型など32ビット定義だと、西暦2038年1月19日3時14分7秒(UTC)を過ぎると整数型が飽和して、正しい時刻が表示されなくなる問題が懸念されてます(2038年問題)。ESP32も32ビットなので、気になるところです。
ESP32
time_t
型を追ってみると、どうもlong
が使われているようです。大丈夫なのかな?^^;
NTPClientのエポック秒では unsigned long
ですから、最大値が 4294967295
ということで2106年までは大丈夫そうです。
さて getFormattedTime()
では「時:分:秒」を取得します。
現在時刻を「西暦-月-日 時:分:秒」のフォーマットで表現したい
さて、先ほどのプログラムでは年月日が表現されてませんでした。実はNTPClientライブラリ(3.2.1)には年月を取得できる関数が実装されていません。ですから関数を作って自力で実装する必要があります。
日は getDay()
で取得できますね。なぜ年と月も取得できるようにしてくれないのでしょうね?手元にあるNTPClientライブラリの別バージョンでは
getFormattedDate()
という関数が実装されてまして、年月日も取得できるんですよね。ただそのライブラリ入手先が不明になってしまったので、先ほどのプログラムに関数を追加する形で「西暦-月-日
時:分:秒」のフォーマットで表現してみます。
次のソースコードは、エポック秒を元に現在時刻を「西暦-月-日 時:分:秒」のフォーマットで表現するプログラムです。
#include <WiFi.h>
#include <WiFiUdp.h>
#include "Arduino.h"
#include "NTPClient.h"
#ifndef WIFI_SSID
#define WIFI_SSID "xxxxx" // WiFi SSID (2.4GHz only)
#endif
#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "xxxxx" // WiFiパスワード
#endif
#define LEAP_YEAR(Y) ((Y > 0) && !(Y % 4) && ((Y % 100) || !(Y % 400)))
;
WiFiUDP udp(udp, "ntp.nict.jp", 32400,
NTPClient ntp60000); // udp, ServerName, timeOffset, updateInterval
(unsigned long secs) {
String getFormattedTimeunsigned long rawTime = secs;
unsigned long hours = (rawTime % 86400L) / 3600;
= hours < 10 ? "0" + String(hours) : String(hours);
String hoursStr
unsigned long minutes = (rawTime % 3600) / 60;
= minutes < 10 ? "0" + String(minutes) : String(minutes);
String minuteStr
unsigned long seconds = rawTime % 60;
= seconds < 10 ? "0" + String(seconds) : String(seconds);
String secondStr
return hoursStr + ":" + minuteStr + ":" + secondStr;
}
/**
* @brief エポックタイムを元に現在時刻を整形して返す(ex: 2022-10-31T19:00:48Z)
*
*/
(unsigned long secs) {
String getFormattedDateunsigned long rawTime = secs / 86400L; // in days
unsigned long days = 0, year = 1970;
uint8_t month;
static const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
while ((days += (LEAP_YEAR(year) ? 366 : 365)) <= rawTime) year++;
-=
rawTime - (LEAP_YEAR(year)
days ? 366
: 365); // now it is days in this year, starting at 0
= 0;
days for (month = 0; month < 12; month++) {
uint8_t monthLength;
if (month == 1) { // february
= LEAP_YEAR(year) ? 29 : 28;
monthLength } else {
= monthDays[month];
monthLength }
if (rawTime < monthLength) break;
-= monthLength;
rawTime }
=
String monthStr ++month < 10 ? "0" + String(month) : String(month); // jan is month 1
= ++rawTime < 10 ? "0" + String(rawTime)
String dayStr : String(rawTime); // day of month
return String(year) + "-" + monthStr + "-" + dayStr + "T" +
(secs) + "Z";
getFormattedTime}
void setup() {
.begin(115200);
Serial
.mode(WIFI_STA);
WiFi(500);
delay.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi
while (WiFi.status() != WL_CONNECTED) {
(500);
delay.print(".");
Serial}
.begin();
ntp}
void loop() {
.update();
ntp
unsigned long epochTime = ntp.getEpochTime();
= getFormattedDate(epochTime);
String formattedDate
.print(epochTime);
Serial.print(" ");
Serial.println(formattedDate);
Serial
(1000);
delay}
コピペして流用した関数ですので関数の中身は追えてませんが、次のようにキレイに年月日も表現してくれます。他のフォーマットに変えるのもそれほど難しくはないでしょう。
Update from NTP Server
1667246120 2022-10-31T19:55:20Z
1667246121 2022-10-31T19:55:21Z
1667246122 2022-10-31T19:55:22Z
1667246123 2022-10-31T19:55:23Z
1667246124 2022-10-31T19:55:24Z
LEAP_YEAR
はうるう年を考慮した日付を出すマクロ関数です。うるう年の計算式って意外とシンプルなんですねー。
NTPサーバーの仕組み
最後に、NTPサーバーの仕組みについて少し触れておきます。 NTPとは「Network Time Protocol」の略です。HTTPやTCPのように通信プロトコル、つまり約束事や取り決めのことですね。NTPサーバーでは原子時計を元に正確な時間を取得してます。端末からNTPサーバーに接続して現在時刻を取得するわけですが、ここで問題があります。ネットワーク接続による遅延の問題ですです。 ネットワーク環境によっては遅延が生じますので、いくらNTPサーバーが正確な時刻を刻んでいても、手元に届いた時は時間がずれている可能性があります。実は先ほどの「Network Time Protocol」と呼ばれる取り決めごとには、ネットワークの遅延も計測して補正しているんです。また、サーバー自身の処理時間も引き算したりと、よく考えられて実現されているんですね。ですから現在時刻をある程度正確に取得できるというわけですが、誤差がまったく無いわけではありません。そこはインターネット社会の宿命でしょうか。
関連記事
- ESP32でWiFi接続を簡単にする「WiFiHelper」ライブラリ開発のリリースノート
- 【Raspberry Pi】Ambientへセンサデータを送信してグラフ化
- ESP32(Arduino)で擬似マルチスレッド可能な「PollingTimer」ライブラリ開発のリリースノート
- 【Arduino】リアルタイムクロック(DS3231)で現在時刻の表示
- ダイソーのリモコンシャッターをESP32で通信するためのヘルパー関数