「#4-1 中国から配送されるネットショッピング」で購入したESP32を使って、WiFi経由、IP通信を試してみようと思います。
とりあえず、Arduinoは使いません。「#4-1 中国から配送されるネットショッピング」の初期不良チェック時と同様、FTDI (USB⇔UART変換モジュール)を使って、シリアルモニタ経由でATコマンドを叩いて通信します。
「Arduino DIYなのに?」と、言う突っ込みもあるでしょうが、最終的には、Arduinoの非同期シリアル通信を使ってESP32を制御したいと思っているので、その準備です。PC ⇒ シリアル ⇒ Arduino ⇒ シリアル ⇒ ESP32と言う最終形でいきなりデバッグすると、うまく動かなかったときに誰の問題なのか突き止めるのが困難となりますので、PC ⇒ シリアル ⇒ ESP32と、簡略化してデバッグしてから先へ進むという、手順を取ります。
「#4-1 中国から配送されるネットショッピング」の初期不良チェック時と同様にして、ESP32(左)と、FTDI(右)、PCを接続します。
PCでArduino IDEを開き、「FTDIのシリアルポート」を指定してシリアルモニタを開きます。改行コード「CR+LF」、通信速度「115,200bps」に設定します。
以下の様にコマンドを入力して送信します。
送信 | AT+GMR |
受信 |
AT version:1.1.1.0(Jun 28 2018 06:19:01) SDK version:v3.0.1 compile time:Jul3 2018 05:27:09 Bin version(Wroom32):1.1.1 OK |
コマンドは、小文字で打っても認識してくれます。「AT+GMR」は、ATコマンドのバージョン情報を返すコマンドです。
ESP32で使用可能なATコマンドのデータシートは、以下より検索してください。
ESP32+ATデータシートを検索する(別ウインドウが開きます)
続けます。
送信 | AT+CWMODE? |
動作モードの確認: デフォルトは0=WiFi RF disableになっている。 この設定は、不揮発メモリに記憶されるので、過去に設定してあれば、そのモードになっているはず。 |
受信 |
+CWMODE:0 OK |
|
送信 | AT+CWMODE=1 |
動作モードを1=ステーションモードに変更 1=ステーション:アクセスポイント(AP)に接続するモード 2=ソフトAP:ESP32がAPとなり、他の機器からの接続を受け付けるモード 3=ステーション+ソフトAP この設定は、不揮発メモリに記憶される。 |
受信 | OK | |
送信 | AT+CWLAP |
周辺のアクセスポイントを探してリスト表示する。 表示されるのは、802.11g規格等、2.4GHz帯のAP。 802.11a規格等、5GHz帯のAPは表示されない。 |
受信 |
+CWLAP:(4,"aterm-g",-61,"<mac>",4) : OK |
|
送信 | AT+CWJAP="aterm-g","<passwd>" |
AT+CWLAPで検索したAPのSSIDと、パスワードを指定して、APに接続する。 この設定は、不揮発メモリに記憶される。 次回以降、ESP32の電源を投入すると、自動的に、ここで指定したAPに接続するようになる。 |
受信 |
WiFi CONNECTED WiFi GOT IP OK |
|
送信 | AT+CWJAP? |
接続中のAPを確認する。 電波状況などによって、切断したり、再接続したりすると、非同期に「WiFi DISCONNECT」や「WiFi CONNECTED」等のメッセージが飛んでくるので、ハンドリングには注意が必要。 |
受信 |
+CWJAP:"aterm-g","<mac>",4,-61 OK |
|
送信 | AT+CIFSR |
ESP32のIPアドレスを表示します。 手元の環境では、これだけで接続できてしまいました。 IPアドレスは、DHCPで取得した様です。DHCPの設定はAT+CWDHCPで確認・変更可能です。 以下、プライベートアドレスが"192.168.0.x"だった前提で、例示しています。 |
受信 |
+CIFSR:STAIP,"192.168.0.xxx" +CIFSR:STAMAC,"<mac>" OK |
この辺の設定は、不揮発メモリに記憶され、次回電源投入時には、自動的に指定したAPに接続するようになるので、最初に1回設定しておけばOKです。
※自動的に接続したくない場合には、「AT+CWAUTOCONN」で設定できます。その他の設定に関しては、最初に検索したデータシートをご確認ください。
無線LANだと、物理的に接続されていないので、「WiFi CONNECTED」とか言われても、「本当につながってるの?」と、不安になりますよね(1^^;)?APに接続しているPC等のIPアドレスを調べて、PINGを投げてみましょう。
送信 | AT+PING="192.168.0.xx" | "192.168.0.xx"には、同じAPに接続されているPCのIPアドレス等を指定してください。 |
受信 |
+PING:2 OK |
数字はレスポンスが返るまでに要した時間。 多分、ms(ミリ秒)。 |
+PING:TIMEOUT ERROR |
存在しない機器のIPアドレスや、もしかして、PINGに反応しないようになっている機器だと、こんな表示が返ります。 |
実は、最初にトライしたとき、手元の環境では、うまくアクセスができませんでした。その時の原因と対策について、記しておきます。
まず、APへの接続。SSIDも間違っていないし、<passwd>も正しいはずなのに、蹴られました。原因は、AP(ルータ)の「MACアドレスフィルタ」機能。
そうだった。セキュリティ対策で、指定したMACアドレス以外からの接続を禁止しているんだった。新たに、ESP32のMACアドレスを追加設定して、クリアしました。ESP32のMACアドレスは、「AT+CIFSR」等で知ることができます。
次に、PINGが通らない。どこに投げても、「+PING:TIMEOUT」が返ってきました。正確には、ルータアドレス"192.168.0.1"だけからは、PINGが返ってきました。と、言うことは、確かにAPへは接続できていて、IP通信もできているようです。
PCのセキュリティソフトウェアとかも疑ってみましたが、違いました。
原因は、今度もAP(ルータ)の設定でした。「ネットワーク分離機能」という設定項目があり、有効になっていました。この設定が有効になっていると、APに接続した機器間の通信が禁止されます。
多分、公共のWiFiアクセスポイントなどに使用する際、AP経由でインターネットにはアクセスできるけど、APに接続した機器間で相手の機器にアクセスできないようにする機能だと思われます。
※「友人が遊びに来た時に接続させてあげる」ことがあるなら、有効にしておくのが無難ですが、自宅で自分(家族)だけが使うのなら、不要な機能ですね。筆者は無効化しました。
手持ちのPCは全て5GHz帯で接続しており、2.4GHz帯APの設定には気を使っていなかったし、影響を受けていなかったので、今まで気がつきませんでした。
PCのセキュリティソフトウェアに蹴られたり、今回の様にAP(ルータ)で蹴られることもあるので、うまく通信できなかった場合には、色々な角度から検証してみる必要がある様です。
ESP32のATコマンドに、インターネットから時刻を取得する「AT-CIPSNTPTIME」と言うコマンドがありますので、これを使って現在時刻を取得してみようと思います。
※SNTP=Simple Network Time Protocol
送信 | AT+CIPSNTPCFG=1,-9 |
日本標準時(JST)のタイムゾーンは−9でした。 電子メールなどに使われるMIMEの"Date:"形式では、「+0900」なので、はじめ「9」を指定してみたのですが、あらぬ時間が表示されました。 MIMEだと、JST=UTC+9時間と、世界標準時(UTC)を基準にしているのですが、ESP32はUTC=JST−9時間というタイムゾーンになっているみたいです。 |
受信 | OK | |
送信 | AT+CIPSNTPTIME? | 正しい時間が表示されたら、SNTPサーバと通信できています。 |
受信 |
+CIPSNTPTIME:Sat Feb 23 12:37:40 2019 OK |
※ATコマンドファームウェアをV.2.0.0にアップデートしたところ、JSTの設定が「9」になりました。本ページの記述はそのまま残してあるので「-9」になっていますが、新しいバージョンのATコマンドファームウェアをお使いの場合は「9」と読み替えてください。
SNTPサーバを指定しなかった場合、ESP32のファームウェアがデフォルトで持っている、中国国内のSNTPサーバに問い合わせに行くようです。ちょっと、ネットワーク距離が遠そうな予感がします。SNTPリクエスト発信から、回答が返るまでの時間(ラウンドトリップ時間)は、時刻合わせの精度に関係するので、なるべく短い方が良いです。日本国内のSNTPサーバを指定しておくのが無難でしょう。
日本国内のSNTPサーバを検索する(別ウインドウが開きます)
送信 | AT+CIPSNTPCFG=1,-9,"<国内のSNTPサーバ名>"[,"<サーバ#2>","<サーバ#3>"] |
受信 | OK |
SNTPサーバは、3つまで並べて設定することができます(オプション、[ ]内は省略可能)。
拙作WindowsソフトウェアのiConClockには、簡易版SNTPサーバになる機能がありますので、iConClockでSNTPサーバを起動したPCのIPアドレスを指定してみました。
送信 | AT+CIPSNTPCFG=1,-9,"192.168.0.xx" |
受信 | OK |
すると、iConClockにリクエストが飛んできたのですが、反応できませんでした。iConClockのログに、以下の一行が残されていました。
-13:受信SNTPパケットが想定外 18:01 SNTP request from 192.168.0.xxx
※192.168.0.xxxは、ESP32のIPアドレス。
飛んできたパケットを確認したところ、SNTP V4(バージョン4)のパケットでした。iConClockは、簡易版SNTPサーバと言うことで、V1(バージョン1)のパケットにしか反応しないようになっているので、時刻設定できません。
※この後、バージョンが変わっても、リクエストの内容に変わりがないらしいことが分かったため、iConClock R08以降で、バージョン1〜4のパケットに反応するよう、対策済みです。
注意点は、時刻設定できなかったはずなのに、レスポンスは「OK」だったこと。エラー状態が通知されないので、時間が設定できたかどうか、判断付きません。
どうも、「AT+CIPSNTPTIME?」が失敗しても、ESP32に内蔵されたRTC(リアルタイムクロックユニット)の時間と「OK」ステータスを返してくるので、これまたエラーを知ることができません。ちょっと今一。
ESP32は、時刻の取得に失敗すると、何度もリトライしてきます。電波を飛ばすのに電力を使うだろうし、失敗したかどうかを知るすべもないので、時刻合わせを実行した後で、明示的にSNTPによる時刻合わせ機能を無効化しておいた方が良さそうです。
送信 | AT+CIPSNTPCFG=0 |
受信 | OK |
次はUDP通信を試してみます。
実は、前節のSNTPはUDP通信なので、例題として、SNTP V1のパケットをiConClockに送り、レスポンスを受け取ってみたいと思います。
まず、UDP接続を確立します。
送信 | AT+CIPSTART="UDP","192.168.0.xx",123 |
受信 |
CONNECT OK |
ATコマンドは小文字でも受け付けてくれましたが、"UDP"は大文字しか受け付けてくれません。"192.168.0.xx"には、iConClockを起動し、簡易SNTPサーバを有効にしたPCのIPアドレスを指定します。SNTPのポート名は、一般に123なので、それを指定します。もし、iConClockの設定で、別なポート名を指定している場合には、「123」の代わりにそのポート名を指定してください。
次に、SNTPのリクエストパケットを送信するのですが、バイナリデータなので、シリアルモニタからでは送信できません。後で、Olive+のスクリプトを組んで、送信します。
iConClockから返ってくる応答パケットも、バイナリデータなので、スクリプトで受信します。
データの送受信が済んだら、切断します。
送信 | AT+CIPCLOSE |
受信 |
CLOSED OK |
では、肝心なリクエストの送信と、応答の受信部分をOlive+のスクリプトで実装します。先ほど、シリアルモニタで動作を確認した、UDP接続の確立部分なども、スクリプトで記述します。
macro sntp_serv="192.168.0.xx"; // SNTPサーバアドレス
macro sntp_port="123"; // SNTPサーバポート名
// UDP接続する
mlet cmnd="AT+CIPSTART="$""UDP"$"","$"$(sntp_serv)$"","$(sntp_port);
serialmon write,$(cmnd);
get_response 500;
UDPに接続します。get_responseは、ESP32の応答メッセージを受け取って、ログに出力する手続きです。内部の処理については、完成版スクリプト(test_udp.olv)で確認してください。
// SNTPリクエストを送信する
mlet cmnd="AT+CIPSEND=48";
serialmon write,$(cmnd);
get_response 500;
// SNTPパケットを送信する
serialmon write_byte,0x0b;
idx=1;
while (idx<48)
{
serialmon write_byte,0x00;
// 継続
idx=idx + 1;
}
SNTP V1のリクエストパケットは、全部で48バイトで、最初の1バイトが0x0bである以外、すべてのバイトが0x00です。
// レスポンス(含むバイナリデータ)を受信する
macro mdat;
repeat
{
serialmon read_byte,nres;
num2str mdat=nres,"%02x";
fprint fout,$(mdat)",";
} until (nres<0);
fprint fout,$,;
完成版スクリプト(test_udp.olv)では、もう少し複雑な処理をしていますが、レスポンスを読み易い様に、成形するための処理ですので、割愛してあります。
完成版スクリプト:test_udp.olv
FTDIとESP32がPCに接続された状態で、test_udp.olvを実行してください。シリアルポートを問い合わせるウインドウが開いたら、FTDIの通信ポート名を指定します。結果はtest_udp.logと言う名前のファイルに出力されていますので、テキストエディタなどで開いて確認してください。
実験サンプル:出力ログ(test_udp.log)
+IPD,48:(0c)(0a)(0a)(00)(00)(00)……(00)(e0)(0b)(de)(ec)(a2)(4d)(d8)(00)
「+IPD,48:」の部分までが、ESP32が出力してくるメッセージです。その後、()内に16進数2桁(1バイト)のデータが計48バイト出力されていて、これが、iConClockが返してきた、実行時点の時刻データです。
データの中身については、SNTPのドキュメントを参照してください。IETF(Internet Engineering Task Force)という組織が、RFC(Request For Comments)というドキュメントで規定しています。
SNTPのRFCドキュメントを検索する(別ウインドウが開きます)
RFCはどんどん更新されているので、古いドキュメントはObsoleteになって新しいドキュメントに上書きされて行きます。本ページ執筆時点の最新は、RFC-5905かな(?)。RFCは、インターネット関連のプログラムを組もうとすると、避けて通れないドキュメント類ですので、拒否反応を起こさないように!
本節では、SNTP V1で時刻合わせすることが目的ではなく、UDP通信の題材としてSNTPを選択しただけですので、データの授受ができたことで目的は果たせました。
最後にTCP通信を試します。
題材として、HTTPプロトコルを使って、HPのHTMLを取得してみます。
と、言っても、筆者もHTTPを喋るのは初めてですので、ドキュメントを検索してみます。
HTTPのRFCドキュメントを検索する(別ウインドウが開きます)
さっきのSNTPと同様に、HTTPもIETFのRFCで規定されています。本ページ執筆時点では、RFC-7230〜7235が最新らしいです。
Arduino IDEを起動して、FTDIのシリアルポートを指定し、シリアルモニタを開きます。改行コード「CR+LF」、通信速度「115,200bps」に設定します。
準備が整ったら、HTTPリクエストを送信して、本HPのトップページ「http://hello.world.coocan.jp/index.html」を取得してみます。
送信 | AT+CIPSTART="TCP","hello.world.coocan.jp",80 | |
受信 |
CONNECT OK |
本HPのサーバに接続します。 前節UDPとの違いは、"UDP"→"TCP"になったことです。 HTTPのポート名は、一般に「80」が使われるので、ポート名は80とします。 |
送信 | AT+CIPMODE=1 | 今回は、「UART-WiFiパススルーモード」を使って、HTTPサーバと直接通信してみます。 |
受信 | OK | |
送信 | AT+CIPSEND |
">"が出力されると、非同期シリアル通信の入出力が、直接、HTTPサーバと接続されます。 従って、この命令実行以降、ESP32のATコマンドではなく、HTTPのメッセージをやり取りすることになります。 |
受信 |
OK > |
|
送信 |
GET http://hello.world.coocan.jp/index.html HTTP/1.0 <空行を送信> |
|
HTTPサーバに対して、ファイルindex.htmlを送ってくれるように要求を送ります。 「GET」の行の後で、空行(<CR><LF>のみ)の送信が必要です。 |
||
受信 |
HTTP/1.1 200 OK <レスポンスヘッダ> <空行> <index.htmlの本体> : |
途中省略していますが、レスポンスヘッダの後で、トップページのHTMLが延々と送られてきます。結構分量があるのでご注意ください。 尚、本HPはS-JISなので、UTF-8で表示するシリアルモニタでは文字化けします。 最初の行に「200 OK」等と入っていれば、正しく通信できています。200番台以外のレスポンスだった場合は、エラーレスポンスです。どこかの手順を間違っています。 |
送信 | +++ |
用が済んだので、「UART-WiFiパススルーモード」を終了します。単体の「+++」を送信する必要があります。 「+++<CR><LF>」ではダメです。 よって、一旦、シリアルモニタの改行モードを「改行なし」に設定して、「+++」を送信してください。 ※送信後、改行モードを「CR+LF」に戻すのをお忘れなく。 |
送信 | AT+CIPMODE? |
「+++」の送信によって、「UART-WiFiパススルーモード」が解除され、通常モードに戻ったことの確認。 すなわち、「+++」の送信以降は、HTTPメッセージではなく、ESP32のATコマンドが受け付けられる状態になっています。 ※実験として確認したかっただけで、手順として確認の必要はありません。 |
受信 |
+CIPMODE:0 OK |
|
送信 | AT+CIPCLOSE | TCP接続を切断します。 |
受信 |
CLOSED OK |
UART-WiFiパススルーモードは、SMTPやPOP3サーバと通信する時にも、重宝するかもしれませんね。
接続するサーバとURLを変更すると、別なページの取得も可能なはずです。ただし、"https:"で始まるURLは、上の方法では取得できません。
HTMLの取得ができることを確認できましたが、今回取得したページのHTMLですら、20Kバイトを超えています。RAM容量2KバイトのArduinoで受け取って、「何ができるの?」ってことではあります。HTML全体を保持するだけのRAM容量すら、持っていない……。
今回のは、「インターネットアクセスの例題」としては成立するけど、実際どうするの(どう使うの)?ってところは疑問が残りますね。
兎にも角にも、ESP32を使い、WiFi無線LAN経由で、LAN内の機器と通信したり、インターネット上のサーバとの通信が可能となりました。
また、シリアルモニタで操作するのと同じことを、Olive+のスクリプトで実行させました。Olive+スクリプトの代わりをArduinoにやらせれば、ArduinoからESP32経由でインターネットアクセスが可能となります。
UDPとTCPを使った通信では、クライアントとして通信しましたが、ATコマンドでサーバとして振舞うこともできるみたいです。ただし、それこそ、非同期に外部から接続されて、非同期にパケットが届くので、非同期シリアル通信でハンドリングするのは、結構大変そうです。
「結局、何に使えるの?SNTPで時間を合わせるのが関の山?」と、思えないことも無いですが、本HPの野望は、SMTPでメールを送信することです。これが可能になると、色々と応用が広がるではないですか?
本HPで公開するかどうか?は未定ですが、一応、そんな野望を持ちながら、実験を続けていきたいと思っています。
※「#18-9 Moscovium+ESP32⇒メール送信」にて、メール送信アプリケーションを作って見ましたのでご参照ください。