M5Stack同士でWiFi, UDPによる双方向リアルタイム同時通信する実験

M5Stackを2台使ってWiFi,,UDP双方向送受信通信 M5Stack

こんばんは。

今回は2台のM5Stack同士でWiFiのUDP通信を使って、双方向リアルタイム同時通信してみる実験を紹介したいと思います。
これはなかなかスゴイですよ。
まるでWebSocketのように通信できるんです。

そもそも、この実験をした理由は、前回記事のカメラ画像をM5StackへWiFiでリアルタイム送信したくて、UDP通信をいろいろ試していた結果得られたものです。

スポンサーリンク

Twitterでは既に以下のようなカメラ画像を送信することに成功したお知らせをしました。

これを実現するためには、まずArduino core for the ESP32のWiFi UDPライブラリを理解しなければなりません。
このUDPライブラリについては、ネット上ではいろいろ情報があるものの、画像等のストリーム通信方法や、双方向同時通信については殆ど情報がありません。

ということで、私自身で独自にWiFiUDPライブラリを用いて双方向リアルタイム通信をする簡単なプログラム(スケッチ)を組んでみました。
以下の動画をご覧ください。

どうですか?
M5Stackのボタンでもう一方のM5Stackの画像が動くという、不思議な動画ですね。
これだけパッと見た方には何をやっているかサッパリわからないと思います。
でも、注目して欲しいのは、四角形が移動しているのは、片方のM5StackからWiFi, UDPで位置データを連続して送信していて、もう一方のM5Stackがデータを受信して図形を動かしているんです。
つまり、連続したデータを受信しながら、ボタン操作でUDPデータを送信してもう一方のM5Stackの図形を動かしているんです。
わかります?
意外とスゴイですよね。ここまで図形がスムースに動いて、リアルタイムに追従してくれることが・・・。

私は以前、スマホとM5StackおよびESP32間でWebSocketを使って双方向リアルタイム通信をやったことがありました。
また、Blynkというソフトを使ったり、Firebaseというクラウドサーバーを使う双方向通信はやりましたが、ローカル環境でM5Stack間の双方向リアルタイム通信はやったことがありませんでした。

WebSocketを使うことも考えましたが、スマホブラウザを使わない方法はかなり難し過ぎてあきらめました。

ならば、UDP通信なら出来そうだと思ったのですが、パケットロスが多すぎて、成功した事例があまり見つかりませんでした。

私自身が実際作ってみても、最初の頃はパケットロスが多く、うまく行きませんでした。
UDPってこんなものかな・・・と、落胆したものでした。

しかーし!
いろいろ実験して調べていくと、UDPでもかなりの多量データ高速通信ができることが分かったんです!!!
しかも双方向リアルタイム同時通信も可能だったんです。

ということで、動画UDP通信の前に、まずは簡単なスケッチでUDP双方向リアルタイム同時通信の方法を紹介したいと思います。

    【目次】

  1. 使ったもの
  2. UDP通信について雑感
  3. Arduino core ESP32 ライブラリのTCPとUDPのデータ送信関数を辿ってみた
  4. UDPで双方向送受信するには? ポート番号とは?
  5. WiFiUDPライブラリによるUDP双方向通信について
  6. UDP通信はブロードキャストにすると速度が極端に遅くなるので注意
  7. Arduino core for the ESP32 のインストール
  8. M5Stackライブラリのインストール
  9. SoftAPモードのWiFi, UDP双方向通信M5Stackスケッチ(プログラミング)例
  10. 外部ルーターを介すSTAモード同士のWiFi, UDP双方向通信M5Stackスケッチ例
  11. 編集後記

使ったもの

M5Stack ( ESP32搭載 )

Espressif Systems の WiFi & Bluetooth マイコン ESP32 を搭載し、M5Stack社が技適を取得し、液晶ディスプレイ、ボタンスイッチ、電池モジュール、micro SDカードスロット、スピーカー、Grove端子等を搭載した、全部入りモジュールです。
今回は以下の2台を使いました。
ただ、スイッチサイエンスさん販売のM5Stack Basicは現在Amazonでは販売されていないようなので、他の販売店をご利用ください。

※M5Stack Gray(9軸IMU搭載)現在は販売終了しております

 

(追記)
M5Stack Basicは、この記事を書いた当時より格段にバージョンアップしております。
以下のスイッチサイエンスさんの公式サイトをご参照ください。
https://www.switch-science.com/collections/%E5%85%A8%E5%95%86%E5%93%81/products/9010

良質なUSB Type-C 変換ケーブル

WiFi で多量のデータを連続してUDP送受信する場合、多量の電力を瞬間的に消費します。
それによる電圧降下に耐えうる、良質で太く短いUSBケーブルが必要です。

私は以下のケーブルを使いました。

ただし、パソコンのUSBポートによって、端子が異なりますので、ご自分の環境に合わせて適切なUSB端子のものを選んでください。

また、このUSBケーブルは硬くて取り回し難いです。
取り回し難いということは、それだけケーブルが良質だということなので、これは仕方ありません。

その他、Arduino IDE が動くパソコン、WiFiルーター、USBケーブル等

UDP通信について雑感

はじめに断っておきますが、私はTCPやUDPについてあまり詳しくありません。
間違えていたら教えてくださいね。

パソコンやスマホのブラウザでWebページを見る場合は主にTCP/IP通信が使われています。
昔勉強して、もう忘れてしまいましたが、にわか知識で簡単に言うと、サーバー側とクライアントのパソコン側で、複雑なコネクション確立手順を踏んだ後にデータが送られてくるというやつです。
相手が通信ポートに接続しているか確認してからデータを送るという手順を踏んだりします。
送られてきたデータが欠けていると、そのデータが揃うまで待ったりするっていうやつです。
HTTPやHTTPS通信なんかはTCP/IPですね。

対して、UDP通信は、TCPに比べてコネクションは簡略化されています。そして、相手が通信ポートを解放してコネクション確立しているかどうかという確認はせずに、一方的にデータを送信します。
受信側はデータが欠けているかどうかという確認はしないので、通信トラフィックが一杯で送信できなかった場合は当然データ欠損(パケットロス)が発生します。

逆に、受信側のデータ処理が間に合わなくて受信し切れない場合でも、送信側はそんなことお構いなしでデータを送信し続けますので、当然パケットロスします。

そんでもって、昔勉強して既に忘れていたのですが、TCPでコネクション確立して動画転送などをする場合は「ストリーム」という専門用語が使われ、対してUDPの動画転送等は「データグラム」という専門用語らしいです。

私は工事担任者の資格持っていても所詮ペーパーですから、そんな用語すっかり忘れてしまいましたね。
個人的には動画転送などは全部ストリームで良いと思うのですがね・・・。
専門用語は嫌いです!!! (と言っても自分自身専門用語多用してますが。。。)
データグラムっていう用語は私には親しみがありませんのであまり使いたくないですね。

正確にはよく知らないのですが、YouTube動画なんかはTCPを使ってストリーミングしているそうですが、TCPでコネクション確立するせいでしょうか、10秒前後遅延するそうです。
対して、ライブ配信はUDPが使われているという情報があり、遅延はごくわずからしいです。
詳しいことは分かりませんので、ネットで調べてみて下さい。

そんなこんなで、UDP通信で多量のデータを転送する場合、受信側の計算処理は送信側のデータ転送間隔より速い必要があり、送信側はネットワークの環境に合わせてデータ送信間隔を決めないといけないことが想像できると思います。
プログラミングする場合、そこを気を付けることが大事ではないか思います。
マイコンのCPU処理速度は速ければ速いほど良いですね。

Arduino core ESP32 ライブラリのTCPとUDPのデータ送信関数を辿ってみた

さて、Arduino core for the ESP32のライブラリ群によるTCPやUDPの扱いはどうなっているのか気になりますよね。

特に疑問に思ったのは、以前のこちらの記事で紹介したような、M5Cameraとスマホとの連携で、ESP32-Cameraライブラリを使った場合です。
これって、すごく不思議に思いませんか?
HTTPでスマホとM5Cameraでコネクション確立して、その後「Stream」ボタンを押すと、カメラ画像がストリーミングでスマホに表示されるんです。

遅延はごくわずかで、ほとんどリアルタイムで、ライブストリーミングですよね。
TCPのコネクション確立なんてやっていると、とてもこのリアルタイム性は出ないと思います。
この方法でM5Stackへ送信できないのかな? と思いました。

そこで、Arduino core for the ESP32ライブラリのソースを辿りにだどってみました。

スマホのクライアント側とコネクション確立した後、実際に画像データを送っているのは、
httpd_resp_send_chunk
という関数です。
これは esp_http_server.h というファイルで定義されています。
それから更に辿っていくと、ESP-IDFでコンパイルされたアセンブラファイルの中の、
httpd_default_send
という関数にたどり着き、更にその中の
send
という関数にたどり着きました。

この関数は、socket.h というヘッダーファイル内で、
lwip_send
という関数で定義されていて、そのソースファイルはどうしても見つけられません。
負けずにネットで更に調べてみると、どうやらFreeRTOSの
FreeRTOS_send
という関数のようです。
この関数はFreeRTOSの以下のリンクに詳細が書いてあります。

https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_TCP/API/send.html

それを見ても意味不明だったので、それ以上追求するのを止めました。
この関数は、TCPソケットにデータを送信する関数と書いてあります。

対してArduino core ESP32のWiFiUdpライブラリの場合はたどり易くて、write関数内に
sendto
という関数がありました。
それはやはり、ESP-IDFでコンパイルされたアセンブラファイル中にあり、
FreeRTOS_sendto
という関数が大元のようです。
これはFreeRTOSの以下のリンクに詳細が書いてあります。

https://freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_UDP/API/sendto.html

結局良く分からず、不明のまま追跡を止めましたが、要するに、画像データの様な多量のデータを一気に送る場合でも、TCP用の送信方法とUDP用の送信方法の2種類があるということです。
ネットでいろいろ調べてみると、TCPの場合、実際の画像データの前にヘッダと呼ばれるいろいろなコネクション確立やトラフィック管理等のデータを付けて送信するようで、対してUDPの場合のヘッダは簡略化されていてヘッダのバイト数も少ないということなので、実際に画像データを送信する場合には、TCPならTCP用のデータ送信関数、UDPならばUDP用のデータ送信関数が用意されているということのようです。

では次の節ではUDPで双方向送受信するにはどうすれば良いかを考えてみます。

コメント

  1. teru より:

    「デバイス側の一つのポートには一方向しかデータは送れない」ですが、普通に間違いですね。
    この記事のUDP通信サンプルの場合、 udp.begin(myIP, my_server_udp_port); でソケットにポートを割り当て、同じ udp インスタンスを使用してパケット受信とパケット送信をしているので、「1つのポートで送受信」を行っています。
    パケットには宛先IPアドレス・ポートだけではなく、送信元IPアドレス・ポートが含まれており、通常アプリケーションから取得可能です。
    WiFiUdpのソースコードを読んでみたところ、 udp.parsePacket() でデータを受信した後に、 udp.remoteIP()、 udp.remotePort() で取得できるようですので、確認してみてください。
    udp.beginPacket(to_udp_address, to_udp_port) を呼び出すと、 remoteIP、remotePort の値は上書きされてしまうようですので、出力するタイミングにはご注意ください。
    下記のようなコードで、別の WiFiUdp インスタンスを使用する場合と比べると、違いが分かりやすいと思います。

    WiFiUdp udp_send;
    udp_send.beginPacket(to_udp_address, to_udp_port);
    udp_send.write((uint8_t)send_position);
    udp_send.write(send_direction);
    udp_send.write(send_color_num);
    udp_send.endPacket();

    Webページを取得するのに、相手先IPアドレス・ポートを指定して自分のIPアドレス・ポートを指定しなくてよいのは、送信元IPアドレス・ポートは、OSが自動的に割り当てるためです。

    WiFiUdp がどこで送受信のIPアドレスとポートを割り当てているかというと…… bind 関数の呼び出しがそれになります。
    bind をせずにいきなり sendto すると、その時にOSが自動的にIPアドレスとポートを選択して、sendtoに指定したソケットに割り当てます。

    * 「OSが~」と書いていますが、これは普通のPCを念頭においていいます。ESP32の場合は事情が異なる気がします。
    * WiFiUdpの動作に付いては、ソースコード見て判断した内容になります。Arduinoは使用していないので実際に確認していない点、ご容赦ください。
    * WiFiUdpのソースコードは https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiUdp.cpp を参照しました。使用しているライブラリが違っていたらごめんなさい。
    * 「ポート開放」ってファイアウォールの話と混ざってませんか……?

    • mgo-tec mgo-tec より:

      teruさん

      先日別記事でご指摘いただいたのに、新たに記事をご覧いただいた上にご指摘いただき、本当にありがとうございます。
      当方で検証する時間が取れなくて、お返事遅くなり申し訳ございません。

      結論は、teruさんのおっしゃる通りでした。
      シリアルモニターでremotePort関数出力をしてもイマイチ良く分からないので、Windows PC のソフトWiresharkでパケットを解析してみました。
      udp.begin関数で、ポートを55555に設定した場合、受信ポートは55555ですが、送信ポート番号は下図の様になっていました。

      Wireshark解析

      これによると、送信ポート番号も受信ポートと同様に55555になっていることが判明しました。
      おかげ様で、teruさんのご指摘が無かったら、Wiresharkで調べようという意欲が働かず、曖昧なまま終わっていたと思います。
      とっても勉強になりました!!!

      今、次にアップする記事の編集に追われているので、それが済み次第徐々にこの記事も修正していく予定です。
      こんなド素人ブログを読んでいただき、そして適切なコメントをしていただき、感謝感謝感謝です。
      ありがとうございました。
      m(_ _)m

  2. Crane より:

    参考になります。
    1対多の場合の方法をが知りたいのですが、やられていますか。

    • mgo-tec mgo-tec より:

      Craneさん

      随分古い記事ですが、ご覧いただきありがとうございます。
      1対多については、この記事で書いてあるようにブロードキャストアドレスを使えば良いと思います。
      ただ、ブロードキャストアドレスを使うと、ルータの設定によって極端に速度が遅くなる場合があるようです。
      現に私の場合は遅く過ぎて使い物にならなかった記憶があります。

      • Crane より:

        ブロードキャストの場合、複数相手に同じデータを送信すると思うのですが、送信元が複数あり受信先は1つとして、送信されてきたデータを1箇所で集めることが出来たらと思った次第です。

        • mgo-tec mgo-tec より:

          Craneさん

          なるほど、そういうことでしたか。
          そういうのはUDPではやったことがないので残念ながら私にはわかりません。
          ただ、その場合は動画ストリーミングの様な重いデータではなく、短いデータ送信ならば、UDPではなくTCPでM5Stackサーバーを作れば、複数端末から送信でも受信できるかと思います。要するにM5Stackでホームページを作るようなものです。
          ただ、私は今は多忙でM5Stackを触ることもプログラミングすることもできない状態なので、あくまで想像でしかお答えできませんが…。

          • RUNA より:

            よく参考にさせていただいています。
            すみませんお手数ですが、この「UDPによる双方向リアルタイム同時通信プログラム」でひとつ教えてください。

            M5.begin() を setup()ではなく、別タスクで実行されている理由を教えていただけませんでしょうか。

            よろしくお願いします。

          • mgo-tec mgo-tec より:

            RUNAさん

            記事をご覧いただき、ありがとうございます。

            この記事は2019年に書いた古いもので、私自身は今となってはM5Stackをまったく触っておりません。
            そんなわけで、すっかり忘れてしまいました。

            ですが、久々にソースコード見てみると、何となく思い出しました。

            loop関数でUDPデータの送受信を行っていて、別タスクではLCD(液晶ディスプレイ)への描画をしています。
            LCDの描画はかなりCPUを消費するので、同じタスクに置いてしまうとUDP送受信している間はLCD描画がストップしてしまいます。
            すると、動画がカクカクした状態になって、スムースに動かなくなるためです。
            よって、UDP送受信とLCD描画は別タスクにして、同時進行させています。
            これはデュアルコアのESP32だから可能なのです。

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