「M5StickC Plus」を使用したWi-Fi時計のサンプルプログラム(Arduinoコマンド使用)で、複数のWi-Fi環境を登録して接続できる WiFiMulti の使い方と、NTPサーバーからの時間取得方法について詳しく紹介します。
個人的には社内のネットワークがどこまで届いて、どこでどのルーターに切り替わるかの確認用として製作したものです。
「WiFiMulti」の使い方と「NTPサーバーからの時間取得」方法の紹介にちょうど良いので、これらについても詳しく紹介します。
1.動作紹介
2.サンプルプログラム(コピペ)
3.Wi-Fi接続先 複数登録 WiFiMultiについて
・WiFiMultiの使い方
・受信信号強度(RSSI)について
4.NTPサーバーから時間取得(同期)について
・NTPサーバーとは
・時間取得方法
5.まとめ
1.動作紹介
「M5StickC Plus」を使用して制作したWi-Fi時計の動作について紹介します。
複数のWi-Fi接続先はプログラム内であらかじめ設定しておきます。
「M5SticC Plus」の電源を入れると、上画像のようにWi-Fiへの接続が開始されます。
登録された「接続先(SSID)」と「パスワード」を使用して、最も電波の強い接続先に接続します。
接続が完了すると上画像のように「IPアドレス」と「接続先(SSID)」が表示され、時間のカウントが始まります。
右上の「%」はバッテリー残量を表し、その下の「dBm」は電波の強さを表し、電波強度によって文字色が変わります。
接続先が見つからない時は、上画像のように1秒だけ表示され、再度接続を実行します。
これは接続が完了するまで繰り返されます。
一度、Wi-Fi接続が完了した後でWi-Fi接続が遮断されると、上画像のように時間カウントが赤色になり「dBm」は「0dBm」となりますが、時間カウントは継続します。
Wi-Fi接続が復旧すると、時間カウントは白色に戻ります。
本体の電源ボタンを押す(6秒以下)と本体はリセットされ再度Wi-Fi接続を実行します。
「M5StickC Plus」の使い方についは、以下のリンクで詳しく紹介しています。
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); // 接続実行後は少し待つ
}
他のWi-Fi環境で接続をやり直す時は以下のように「ESP.restart()」を実行して本体を再起動するようにしています。
if (M5.Axp.GetBtnPress()) { // 電源ボタンが押されたら
ESP.restart(); // 再起動実行
}
受信信号強度(RSSI)について
「WiFiMulti」では最も受信信号強度(RSSI:Received Signal Strength Indicator)の強い接続先に接続されます。
RSSIの単位は「dBm(decibels relative to a milliwatt) 」でミリワットをデシベル値で表したものです。
極小電力のWi-Fi信号の強度を表すために使用され「dBm」値に対する通信品質の目安は以下のようになります。
・良好 :-67dBmより強い
・不安定 :-67dBm 〜 -70dBm
・悪い :-70dBm 〜 -80dBm
・使用不可:-80dBmより弱い
4.NTPサーバーからの時間取得(同期)について
NTPサーバーとは
NTPとは「Network Time Protocol」の略で時刻情報を取得(同期)するためのプロトコル(手順、決め事)のことです。
NTPサーバーとは、パソコンやスマホ等ネットワーク上のあらゆる機器に時刻情報を提供しているサーバーです。
時刻の同期はGPSや原子時計などを用いて行われ、受信した側の時刻のわずかなズレもNTPサーバー側で調整してくれるため、正確な時刻を取得することができます。
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サーバー」から時間を取得するには以下のように指定します。
サンプルプログラムから時間取得の部分を以下に抜粋します。
まず、サンプルプログラムの 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を表示
}
5.まとめ
「WiFiMulti」を使用して、登録した複数のネットワークに接続可能でNTPサーバーから正確な時間を表示できるWi-Fi時計について紹介しました。
「WiFiMulti」は複数の「SSID」と「パスワード」を登録して実行すると、最も受信信号強度(RSSI)の強い接続先に接続してくれます。
時計の時間はWi-Fi接続中はNTPサーバーから取得し続けますが、一度同期が完了すると、ネットワーク接続が遮断されても時間カウントは継続します。
ネットワーク接続が復旧すると再び時間の同期が開始されます。
登録された別のネットワーク環境で時間を取得したい場合は、一度接続を遮断してまた「WiFiMulti」を実行することで行えますが、本体を再起動した方が素早く別のWi-Fi環境での時間取得が再開できると思います。
今回は「NTPサーバー」と同期したWi-Fi時計として使用ましたが、複数のネットワーク環境でWi-Fi経由で遠隔操作やデータ取得を行うような用途にも応用できます。
コメント