#topicpath

** ESP32 で NTP の情報を使って時計を合わせる [#x40ba5ef]
 これではうまくいかないことがわかりました。通信が安定せず、時刻のデータを更新しなくなるようです。
 現在、対策を検討中。
 2019-06-18 (火) 09:36:15
[[../ezTime]]を参照のこと。
[[ezTime>../ezTime]]を参照のこと。
- 使い方~
ESP32が使える Arduino IDE の環境下で、次のプログラムの他に、以下の二つ~
のファイル(NTPClient.h, NTPClient.cpp) を、それぞれ新しいタブで書き込んで~
実行する。~
もともと、github に掲載されていた [[NTPClient ライブラリ>https://github.com/arduino-libraries/NTPClient]]を改変したもの。~
~
ESP32とNTPについては、[[こちら>電子工作/ESP32#ld460607]]も参照してください。~
~
-- サンプルプログラム~
 #include <WiFi.h>
 #include <time.h>
 
 #include <WiFiUdp.h>
 #include "NTPClient.h"
 
 const char* ssid       = "xxxxxxxxxxxxx";
 const char* password   = "xxxxxxxxxxxxx";
 const char* ntpServer = "ntp.jst.mfeed.ad.jp";
 
 WiFiUDP ntpUDP;
 NTPClient ntpc(ntpUDP, ntpServer, long(3600*9), 30000); // 30秒で更新
 
 void printLocalTime()
 {
   struct tm timeinfo;
   if(!getLocalTime(&timeinfo)) return;
   Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
 }
 
 void setup()
 {
     // --------------------------------------- シリアル通信
     Serial.begin(115200);
   
     // --------------------------------------- WiFi
     Serial.printf("Connecting to %s ", ssid);
     WiFi.begin(ssid, password);
     while (WiFi.status() != WL_CONNECTED) {
         delay(500);
         Serial.print(".");
     }
     Serial.println(" CONNECTED");
   
     // --------------------------------------- NTP : タイムゾーンを環境変数で設定する。
     setenv("TZ","JST-9",1);
 }
 
 void loop()
 {
   delay(100);
 
   // NTPClient : update() の頻度を高める。ただし、冒頭で設定した時間間隔以下では、実際には実行されない。
   ntpc.update();
   printLocalTime();
 }
~
--- ポイント~
 NTPClient ntpc(ntpUDP, ntpServer, long(3600*9), 30000); // 30秒で更新
3600*9 は日本時間(+9時間)に対応している。更新時間間隔はミリ秒で与える。30000[ms] = 30[s]。
 setenv("TZ","JST-9",1);
関数get Local Time()で日本時間が反映されるようになる。
 ntpc.update();
頻繁にこれを実行する。実際には更新時間間隔内だと更新は実行されない。~
実行しないと更新されない。~
~
-- NTTClient.h
 #pragma once
 
 #include "Arduino.h"
 
 #include <Udp.h>
 
 #define SEVENZYYEARS 2208988800UL  // 70年間の秒数
 #define NTP_PACKET_SIZE 48
 #define NTP_DEFAULT_LOCAL_PORT 1337
 
 class NTPClient {
   private:
     UDP*          _udp;
     bool          _udpSetup       = false;
 
     const char*   _poolServerName = "pool.ntp.org"; // Default time server
     int           _port           = NTP_DEFAULT_LOCAL_PORT;
     long          _timeOffset     = 0;
 
     unsigned long _updateInterval = 60000;  // In ms : 60000ms means 1min.
 
     unsigned long _currentEpoc    = 0;      // In s
     unsigned long _currentEpoc_us = 0;      // In us
     unsigned long _lastUpdate     = 0;      // In ms    
 
     byte          _packetBuffer[NTP_PACKET_SIZE];
 
     void          sendNTPPacket();
 
   public:
     /* ----- コンストラクタ ---------------------------------------- */
     /*   UDP オブジェクト、サーバー名、オフセット、更新時間間隔 */
     NTPClient(UDP& udp);
     NTPClient(UDP& udp, long timeOffset);
     NTPClient(UDP& udp, const char* poolServerName);
     NTPClient(UDP& udp, const char* poolServerName, long timeOffset);
     NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval);
 
     /* -- 開始 ---------------------------------------------------- */
     void begin();
     void begin(int port);
 
     /* -- 更新 ---------------------------------------------------- */
     /* 成否を返す。デフォルトで60秒で更新 */
     bool update();
 
     /* -- 強制更新 ------------------------------------------------ */
     bool forceUpdate();
 
     /* -- 時刻抽出 ------------------------------------------------ */
     int getDay() /* const */;
     int getHours() /* const */;
     int getMinutes() /* const */;
     int getSeconds() /* const */;
 
     /* -- UNIX Epoch 表示 ----------------------------------------- */
     unsigned long getEpochTime() /*const*/;
 
     /* -- 時刻表示 ------------------------------------------------ */
     String getFormattedTime() /* const */;
 
     /* -- オフセットの表示 ---------------------------------------- */
     void setTimeOffset(int timeOffset);
 
     /* -- NTP サーバの指定 ---------------------------------------- */
     void setPoolServerName(const char* poolServerName);
 
     /* -- 更新間隔再設定 ------------------------------------------ */
     void setUpdateInterval(unsigned long updateInterval);
 
     /* -- 終了 ---------------------------------------------------- */
     void end();
 };
~
~
-- NTPClient.cpp
 /**
  * The MIT License (MIT)
  * Copyright (c) 2015 by Fabrice Weinberg
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  * The above copyright notice and this permission notice shall be included in all
  * copies or substantial portions of the Software.
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
  */
 
 /* #define DEBUG_NTPClient */
 
 /* ---- システムが持っている時間をセットする。 ---- */
 /* ESP32 配布ファイルの中の lwipopts.h を参照した。また、UNIX の settimeofday() も参照した */
 #include <time.h>      // 多分必要
 #include <unistd.h>    // settimeofday() に必要
 #include <sys/time.h>  // struct timeval に必要
 
 #include "NTPClient.h"
 
 /* ----- private 関数 ------------------------------------------ */
 
 void NTPClient::sendNTPPacket() {
   // NTP は、48バイトのデータをやり取りする。初期にそれらをゼロにする。
   memset(this->_packetBuffer, 0, NTP_PACKET_SIZE);
 
   // Initialize values needed to form NTP request
   // 参照URL : https://milestone-of-se.nesuke.com/I7protocol/ntp/ntp-format/ )
   this->_packetBuffer[0] = 0b11100011;   // LI(Leap Indicator):2bit:11(不明),
                                          // Version:3bit:100:4(現行バージョン4), Mode:3bit:3(クライアント)
   this->_packetBuffer[1] = 0;            // Stratum, 0 としても問題ない。
   this->_packetBuffer[2] = 6;            // Polling Interval 6-> 2^6 = 64秒 次のNTPパケット送出までの最大時間間隔
   this->_packetBuffer[3] = 0xEC;         // Peer Clock Precision : 2^(-20?) マイクロ秒まで?
   // 8 bytes of zero for Root Delay & Root Dispersion
   this->_packetBuffer[12]  = 49;         // この辺は、サーバーとの前回のやり取りにかかった時間などの情報。
   this->_packetBuffer[13]  = 0x4E;
   this->_packetBuffer[14]  = 49;
   this->_packetBuffer[15]  = 52;
 
   // all NTP fields have been given values, now
   // you can send a packet requesting a timestamp:
   this->_udp->beginPacket(this->_poolServerName, 123); //NTP requests are to port 123
   this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE);
   this->_udp->endPacket();
 }
 
 /* ----- コンストラクタ ---------------------------------------- */
 /*   UDP オブジェクト、サーバー名、オフセット、更新時間間隔 */
 NTPClient::NTPClient(UDP& udp) {
   this->_udp            = &udp;
 }
 
 NTPClient::NTPClient(UDP& udp, long timeOffset) {
   this->_udp            = &udp;
   this->_timeOffset     = timeOffset;
 }
 
 NTPClient::NTPClient(UDP& udp, const char* poolServerName) {
   this->_udp            = &udp;
   this->_poolServerName = poolServerName;
 }
 
 NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) {
   this->_udp            = &udp;
   this->_timeOffset     = timeOffset;
   this->_poolServerName = poolServerName;
 }
 
 NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) {
   this->_udp            = &udp;
   this->_timeOffset     = timeOffset;
   this->_poolServerName = poolServerName;
   this->_updateInterval = updateInterval;
 }
 
 /* -- 開始 ---------------------------------------------------- */
 void NTPClient::begin() {
   // 引数無しなら、次の bigin()
   this->begin(NTP_DEFAULT_LOCAL_PORT);
 }
 
 void NTPClient::begin(int port) {
   this->_port = port;
   this->_udp->begin(this->_port);
   
   // UDP のセットアップができていることの識別子
   this->_udpSetup = true;
 }
 
 /* -- 更新 ---------------------------------------------------- */
 bool NTPClient::update() {
   // 実際には、どのような状況で forceUpdate() を呼ぶか、を判定し実行する関数。
   if (
     // 1. 前回更新後、updateInterval 以上の時間が経過している場合
     (millis() - this->_lastUpdate >= this->_updateInterval)   ||
     // 2. millis() があふれて戻ってしまった場合
     (millis() < this->_lastUpdate)   ||
     // 3. 初回
     (this->_lastUpdate == 0)) {
       if (!this->_udpSetup) this->begin();   // 初回でUDPのセットアップができていないとき
       return this->forceUpdate();
   }
   return true;
 }
 
 /* -- 強制更新 ------------------------------------------------ */
 bool NTPClient::forceUpdate() {
   #ifdef DEBUG_NTPClient
     Serial.print("Update from NTP Server");
   #endif
 
   // --------------------- データ送信 ---------------------
   this->sendNTPPacket();
 
   // --------------------- データ受信 ---------------------
   // タイムアウト(10ms×100回=1秒)まで受信を待つ
   // 受信したパケットのサイズ?が値をもったら受信したことになる
   byte timeout = 0;
   int  cb = 0;
   do {
     delay ( 10 );
     cb = this->_udp->parsePacket();
     if (timeout > 100) return false; // タイムアウト時は false で終わり。
     timeout++;
   } while (cb == 0);
 
   // -------------- 更新時刻はパケット送信時 --------------
   // タイムアウトしたらここまで来ない。
   this->_lastUpdate = millis() - (10 * (timeout + 1));
 
   // -------------------- パケット解析 --------------------
   // cf. http://www.venus.dti.ne.jp/~yoshi-o/NTP/NTP-SNTP_Format.html 
   // Transmit Timestamp まで読み込む(48バイト)
   // 40-43 の 4tyte, 32bit が UNIX Epoch
   // 44-47 の 4byte, 32bit が マイクロ秒
   this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE);
 
   // 上位32bit
   unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); // 2 byteで16bit。
   unsigned long lowWord  = word(this->_packetBuffer[42], this->_packetBuffer[43]);
   // combine the four bytes (two words) into a long integer : 上記の word は 2バイト=16bit。
   // this is NTP time (seconds since Jan 1 1900)            : 上記の 32bit が 1900年からの秒数。
   unsigned long secsSince1900 = highWord << 16 | lowWord;
 
   // NTP は、1900年からの秒数を数える。一方、UNIX Epoch は、1970年からの秒数を数える。換算する。
   // _currentEpoc がこのライブラリが保持する時刻。
   this->_currentEpoc = secsSince1900 - SEVENZYYEARS;
 
   // 下位32bit : 固定小数点なので、2^32 = 4294967296 で割る。
   highWord = word(this->_packetBuffer[44], this->_packetBuffer[45]);
   lowWord  = word(this->_packetBuffer[46], this->_packetBuffer[47]);
   this->_currentEpoc_us = int(double( highWord << 16 | lowWord)/ 4294967296 * 1000 * 1000);
 
   struct timeval  tv = { .tv_sec = this->_currentEpoc, .tv_usec = this->_currentEpoc_us };
   // struct timezone tz = { .tz_minuteswest = int(this->_timeOffset/60), .tz_dsttime=0}; 
   // settimeofday(&tv, &tz);  // 設定しても効かなかった。
   settimeofday(&tv, NULL);
 
 #ifdef DEBUG_NTPClient
     Serial.print("  ");
     Serial.print(timeout);
     Serial.print("  ");
     Serial.println(this->_currentEpoc_us);
 #endif
 
   return true;
 }
 
 /* -- 時刻抽出 ------------------------------------------------ */
 int NTPClient::getDay() /* const */ {
   return (((this->getEpochTime()  / 86400L) + 4 ) % 7); //0 is Sunday
 }
 int NTPClient::getHours() /* const */ {
   return ((this->getEpochTime()  % 86400L) / 3600);
 }
 int NTPClient::getMinutes() /* const */ {
   return ((this->getEpochTime() % 3600) / 60);
 }
 int NTPClient::getSeconds() /* const */ {
   return (this->getEpochTime() % 60);
 }
 
 /* -- UNIX Epoch 計算 ----------------------------------------- */
 unsigned long NTPClient::getEpochTime() /*const*/ {
 
   // millis() は50日で溢れる。リセットされたときは、updateする。これを入れたので 全部 const を外す。
   if( millis() < this->_lastUpdate ){
       this->forceUpdate();
   }
   return this->_timeOffset +                      // タイムゾーンの offset
          this->_currentEpoc +                     // 前回の結果
          ((millis() - this->_lastUpdate) / 1000); // 前回からの経過時間
 }
 
 /* -- 時刻表示 ------------------------------------------------ */
 String NTPClient::getFormattedTime() /* const */ {
   unsigned long rawTime = this->getEpochTime();
   unsigned long hours = (rawTime % 86400L) / 3600;
   String hoursStr = hours < 10 ? "0" + String(hours) : String(hours);
 
   unsigned long minutes = (rawTime % 3600) / 60;
   String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes);
 
   unsigned long seconds = rawTime % 60;
   String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds);
 
   return hoursStr + ":" + minuteStr + ":" + secondStr;
 }
 
 /* -- オフセットの表示 ---------------------------------------- */
 void NTPClient::setTimeOffset(int timeOffset) {
   this->_timeOffset     = timeOffset;
 }
 
 /* -- NTP サーバの指定 ---------------------------------------- */
 void NTPClient::setPoolServerName(const char* poolServerName) {
     this->_poolServerName = poolServerName;
 }
 
 /* -- 更新間隔再設定 ------------------------------------------ */
 void NTPClient::setUpdateInterval(unsigned long updateInterval) {
   this->_updateInterval = updateInterval;
 }
 
 /* -- 終了 ---------------------------------------------------- */
 void NTPClient::end() {
   this->_udp->stop();
 
   this->_udpSetup = false;
 }
~
~
- コメント~
-- NTPの情報を使っているが、NTP ではない。~
単に、NTP の情報を使って時間を合わせているだけ。パケットの往復にかかる時間~
などは考えていない。~
~
-- millis()~
millis() が 50日程度で溢れてしまうことは、気をつけなければいけないと思うが、~
あまりそのような記述を見ないのは、長期間観測させることが少ないからか?~
一応、配慮したが、実際には試していない。~
~
- 参照URL
-- NTP (Network Time Protocol) について
--- [[NTPのパケットフォーマット>https://milestone-of-se.nesuke.com/I7protocol/ntp/ntp-format/]]
--- [[NTP、SNTP のフォーマット>http://www.venus.dti.ne.jp/~yoshi-o/NTP/NTP-SNTP_Format.html]]
-- NTPClient ライブラリについて
--- [[github>https://github.com/arduino-libraries/NTPClient]]
-- ESP32の時間の扱いについて
--- [[サンプルプログラムSimpleTime>https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Time/SimpleTime/SimpleTime.ino]]
--- [[lwipopts.h>https://github.com/espressif/arduino-esp32/blob/96822d783f3ab6a56a69b227ba4d1a1a36c66268/tools/sdk/include/lwip/lwipopts.h]]~
settimeofday()が使われている。
--- [[sntp_opts.h>https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/lwip/lwip/apps/sntp_opts.h]]~
SNTP_UPDATE_DELAY で更新時間を設定している。

トップ   編集 差分 添付 複製 名前変更 リロード   新規 検索 最終更新   ヘルプ   最終更新のRSS