06.Wio TerminalでWi-Fiから時刻取得


06.Wio TerminalでWi-Fiから時刻取得

Wi-Fi経由でNTP時刻を所得し表示するスケッチです。NTP (Network Time Protocol)はネットにつながれた機器が正確な時刻を共有するために使われるプロトコルです。時刻同期に協定世界時(UTC)を基準にしています。日本時刻は、協定世界時を9H進めたもの(UTC+9)で、日本標準時(JST)と呼ばれます。スケッチは
Seeed Wiki > Platform > Wio Terminal > Network > Wi-Fi の
https://wiki.seeedstudio.com/Wio-Terminal-Wi-Fi/
に出ているWi-Fi NTPサンプルコードを少し変更して日本語で画面表示しました。元の例では、UDPを使用してNTP時刻を12時間ごとに取得し、メインチップに組込まれているRTCを使用して時刻を最新の状態に保ちますが、時計のように表示時刻は更新していません。また、表示先はシリアルモニタです。
UDP(User Datagram Protocol)は、ネットで一般的に使用されているプロトコルの1つです。TCP等と同様に、IPの上位プロトコルとして使用されます。一方的にメッセージを送るプロトコルで、TCPに比べて通信エラーへの耐性は低く、信頼性は高くありませんが、スピードやリアルタイム性が求められる通信に向いています。
RTC(real-time clock)は、内蔵時計です。Wio Terminalのメインチップ(SAMD51)内に組込まれている時計機能を使用していて、バッテリーバックアップはしていません。
サンプルからの主な変更点は、
・NTPサーバーはいくつもありその1つの情報通信研究機構の"ntp.nict.jp"を使用しました。
・起動時のみ1度受信し、以後内部RTCを1分ごとに表示するので誤差は最大1分程度あります,
・オフセット時間を+8H(中国)から+9H(JST)に修正。
注意) Wio Terminalでは、電源offで時刻は保持せず、2000/1/1 00:00:00になってしまいます。

RSSI

RSSI(Received Signal Strength Indicator)は、ルータからの信号をデバイスがどれくらい受信できるかを測定したものです。
SSIRとmetageek.comの信号品質基準
-30dBm 非常に強い
-67dBm とても強い
-70dBm 強い
-80dBm 弱い
-90dBm 使えない
https://milestone-of-se.nesuke.com/nw-basic/wireless/rssi-snr-definition/

Seeed_Arduino_rpcWiFi

前回インストールしたSeeed ArduinorpcWiFi 1.0.5ライブラリについて
https://github.com/Seeed-Studio/Seeed_Arduino_rpcWiFi
特徴
・ESP32wifiライブラリとの最大互換性
 SeeedArduino rpcUnifiedを呼出して、Arduino-ESP32とのWiFi機能の互換性があるので、小さな変更でESP32 Wi-Fiアプリを使用できます。
・RealtekRTL8720DNを搭載
・デュアルバンド 2.4/5GHzWi-Fi (802.11a/b/g/n)
・低消費電力

Seeed_Arduino_RTCライブラリをインストール

Arduino-IDE > スケッチ > ライブラリをインクルード > ライブラリを管理... > "Seeed_Arduino_RTC"で検索
Seeed_Arduino_RTC by Seeed Studio 2.0.0をインストール > 閉じる

millisDelayライブラリをインストール

遅延機能とタイマーを提供しています。
https://github.com/ansonhe97/millisDelay
にて、Code > Download ZIP > そのままディスクトップに置く
Arduino-IDE > スケッチ > ライブラリをインクルード > .ZIP形式のライブラリをインストール... > 先程の"millisDelay-master.zip"を選択して開く
C:\Users\〇〇〇\Documents\Arduino\libraries\millisDelay-master\src
の下に"millisDelay.cpp" と "millisDelay.h" が出来ました。

NTPのパケットフォーマット

最初の32bitで

送信元ポート

Source Port=123 上位(左)16bit

宛先ポート

Destination Port=123 下位(右)16bit

次の32bitで

セグメント長

Segment Length 上位(左)16bit

チェックサム

Checksum 下位(右)16bit

その次はUDPデータで、最初の32bitは上位(左)から

閏秒指示子 (Leap Indicator)

1バイト目の一部
1-2の2bit 11(2)=3(10)
その日の最後の1分が1秒追加(うるう秒=Leap Second)されるか、もしくは1秒削除されるかを事前に予告するフィールド。
0:予告なし(通常)
1:その日の最後の1分が61秒
2:その日の最後の1分が59秒
3:時刻同期無し

バージョン番号 (Version Number)

1バイト目の一部
3-5の3bit 100(2)=4(10)
現在のNTP及びSNTPバージョンは4です。

Mode

1バイト目の一部
6-8の3bit 011(2)=3(10)
アソシエーションのモード。NTP時刻情報の提供形態
0:予約
1:Symmetric Active
2:Symmetric Passive
3:Client
4:Server
5:Broadcast
6:NTP control message(制御クエリ)
7:プライベート利用に予約

階層 (Stratum)

2バイト目 9-16の8bit
0:原子時計やGPSの時刻、NTPパケット上は取扱いません。
1:原子時計やGPSに物理的に直結したNTPサーバ
2以降は、それより1つ上のStratumのNTPサーバから時刻同期したことを意味します。
16:時刻同期していない。
17-255:使われていません。

ポーリング間隔 (Poll)

3バイト目 17-24の8bit
次のNTPパケット送出までの最大間隔 秒に対してlog2の値。例)デフォルト値10の場合は2^10=1024秒。

精度 (Precision)

4バイト目 25-32の8bit
NTPを扱う機器のシステムクロックが扱える精度を相手に示します。Pollと同じく、log2の値。例)-18は約1uS。

ルート遅延 (Root Delay)

5-8バイト目
32bit符号付固定小数点数 小数点がビット15と16の間
Strarum 1までの往復遅延(秒)。相対時間と周波数変位によって正負両方の値をとります。通常、-数mS~数百mS。

ルート拡散 (Root Dispersion)

9-12バイト目
32bit符号無し固定小数点数で小数点がビット15と16の間
Strarum 1までの誤差(秒)。通常、0~数百mS。

参照識別子 (Reference ID)

13-16バイト目 32bit
どのNTPサーバを参照しているかを表しています。
Stratum 2~14のNTPサーバは、上位の NTPサーバのIPアドレスをセット。
Stratum 1のNTPサーバは、参照先を暗示する任意の1~4 文字のASCII文字列(GPS、WWVB等)

参照時刻 (Reference Timestamp)

17-24バイト目
64bitのタイムスタンプフォーマット
最後に同期した時刻

開始時刻 (Origin Timestamp)

25-32バイト目
64bitのタイムスタンプフォーマット
NTPサーバにリクエストを送信した時刻

受信時刻 (Receive Timestamp)

33-40バイト目
64bitのタイムスタンプフォーマット
NTPサーバがリクエストを受信した時刻

送信時刻 (Transmit Timestamp)

41-48バイト目
64bitのタイムスタンプフォーマット
NTPサーバのパケットの送信時刻

以下略

タイムスタンプフォーマット

64bit符号なし固定小数点。上位32bitが整数秒、下位32bitが小数点以下。
1900/1/1 0:0:0からの経過秒数。協定世界時(UTC:Universal Time, Coordinated)。日本時間はUTCから9H遅れ(+9H)

時刻

・NTP(Network Time Protocol)
 サーバのパケットの送信時刻(UTC)
・UTC(協定世界時 Coordinated Universal Time)
 1900/1/1 00:00:00からの秒数
・JST(日本標準時)
 JST=UTC+9H (UTCから9H進める)
・UNIX時間(コンピューターシステム上での時刻)
 1970/1/1 00:00:00からの秒数

うるう年

2/29がある年です。西暦/4が割切れる年。しかし、100で割切れて、400で割切れない年は平年とします。よって、1900年は平年です。1900-1970年にうるう日は17日あります。

スケッチ

自分のSSIDとそのパスワードの**********の所は自分のを入れて下さい。

// ネットから時刻受信表示 (起動時のみ受信,以後内部RTCを1分ごとに表示するので誤差1分あります,
// 電源offで時刻リセット)
#include <rpcWiFi.h>      // Wi-Fiを使用
#include <millisDelay.h>  // ノンブロック遅延を使用。システムが停止しないタイマー
#include <RTC_SAMD51.h>   // RTC(real-time clock)を使用
#define LGFX_AUTODETECT   // LovyanGFX対応機種を自動認識
#include <LovyanGFX.hpp>  // LovyanGFXを使用
millisDelay updateDelay;  // updateDelayを定義
DateTime now;             // nowを定義
WiFiClient client;        // clientを定義
WiFiUDP udp;              // udpを定義
RTC_SAMD51 rtc;           // rtcを定義
static LGFX lcd;          // LGFXのインスタンスを作成
unsigned long devicetime; // 受信時刻
const char ssid[] = "**********"; // 自分のSSID
const char password[] = "**********"; // そのパスワード
const int NTP_PACKET_SIZE = 48;     // NTPタイムスタンプはメッセージの最初の48バイト
unsigned int localPort = 2390;      // udpローカルポート番号
char timeServer[] = "ntp.nict.jp";  // 情報通信研究機構のNTPサーバー
byte packetBuffer[NTP_PACKET_SIZE]; // パケットを保持するためのバッファ
char daysOfTheWeek[7][4] = {"日", "月", "火", "水", "木", "金", "土"}; // 曜日

void setGamen() {                    // 画面初期設定関数
  lcd.init();                        // LCD初期化
  lcd.setBrightness(64);             // バックライト輝度(暗0-255明)
  lcd.setFont(&lgfxJapanGothic_20);  // ゴシック固定長 縦ドット(16,20,24,28,32)
  //lcd.setTextScroll(true);         // 画面下端に到達後スクロール
  //lcd.setScrollRect(0, 0, lcd.width(), lcd.height());//スクロール範囲(X,Y,W,H)
  lcd.setTextSize(1.0);                   // 文字サイズ(横縦幅倍数)
  lcd.setTextColor(TFT_GREEN, TFT_BLACK); // (文字色,背景色)
  //lcd.setCursor(0, 0);                  // 画面左上から開始
}

unsigned long sendNTPpacket(const char* address) {  // 指定アドレスにNTP要求送信関数
  for (int i = 0; i < NTP_PACKET_SIZE; ++i) {       // 0-47の48バイトについて
    packetBuffer[i] = 0;         // バッファ内の全バイトを0
  }                              // 以下にNTP要求を形成するための値を設定
  packetBuffer[0] = 0b11100011;  // うるう年=同期無し,Ver4(通常),Mode=Client
  packetBuffer[1] = 0;           // 地層=原子時計???
  packetBuffer[2] = 6;           // 送出間隔 2^6=64秒
  packetBuffer[3] = 0xEC;        // 精度
  //                                packetBuffer[4]-[7]:ルート遅延は8バイトのゼロ
  //                                packetBuffer[8]-[11]:ルート拡散は8バイトのゼロ
  packetBuffer[12] = 49;         // [12]-[15]はIPアドレス[49,78,49,52]
  packetBuffer[13] = 0x4E;       // IPアドレス
  packetBuffer[14] = 49;         // IPアドレス
  packetBuffer[15] = 52;         // IPアドレス
  udp.beginPacket(address, 123);           // addressのポート123へ書込開始
  udp.write(packetBuffer, NTP_PACKET_SIZE);// udpデータを書出す(送信バッファ,メッセージ長)
  udp.endPacket();                         // udpデータを書込んだ後に送信
}

unsigned long getNTPtime() {             // 時刻受信関数 UNIXの日本時間を返す
  if (WiFi.status() == WL_CONNECTED) {   // WiFiネットに接続したら
    udp.begin(WiFi.localIP(), localPort);// udp(転送バッファ)を初期化 (IPアドレス,ポート番号)
    sendNTPpacket(timeServer);           // NTP要求送信関数へ
    delay(1000);                         // 1秒待つ
    if (udp.parsePacket()) {             // 次に利用可能な着信パケットの処理を開始し、
      // パケットの存在を確認。戻り値:パケットのサイズ(バイト)。udp.read()の前に必要
      //lcd.setCursor(10, 210); lcd.print("時刻受信           "); // 表示
      udp.read(packetBuffer, NTP_PACKET_SIZE);  // パケットをバッファに読込む
      // (格納バッファ(char or 配列byte), 読取バイト数(int))
      // NTPサーバのパケットの送信時刻(1900/1/1 0:0:0からの秒数)は、41-48バイト目で整数部は41-44バイト目
      // 1ワード=2バイト=16ビット
      unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);// 値をwordデータ型に変換
      unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); // (上位(左)バイト,下位(右)バイト)
      unsigned long secsSince1900 = highWord << 16 | lowWord; // 16ビット左にシフトしビットごとにORする
      const unsigned long seventyYears = 2208988800;      // 70年=(70x365+閏日17)x24x60x60秒
      unsigned long epoch = secsSince1900 - seventyYears; // UNIX=UTC-70年
      long tzOffset = 32400;                              // 9H=9x60x60=32400S
      unsigned long adjustedTime;                         // JST(日本時間)
      return adjustedTime = epoch + tzOffset;             // JST=UTC+9H
    } else {       // 利用可能なパケットがない場合
      udp.stop();  // サーバーから切断し、udpセッション中に使用されていたリソースを解放
      return 0;    // 0は失敗、利用可能なパケットがない
    }
    udp.stop();    //ntp timeを頻繁に呼び出さないで、リソースのリリースを停止
  } else {
    return 0;      // 0は失敗、ネットに接続できない
  }
}

void timeprint() {                              // 時刻画面表示関数
  lcd.setCursor(60, 10); lcd.setTextSize(2.0);  // 表示位置 文字サイズ
  lcd.printf("%04d/%02d/%02d", now.year(), now.month(), now.day());// 年月日表示
  lcd.setCursor(100, 60); lcd.setTextSize(1.5); // 表示位置 文字サイズ
  lcd.print("(");                               // 表示
  lcd.print(daysOfTheWeek[now.dayOfTheWeek()]); // 曜日表示
  lcd.print("曜日)");                            // 表示
  lcd.setCursor(60, 135); lcd.setTextSize(1.5); // 表示位置 文字サイズ
  int hourP = now.hour();                       // 時刻
  if (hourP > 12) {                             // 0-12時の場合
    hourP = hourP - 12;                         // 12引く
    lcd.print("PM");                            // 表示
  } else {                                      // 13-23時の場合
    lcd.print("AM");                            // 表示
  }
  lcd.setCursor(100, 110); lcd.setTextSize(3.0); // 表示位置 文字サイズ
  lcd.printf("%02d:%02d", hourP, now.minute());  // 時分表示
  lcd.setTextSize(1.0);                          // 文字サイズ
}

void setup() {
  setGamen();                     // 画面初期設定関数へ
  lcd.setCursor(10, 180); lcd.print("接続:" + String(ssid)); // 接続するSSID表示
  WiFi.disconnect();              // Wi-Fiシールドをネットから切断
  lcd.setCursor(10, 210); lcd.print("Wi-Fi接続待ち。        "); // 表示
  WiFi.begin(ssid, password);     // WiFiライブラリのネット設定を初期化し、接続開始
  while (WiFi.status() != WL_CONNECTED) {  // WiFiネットに接続していなかったら
    WiFi.begin(ssid, password);   // WiFiライブラリのネット設定を初期化し、接続開始
    delay(500);                   // 0.5秒待つ
  }
  long rssi = WiFi.RSSI();                                     // 受信信号強度を取得
  lcd.setCursor(10, 210); lcd.printf("受信強度:%4lddBm", rssi); // 受信信号強度を表示
  if (rssi <= -81) {         // -81dBm以下の時
    lcd.print("(使えない)");   // 表示
  } else if (rssi <= -71) {  // -71~-80dBmの時
    lcd.print("(弱い)");      // 表示
  } else if (rssi <= -68) {  // -68~-70dBmの時
    lcd.print("(強い)");      // 表示
  } else if (rssi <= -31) {  // -31~-67dBmの時
    lcd.print("(とても強い)"); // 表示
  } else {                   // 0~-30dBmの時
    lcd.print("(非常に強い)"); // 表示
  }
  devicetime = getNTPtime(); // 時刻受信関数へ
  if (devicetime == 0) {     // 時刻取得出来ない場合
    lcd.setCursor(10, 210); lcd.print("時刻を受信できません。    "); // 表示
  }
  rtc.begin();                       // 内部RTCを初期化
  rtc.adjust(DateTime(devicetime));  // 受信時刻に更新
  now = rtc.now();                   // 更新後のWioのRTC時刻
  timeprint();                       // 時刻画面表示関数へ
  updateDelay.start(60 * 1000);      // ノンブロック遅延開始 遅延1分
}

void loop() {
  if (updateDelay.justFinished()) {     // ノンブロック遅延が終了したら
    updateDelay.repeat();               // ノンブロック遅延を再開 定期更新
    //devicetime = getNTPtime();        // 時刻受信関数へ
    //rtc.adjust(DateTime(devicetime)); // 受信時刻に更新
    now = rtc.now();                    // RTC時刻
    timeprint();                        // 時刻画面表示関数へ
  }
}
* フラッシュメモリの60%を使用。