NTPサーバーから時間取得、WiFiMultiで接続先 複数登録

Arduinoコマンド:接続先複数WiFiMultiでNTP時間取得

「M5StickC Plus」を使用したWi-Fi時計のサンプルプログラム(Arduinoコマンド使用)で、複数のWi-Fi環境を登録して接続できる WiFiMulti の使い方と、NTPサーバーからの時間取得方法について詳しく紹介します。

個人的には社内のネットワークがどこまで届いて、どこでどのルーターに切り替わるかの確認用として製作したものです。

「WiFiMulti」の使い方と「NTPサーバーからの時間取得」方法の紹介にちょうど良いので、これらについても詳しく紹介します。

スポンサーリンク

1.動作紹介

「M5StickC Plus」を使用して制作したWi-Fi時計の動作について紹介します。
複数のWi-Fi接続先はプログラム内であらかじめ設定しておきます。

接続先複数WiFiMultiでNTP時間取得

「M5SticC Plus」の電源を入れると、上画像のようにWi-Fiへの接続が開始されます。
登録された「接続先(SSID)」と「パスワード」を使用して、最も電波の強い接続先に接続します。

接続先複数WiFiMultiでNTP時間取得

接続が完了すると上画像のように「IPアドレス」と「接続先(SSID)」が表示され、時間のカウントが始まります。
右上の「%」はバッテリー残量を表し、その下の「dBm」は電波の強さを表し、電波強度によって文字色が変わります。

「dBm」については「WiFiMultiについて」で詳しく紹介します。

接続先複数WiFiMultiでNTP時間取得

接続先が見つからない時は、上画像のように1秒だけ表示され、再度接続を実行します。
これは接続が完了するまで繰り返されます。

接続先複数WiFiMultiでNTP時間取得

一度、Wi-Fi接続が完了した後でWi-Fi接続が遮断されると、上画像のように時間カウントが赤色になり「dBm」は「0dBm」となりますが、時間カウントは継続します。
Wi-Fi接続が復旧すると、時間カウントは白色に戻ります。

本体の電源ボタンを押す(6秒以下)と本体はリセットされ再度Wi-Fi接続を実行します。


「M5StickC Plus」の使い方についは、以下のリンクで詳しく紹介しています。

M5StickC Plusの使い方、初期設定、サンプルプログラム、M5StickCとの違い等を詳しく紹介
M5StickC PlusをArduino IDEやPlatformIOで使うための初期設定やサンプルプログラムでの動作確認の方法です。ビジュアルプログラミングのUiFlowの初期設定についても紹介します。
スポンサーリンク

2.サンプルプログラム(コピペ)

サンプルプログラムは以下になります。「コピペ」して書き込んでください。

※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。

#include <M5StickCPlus.h>
#include <WiFi.h>
#include <WiFiMulti.h>
WiFiMulti wifiMulti;  // WiFiMultiのインスタンスを準備
/****************** WiFiMultiを使用して複数のSSIDに接続 ******************/
void wifiConnect() {
  // wifiMultiの接続先を複数登録(SSID, PASSWORD)
  wifiMulti.addAP("SSID1", "パスワード1");  // 接続先1
  wifiMulti.addAP("SSID2", "パスワード2");  // 接続先2
  wifiMulti.addAP("SSID3", "パスワード3");  // 接続先3
  // WiFiMulti実行
  while (WiFi.status() != WL_CONNECTED) { // 接続完了するまで繰り返す
    M5.Lcd.fillScreen(BLACK); // 背景黒リセット
    M5.Lcd.setTextFont(4);    // フォント
    M5.Lcd.setCursor(5, 2);   M5.Lcd.setTextColor(WHITE, BLACK);  // 座標、文字色
    M5.Lcd.println("Wi-Fi Search ...   "); // 初期表示
    wifiMulti.run();  // WiFiMultiを実行
    delay(1000);      // 接続実行後は少し待つ
    // Wi-Fi接続結果表示
    if (WiFi.status() == WL_CONNECTED) {  // 接続成功ならIPアドレス、SSID表示
      M5.Lcd.fillScreen(BLACK);                                     // 背景黒リセット
      M5.Lcd.setTextFont(2);                                        // フォント
      M5.Lcd.setCursor(5, 2);   M5.Lcd.setTextColor(ORANGE, BLACK); // 座標、文字色
      M5.Lcd.print("IP : ");    M5.Lcd.print(WiFi.localIP());       // IPアドレス
      M5.Lcd.setCursor(5, 18);  M5.Lcd.setTextColor(CYAN, BLACK);   // 座標、文字色
      M5.Lcd.print("SSID : ");  M5.Lcd.print(WiFi.SSID());          // SSID表示
      M5.Lcd.drawFastHLine(0, 37, 240, WHITE);                      // 指定座標から水平線
    } else {                              // 接続失敗ならエラーメッセージ表示
      M5.Lcd.setTextColor(RED, BLACK);            // 座標、文字色
      M5.Lcd.println("Wi-Fi Nothing!\nRetry!!");  // エラー表示
      delay(1000);
    }
  }
}
/************************ NTPサーバと時間を同期 ************************/
char ntpServer[] = "pool.ntp.org";    // 最適なNTPサーバーへ誘導してくれる
const long gmtOffset_sec = 9 * 3600;  // UTC(協定世界時)をJST(日本標準時)にするため9時間加算
const int daylightOffset_sec = 0;     // サマータイムのオフセットは無しに設定
struct tm timeinfo;                   // tmのインスタンスを準備
char date[11];                        // 年月日データ格納バッファ
char now_time[9];                     // 時間データ格納バッファ

void getTimeFromNTP() {
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // 時間取得条件を指定
  while (!getLocalTime(&timeinfo)) { delay(1000); }         // 時間の同期が完了するまで待つ
}
/**************** NTPサーバから取得した情報で日付時刻を更新 ****************/
void getTime() {
  getLocalTime(&timeinfo);          // 時間情報を構造体で取得
  sprintf(date, "%02d/%02d/%02d",   // 年月日をdateに結合
          timeinfo.tm_year + 1900,  // 年(1900年からの経過年)
          timeinfo.tm_mon + 1,      // 月(0〜11で取得するため+1)
          timeinfo.tm_mday          // 日
  );
  sprintf(now_time, "%02d:%02d:%02d", // 時分秒をnow_timeに結合
          timeinfo.tm_hour,           // 時
          timeinfo.tm_min,            // 分
          timeinfo.tm_sec             // 秒
  );
  // 時刻表示
  if (WiFi.status() == WL_CONNECTED) {  // Wi-Fi接続有りなら
    M5.Lcd.setTextColor(WHITE, BLACK);  // 文字色白
  } else {                              // Wi-Fi接続無しなら
    M5.Lcd.setTextColor(RED, BLACK);    // 文字色赤
  }
  M5.Lcd.setCursor(5, 44);  M5.Lcd.setTextFont(4);  // 座標指定、フォント 4(26px)
  M5.Lcd.print(date);                               // 年月日dateを表示
  M5.Lcd.setCursor(13, 78); M5.Lcd.setTextFont(7);  // 座標指定、フォント(7セグ)
  M5.Lcd.print(now_time);                           // 現在時刻now_timeを表示
}
// 初期設定 -------------------------------------------------
void setup() {
  M5.begin();             // 本体初期化
  M5.Lcd.setRotation(1);  // 画面向き設定(USB位置基準 0:下/ 1:右/ 2:上/ 3:左)
  M5.Lcd.setTextSize(1);  // 文字サイズ(整数倍率)
  wifiConnect();          // Wi-Fi接続実行
  if (WiFi.status() == WL_CONNECTED) {  // Wi-Fi接続有りなら
    getTimeFromNTP();     // NTPサーバーと時間を同期
  }
}
// メイン ---------------------------------------------------
void loop() {
  M5.update();  // ボタン状態更新
  getTime();    // NTPサーバーより時間取得表示(一度同期すればWi-Fi接続が遮断されてもカウント継続)
  // 電源ボタン(Wi-Fi再接続のため再起動)
  if (M5.Axp.GetBtnPress()) { // 電源ボタンが押されたら
    ESP.restart();  // 再起動実行
  }
  // バッテリー残量(MAX約4.2V、限界電圧3V)パーセント換算表示
  float battery;    // バッテリー残量表示用
  battery = (M5.Axp.GetBatVoltage() - 3.1) * 100;   // バッテリー残量取得、3.1Vで0%として換算
  if (battery > 100) {                              // 換算値が100以上なら
    battery = 100;                                  // 100にする
  }
  M5.Lcd.setCursor(205, 2); M5.Lcd.setTextFont(2);  // 座標、フォント 2(16px)
  M5.Lcd.setTextColor(DARKGREY, BLACK);             // 文字色
  M5.Lcd.printf("%3.0f%%", battery);                // バッテリー残量表示

  // RSSI(受信信号強度)を強度によって色分けして表示
  M5.Lcd.setCursor(193, 18);  // 座標
  if (WiFi.RSSI() >= -67) {                             //RSSI -67dBm以上なら良好
    M5.Lcd.setTextColor(CYAN, BLACK);                   //水色表示
  } else if (WiFi.RSSI() < -67 && WiFi.RSSI() >= -70) { //RSSI -67dBmより低くて-70dBm以上なら不安定
    M5.Lcd.setTextColor(GREENYELLOW, BLACK);            //黄色表示
  } else if (WiFi.RSSI() < -70 && WiFi.RSSI() >= -80) { //RSSI -70dBmより低くて-80dBm以上なら悪い
    M5.Lcd.setTextColor(ORANGE, BLACK);                 //橙色表示
  } else {                                              //RSSI -80dBmより低いなら使用不可
    M5.Lcd.setTextColor(RED, BLACK);                    //赤色表示
  }
  M5.Lcd.printf("%3ddBm", WiFi.RSSI());      // SSID表示
  delay(500); // 遅延時間
}
スポンサーリンク

3.Wi-Fi接続先 複数登録 WiFiMultiについて

WiFiMultiの使い方

接続先を複数登録してWi-Fi接続を行う「WiFiMulti」の使い方について紹介します。

まず、ヘッダ部に以下のように記入します。

#include <WiFi.h>
#include <WiFiMulti.h>
WiFiMulti wifiMulti;  // WiFiMultiのインスタンスを準備

次に「SSID(接続先名)」と「パスワード」を以下のように複数設定して「wifiMulti.run()」を実行する事で、接続先の中から最も「RSSI(受信信号強度)」の強い接続先に接続を行います。

// wifiMultiの接続先を複数登録(SSID, パスワード)
wifiMulti.addAP("SSID1", "パスワード1");  // 接続先1
wifiMulti.addAP("SSID2", "パスワード2");  // 接続先2
wifiMulti.addAP("SSID3", "パスワード3");  // 接続先3

while (WiFi.status() != WL_CONNECTED) { // 接続完了するまで繰り返す
  wifiMulti.run();                      // WiFiMultiを実行
  delay(1000);                          // 接続実行後は少し待つ
}
「wifiMulti.run()」実行後には少し待ち時間を設けましょう。(1秒以上)
接続確認は「wifiMulti.run()」の実行結果で行うと失敗することが多いように思います。
接続確認には「WiFi.status() 」で確認した方が安定して接続できると思います。

他のWi-Fi環境で接続をやり直す時は以下のように「ESP.restart()」を実行して本体を再起動するようにしています。

if (M5.Axp.GetBtnPress()) { // 電源ボタンが押されたら
  ESP.restart();  // 再起動実行
}
再接続には「WiFi.disconnect()」を実行して通信の遮断を確認してから「wifiMulti.run()」を再度実行しても良いのですが「WiFi.status()」の状態によっては接続に時間がかかったり、接続されなかったりしたので、本体を再起動するようにしました。

受信信号強度(RSSI)について

「WiFiMulti」では最も受信信号強度(RSSI:Received Signal Strength Indicator)の強い接続先に接続されます。
RSSIの単位は「dBm(decibels relative to a milliwatt) 」でミリワットをデシベル値で表したものです。

極小電力のWi-Fi信号の強度を表すために使用され「dBm」値に対する通信品質の目安は以下のようになります。

・良好  :-67dBmより強い
・不安定 :-67dBm 〜 -70dBm
・悪い  :-70dBm 〜 -80dBm
・使用不可:-80dBmより弱い

「mW」と「dBm」の関係は以下のようになります。
       1mW =    0dBm
    0.1mW = -10dBm
  0.01mW = -20dBm
0.001mW = -30dBm

4.NTPサーバーからの時間取得(同期)について

NTPサーバーとは

NTPとは「Network Time Protocol」の略で時刻情報を取得(同期)するためのプロトコル(手順、決め事)のことです。
NTPサーバーとは、パソコンやスマホ等ネットワーク上のあらゆる機器に時刻情報を提供しているサーバーです。

時刻の同期はGPSや原子時計などを用いて行われ、受信した側の時刻のわずかなズレもNTPサーバー側で調整してくれるため、正確な時刻を取得することができます。

インターネット環境に応じて数mから数百ミリ秒のズレは発生します。

NTPサーバーは世界中に複数存在しており、どのサーバーからでも取得できる時刻は協定世界時(UTC)です。日本標準時(JST)は協定世界時(UTC)に9時間加算することで取得できます。

NTPサーバーは階層構造になっており、各層を「Stratum」と呼びます。
最も信頼性が高い時刻を「Stratum0」と定義しており、「Stratum1」は「Stratum0」の時刻を受け取って下の階層の「Stratum2」に時刻を配信します。(最下層「Stratum16」まで同様に時刻を配信)

このような階層構造にすることで負荷を分散させ、障害が起きた場合でも正確な時間を配信できるようになっています。

下の階層へ行けば行くほど時刻のズレは大きくなるため、正確な時間を取得したい場合にはより高い層を選択した方が良いことになります。

主な「NTPサーバー」には以下のものがあります。

NTPサーバーアドレス
独立行政法人情報通信機構(NICT)日本標準時に直結した時刻サーバntp.nict.jp
NTP POOL PROJECTの公開NTPサーバーpool.ntp.org
Windows標準の時刻合わせのサーバーtime.windows.com
サーバー側も強化されているようですが、負荷の軽減を考えると、用途に応じて下の階層を選択した方が良いように思います。
「NTP POOL PROJECT」のNTPサーバーは自動的に最適なサーバーへ誘導してくれるので、こちらを設定しておくのがおすすめです。

時間取得(同期)方法

「NTPサーバー」から時間を取得するには以下のように指定します。
サンプルプログラムから時間取得の部分を以下に抜粋します。

まず、サンプルプログラムの 36〜41行目のように時間を同期するための初期設定を行います。

char ntpServer[] = "ntp.nict.jp";     // 日本標準時刻と直結したNTPサーバ
const long gmtOffset_sec = 9 * 3600;  // UTC(協定世界時)をJST(日本標準時)するため9時間加算
const int daylightOffset_sec = 0;     // サマータイムのオフセットは無しに設定
struct tm timeinfo;                   // tmのインスタンスを準備
char date[11];                        // 年月日データ格納バッファ
char now_time[9];                     // 時間データ格納バッファ

次に 43〜46行目のように以下を実行することで、初期設定の情報を使用してNTPサーバーと時刻の同期を行います。

void getTimeFromNTP() {
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // 時間取得条件を指定
  while (!getLocalTime(&timeinfo)) { delay(1000); }         // 時間の同期が完了するまで待つ
}

時間を取得するには 48〜70行目のように以下を実行します。
以下では取得した日付「date」と時間「now_time」をそれぞれ「sprintf」で結合して表示しています。

void getTime() {
  getLocalTime(&timeinfo);          // 時間情報を構造体で取得
  sprintf(date, "%02d/%02d/%02d",   // 年月日をdateに結合
          timeinfo.tm_year + 1900,  // 年(1900年からの経過年)
          timeinfo.tm_mon + 1,      // 月(0〜11で取得するため+1)
          timeinfo.tm_mday          // 日
  );
  sprintf(now_time, "%02d:%02d:%02d", // 時分秒をnow_timeに結合
          timeinfo.tm_hour,           // 時
          timeinfo.tm_min,            // 分
          timeinfo.tm_sec             // 秒
  );
  // 時刻表示
  if (WiFi.status() == WL_CONNECTED) {  // Wi-Fi接続有りなら
    M5.Lcd.setTextColor(WHITE, BLACK);  // 文字色白
  } else {                              // Wi-Fi接続無しなら
    M5.Lcd.setTextColor(RED, BLACK);    // 文字色赤
  }
  M5.Lcd.setCursor(5, 44);  M5.Lcd.setTextFont(4);  // 座標指定、フォント 4(26px)
  M5.Lcd.print(date);                               // 年月日dateを表示
  M5.Lcd.setCursor(13, 78); M5.Lcd.setTextFont(7);  // 座標指定、フォント(7セグ)
  M5.Lcd.print(now_time);                           // 現在時刻now_timeを表示
}
一度同期が完了すると通信が遮断されても時間情報を更新すれば時間のカウントは継続します。
電源ON時にWi-Fi接続が無く「NTPサーバー」との同期に失敗すると、基準時間の「1970/01/01 00:00:00」から時間がカウントされます。
サンプルプログラムではWi-Fi接続が完了するまで、再接続を実行し続けるようにしているため、Wi-Fi接続が無く同期が完了できない場合は時間カウントが始まらないようになっています。
本当に時計として使用する場合は、消費電力を少なくするために、常にWi-Fiに接続せずに起動時や1日に数回程度接続して時間を同期するような使い方をしましょう。

5.まとめ

「WiFiMulti」を使用して、登録した複数のネットワークに接続可能でNTPサーバーから正確な時間を表示できるWi-Fi時計について紹介しました。

「WiFiMulti」は複数の「SSID」と「パスワード」を登録して実行すると、最も受信信号強度(RSSI)の強い接続先に接続してくれます。

時計の時間はWi-Fi接続中はNTPサーバーから取得し続けますが、一度同期が完了すると、ネットワーク接続が遮断されても時間カウントは継続します。
ネットワーク接続が復旧すると再び時間の同期が開始されます。

登録された別のネットワーク環境で時間を取得したい場合は、一度接続を遮断してまた「WiFiMulti」を実行することで行えますが、本体を再起動した方が素早く別のWi-Fi環境での時間取得が再開できると思います。

今回は「NTPサーバー」と同期したWi-Fi時計として使用ましたが、複数のネットワーク環境でWi-Fi経由で遠隔操作やデータ取得を行うような用途にも応用できます。

WiFi(ローカル接続、サーバー)で遠隔操作する方法(Arduinoプログラミング)
Wi-Fi通信でスマホから遠隔操作を行う方法を詳しく紹介。サーバーの仕組みを利用することで簡単にブラウザからの遠隔操作が実現できます。ATOM LITE使用
WiFi遠隔操作Arduinoコマンドでブラウザベースのスマホ、PCリモートコントローラの紹介
ブラウザベースで遠隔操作、リアルタイムデータ通信を行う方法をコピペ用サンプルプログラムを使って紹介します。 サーバー機能を利用して「JavaScript」の「fetch」を使うことでデータの送受信を行います。

コメント

タイトルとURLをコピーしました