ESP32活用① ESP32とブラウザでお話しする(7)日時を表示する
ESP32は、暦時間を得る関数が利用できます。time()関数で、現在のシステム時間を得てtime_t型の変数に格納されます。この変数をlocaltime()関数で年月日時刻に変換し、tm型の構造体に格納します。
●Time_t型の変数
time()関数で得られる暦時間は、1900年1月1日0時0分0秒から経過時間の秒数で示されます。この暦時間には32ビット・エリアが確保されています。
time_t 型の変数 |
このtime_t型の変数は、次のように定義します。
time_t t ; // t は日時を格納する変数名 |
この設定されたtime_t型の変数t に、システムの現在時間を取り込むためのtime()関数が用意されています。戻り値はtime_t型の現在の時刻、基準からの経過秒数となります。データが得られないときは -1となります。引数は得られた日時を格納するtime_t型の変数のアドレスを渡します。
time_t time(&t) |
time_t型の変数は、4バイトの整数で符号付きの場合 -2,147,483,648~2,147,483,647の範囲の値となり、2038年1月19日3時14分7秒までしか表現できません。符号なしの整数の場合は0~4,294,967,295の値となり、2106年2月7日6時28分15秒まで表現できます。実時間とは閏秒があるために少し変わります。
このtime_t型はC言語では符号付きの32ビットで定義されていました。そのため、2038年問題という課題を抱えています。Arduino ESP32で設定されているtime_tは4バイトで正負の符号がある整数です。2038年問題の対象となりますが、当面は問題ありません。10年後くらいから対応を考えればと思います。
●日時の各要素を構造体のメンバとして参照できるstruct tm型
time_t型の変数に格納された現在時刻の値は、32ビットの符号付きの整数です。そのままでは、日時がどのようなものかわかりません。そのため、localtime()関数で年月日時分秒などをメンバとした構造体が次のように設定されています。
struct tm { int tm_sec; // 秒 - (0~61) 閏秒を考慮 int tm_min; // 分 - (0~59) int tm_hour; // 時 - (0~23) int tm_mday; // 日 - (1~31) int tm_mon; // 1月からの月数 - (0~11) int tm_year; // 1900年からの年数 int tm_wday; // 日曜日からの日数 - (0~6) int tm_yday; // 1月1日からの日数 - (0~365) int tm_isdst; //夏時間フラグ } |
各メンバの()内の値は格納されている値を示します。tm_yearは経過年数、夏時間フラグは国内では夏時間を使用していないので0が設定されています。
構造体tmに当地の時間をセットするには、次のように行います。
tm=localtime(&t) |
●実際の使用方法
char pdata[80]; static const char *wd[7] = {"Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat"}; time_t t; // time_t 型の変数を定義する struct time_tm *tm; // localtime()変換した時間をセットする構造体を設定する |
ここまでで変数、構造体などの定義を終えます。
time(&t); // システム・クロックによりtime_t型変数tに日時をセットする tm=localtime(&t); // tm構造体に日時が参照可能なメンバとしてセットする snprintf(ar,80,"%04d/%02d/%02d(%s) %02d:%02d:%02d:", tm->tm_year+1900,tm->tm_mon+1, tm->tm_mday, wd[tm->tm_wday], tm->tm_hour, tm->tm_min, tm->tm_sec); // tmから読み取り可能な文字列にする // tm構造体の各メンバを文字列にする関数asctime(tm)も用意されている。後で説明 Serial.println(ar); // シリアルモニタに表示 |
これで時刻の処理に関する基本的な流れが把握できると思います。
ただし、時刻合わせの処理をまだ説明していません。ESP32には時刻合わせ関数configTime()も用意されています。時刻合わせが必要になった時点で追加説明します。
前回の温度表示、LEDのON/OFFをWebで行うページの最初に現在時刻を表示する処理を追加します。
●前回のプログラムに追加した部分
●この処理を行うために定義された変数
No15行からNo17行とNo5行に、今回利用する変数、定数を定義しています。time_t型変数(No15行)、tm型の構造体(No16行)、曜日を示す0から6の値から曜日名を得るための配列(No16行)と、時刻合わせのため必要な標準時を得るときに使用する日本と世界標準時(協定世界時UCT)との時差をJST(No5行)として追加定義しています。
●時刻を得るための処理
時刻を得るための処理は、Webブラウザに送るデータを作るhandleRootmsg()関数内のNo63行からNo65行に記述します。time(&t)(No63行)はシステム・クロックから時刻を得る関数です。time_t型変数のtの先頭アドレス(ポインタ)が引数となります。No65行で経過時間秒数の日時をtm型構造体として設定されたtmにセットします。
構造体tmに得られたデータを表示のための文字データにセットする処理は、snprintf()関数の引数に設定する書式の文字列(No76、77行)に追加データの書式をセットし、構造体から取り出した対応するデータをNo85、86行でセットします。
time()関数で日時を取り出しlocaltime()関数で日時の各項目に分け、その結果を前回作成したページに追加した結果です。
基準の日付は1970年1月1日0時0分0秒となっていて、ESP32が起動した時刻からの経過時間から日時を算出しています。そのためESP32が起動したときが1970年1月1日0時0分0秒となります。時計が止まり電池を入れ替えたとき同じように時刻合わせが必要になります。
●時刻合わせ
電波時計で時刻合わせを行うように、インターネット上にも時刻合わせのためのサーバが用意されています。今回は情報通信研究機構が用意した公開NTPサーバを利用します。
NTPサーバ名 ntp.nict.jp |
上記のサーバにアクセスできない場合の代替えとして「インターネットマルチフィード株式会社」が提供する公開サーバも設定しています。
ntp.jst.mfeed.ad.jp |
●ESP32で用意されている時刻合わせの関数
ESP32では時刻合わせの関数として次に示すconfigTime()関数が用意されています。引数として次の3種類が必要です
① gmtOffset_sec GMT(グリニッジ平均時)とローカル時刻(日本の標準時)との差(単位は秒)。時差9時間を秒の単位で設定 9×3600
② daylightOffset_sec 夏時間で進める時間(単位は秒)。国内では夏時間は使用しないので0を設定
③ server1, server2, server3 NTPサーバ。最低一つ設定する。server1との時刻同期に失敗した場合、server2、server3の順に時刻同期を試みる。ここではntp.nict.jpを第1、ntp.jst.mfeed.ad.jpを第2のサーバとして設定しました。
具体的な記述は次のNo150行に記述しています。
●時刻合わせをすると
今回は、プログラムの起動時にのみ呼び出されるsetup()関数の最後に記入しています。そのため、時刻合わせはプログラムの起動時の1回のみとなります。そのため長時間稼働し正確な時刻を必要とするシステムなどでは定期的に時刻合わせを行います。ここではテストなので初回のみとしました。
結果を次に示します。
tm型構造体の変数から直接日時を読み取ることが可能な文字列に変換する関数が用意されています。
char *ap; ap=asctime(tm)関数 |
ポインタで設定した文字列apに可視化された日時がセットされます。char *ap;はプログラムの先頭の変数などの定義部に、asctime(tm)関数は次に示すようにNo66行に追加記入してあります。
snprintf()関数の書式の定義部は、No76行に文字列であることを指定する%Sを設定します。
表示データの指定は、No84行で指定しています。シンプルなものになっています。
表示結果、国内の表示と順番が異なりますが、曜日、月、日、時、分、秒、年の順に表示されます。
年月日標準はやはり見なれたほうがすぐにわかります。また、時間のみ、年月日のみの表示の場合もあるので、個別にtm構造体の日時の各メンバにアクセスする方法も把握しておいてください。
(2019/12/9 V1.0)
<神崎康宏>