Arduino UNO R4 WiFiをサーバにする・その1/『HelloWorldサーバ』を作る

Arduino

Arduino UNO R4 WiFiをサーバにする

Arduino UNO R4 WiFiはボード上にWiFiモジュールを搭載しており、外部回路なしに無線LANへの接続が可能です。今回はArduino UNO R4 WiFiのネットワーク機能の基本的な使い方を試してみます。

まず今回は、サーバとして動作するプログラムを何例か、簡単のものから順に試作していきます。クライアントはまた別記事で。

Arduino UNO R4 WiFiで利用可能な通信規格

Arduino UNO R4 WiFi に搭載されている無線モジュール ESP32-S3 は、IEEE802.11b/g/n(2.4GHz帯)に対応しています。これらは20年くらい前のやや古めの規格で、残念ながらもっと新しい規格であるIEEE802.11ac/ad/axには対応していません。

通信速度は最高150Mbpsで、これも新しい規格がGbps単位になっているのに較べると遅めです。

とはいえArduinoはデスクトップPCに較べると処理速度も遅くメモリも少ないので、たとえ最新規格のような高速通信ができたとしてもそれを活かすことは難しいような気もします。

『Hello,World!サーバ』の製作

公式のサンプルはちょっと長いので、究極まで機能をそぎ落とした最小限のサンプルを作りました。

telnetで接続すると”Hello,World!”という文字列をクライアントに送信して直ちに切断する

という実用性皆無なものですが、Arduinoをサーバとして設定する方法とクライアントからの接続を受け付ける方法はこれで判ると思います。

名付けて『Hello,World!サーバ』です。

プログラム

ソースコード

C++
#include "WiFiS3.h"

// 無線LAN接続情報
char ssid[] = "your SSID";
char pass[] = "your PASSWORD";

// リスンポートを23番としてサーバのインスタンスを作成
WiFiServer server(23);

void setup(){
  // シリアルインターフェイスの初期化
  Serial.begin(9600);
  delay(1000);

  // WiFiモジュールの確認
  if(WiFi.status() == WL_NO_MODULE){
    Serial.println("WiFiモジュールがありません");
    while(true);
  }
  
  // 無線LANへの接続
  Serial.print("接続中...");
  while(true){
    if(WiFi.begin(ssid, pass) == WL_CONNECTED)break;
    Serial.print(".");
    delay(1000);
  }
  Serial.println("完了");
  
  // Arduinoに割り当てられたIPアドレスの確認
  Serial.print("IPアドレス:");
  Serial.println(WiFi.localIP().toString());

  // サーバの起動
  server.begin();
}

void loop(){
  WiFiClient client = server.available();
  if(client){
    // クライアントと接続した時の処理
    Serial.println("new client");
    client.println("Hello,World!");
    client.stop();
  }
}

プログラムの解説

ライブラリのインクルード
C++
#include "WiFiS3.h"

まず、Arduino UNO R4 WiFiでWiFiを機能を使うためのライブラリWiFiS3.hをインクルードします。これはR4用に開発環境をセットアップするときに自動的にインストールされるようです。

WiFiS3.hをインクルードすると、そこからさらにいろいろなインクルードファイルを読み込み、WiFi接続のためのクラスWiFiや、サーバ機能を管理するクラスWiFiServer、クライアントを管理するクラスWiFiClientなどが定義されるようになっています。

無線LANの接続情報
C++
// 無線LAN接続情報
char ssid[] = "your SSID";
char pass[] = "your PASSWORD";

接続先の無線LANの SSID およびパスワードを設定します。
“your SSID”と”your PASSWORD”の部分はご利用の環境に合わせて書き直してください。

WiFiServerクラスのインスタンスを生成
C++
// リスンポートを23番としてサーバのインスタンスを作成
WiFiServer server(23);

サーバとして動作させる場合、WiFiSeverクラスを利用するのが簡単です。コンストラクタの引数はListen port番号(クライアントの接続を待ち受けるポート)です。この例では、Listen portとしてtelnetのポートである23番を指定して、serverという名前のインスタンスを生成しています。

シリアル通信の設定
C++
void setup(){
  // シリアルインターフェイスの初期化
  Serial.begin(9600);
  delay(1000);

Serial.begin()関数はWiFiとは直接関係ありません。動作確認やエラー通知、DHCPで取得したArduinoのIPアドレスなどをシリアルモニタで確認できるようにSerial通信の設定をしています。この例では速度が遅めの9600になっていますが、環境に合わせてもっと速くしても大丈夫です。
delay()関数もWiFi接続には関係ないのですが、ここにウェイトを入れないとうまくメッセージがシリアル出力されないようなので1000msだけ待っています。

シリアルモニタでの監視なしに運用する場合は、この2行は削除してしまって構いません。

WiFiモジュールの存在チェック
C++
  // WiFiモジュールの確認
  if(WiFi.status() == WL_NO_MODULE){
    Serial.println("WiFiモジュールがありません");
    while(true);
  }

Arduino UNO R4にはWiFiモジュールが搭載されていないモデルMinimaもあるので、まず最初にWiFiモジュールがあるかどうかのチェックをしています。チェックにはWiFi.status()関数を使用します。

書式
C++
uint8_t WiFi.status()
返却値

返却値はすべて状態を表すコード(整数値)です。判りやすいように WiFiType.h の中で定数(enum型の列挙子)として定義されています。

WiFi.status()関数の返却値がWL_NO_MODULEの場合、WiFiモジュールが存在しません。その場合は『WiFiモジュールがありません』というエラーメッセージをシリアルに送信します。
また、WiFiモジュールが存在しない場合はこの先の処理にはまったく意味がないので、while(true); という無限ループでこれ以上は何の処理もしないようにします。returnでsetup()関数を終了するとloop()関数を実行してしまうので、このようになっています。

シリアルモニタでの監視なしに運用する場合はSerial.println()は削除してしまって構いません。

アクセスポイントに接続する
C++
  // 無線LANへの接続
  Serial.print("接続中...");
  while(true){
    if(WiFi.begin(ssid, pass) == WL_CONNECTED)break;
    Serial.print(".");
    delay(1000);
  }
  Serial.println("完了");

アクセスポイントへの接続には、WiFi.begin()関数を使用します。

書式
C++
int WiFi.begin(char* ssid, char* pass)
引数
名前意味
ssidchar*接続するネットワークのSSID(文字列へのポインタ)
passchar*接続するネットワークのパスワード(文字列へのポインタ)
返却値

接続に成功した場合:WM_CONNECTED
接続に失敗した場合:WM_CONNECT_FAILED

WM_CONNECTED および WM_CONNECT_FAILED は、 WiFiTypes.h 内で定義された定数(enum型の列挙子)です。

このプログラム例では、接続に失敗した場合は1000ms待った後に再試行するようになっています。接続に成功するまで無限に試行を繰り返します。

3つある Serial.print()Serial.println() はすべて動作確認のためのメッセージをシリアル出力するものです。シリアルモニタでの監視なしに運用する場合は削除してしまって構いません。
この例では、

  • 接続試行前に『接続中…』
  • 接続試行を繰り返すたびに『.(ピリオド)』
  • 接続に成功すると『完了』

という文字列をそれぞれシリアル出力しています。これらにより、プログラムの実行がどこまで進んでいるか、接続試行を繰り返しているのかプログラムがハングアップしているのか、などの判断が出来るようにしてあります。

IPアドレスの表示
C++
  // Arduinoに割り当てられたIPアドレスの確認
  Serial.print("IPアドレス:");
  Serial.println(WiFi.localIP().toString());

今回のプログラムでは、ArduinoのIPアドレスはDHCPから取得するようになっています。後で接続実験を行う際にアドレスが判らないと困るので、Arduinoに割り当てられたIPアドレスをシリアル出力し、シリアルモニタ等で確認できるようにしてあります。IPアドレスの取得には WiFi.localIP()関数を使用します。

書式
C++
IPAddress WiFi.localIP()
返却値

返却値は割り当てられたIPアドレスです。IPAddressクラスで返されることに注意してください。

この例では、IPAddressクラスの返却値を“123.45.67.89”の形式の文字列に変換するために toString()関数を使用しています。

※実は Serial.println(WiFi.localIP()) としても “123.45.67.89” の形式でシリアル出力されます。これはIPAddressクラスがPrintableクラスのサブクラスとして定義されており、print()関数の中では自動的に”123.45.67.89″の形式の文字列に変換されるようにprintTo()関数がオーバーライドされているからです。

サーバの起動
C++
  // サーバの起動
  server.begin();

無線LANへの接続が終わったら、サーバを起動します。サーバの起動には WiFiServer.begin()関数を使用します。

書式
C++
void WiFiServer.begin()

この関数には引数も返却値もありません。

クライアントからの接続を取得
C++
  WiFiClient client = server.available();

loop()関数内では、まずクライアントからの接続を取得します。クライアントからの接続リクエストを取得するには、WiFiServer.available()関数を使用します。

書式
C++
WiFiClient WiFiServer.available()
引数

なし

返却値

新たに接続したクライアントがある場合は、そのクライアントを表す WiFiClientクラスのインスタスが返却されます。

WiFiServer.available()関数は、クライアントからの接続リクエストが届くまで待ち受ける関数ではありません。新たに接続してきたクライアントがあってもなくても、関数自体はすぐに結果を返して処理は先に進んでしまいます。

これとよく似たWiFiServer.accept()関数というものもあって、どう使い分けるのだろう…とソースを調べてみたら、available()関数の中身は

C++
WiFiClient WiFiServer::available(byte* status) {
    (void) status;
    return accept();
}

でした。『よく似た』どころか、available()関数は処理をaccept()関数に丸投げしていました(笑

接続したクライアントがあったかどうかの判定
C++
  if(client){
    // クライアントと接続した時の処理
    Serial.println("new client");
    client.println("Hello,World!");
    client.stop();
  }

前項にも書いたとおり、WiFiServer.available()関数は新たに接続してきたクライアントがあってもなくても処理が先に進んでしまいます。ここでは返却値clientをチェックして、新たに接続してきたクライアントがあった場合のみ、クライアントに対する送信処理を行うようにしています。

普段Javaを使っている人には奇妙な条件式に見えるかも知れませんが、CやC++では結果が論理型以外になる式について、

『数値が0』『オブジェクトの実態がない』などをfalse
『数値が0以外』『オブジェクトの実態がある』などをtrue

と読み替えます。

クライアントと接続した場合の処理
C++
  if(client){
    // クライアントと接続した時の処理
    Serial.println("new client");
    client.println("Hello,World!");
    client.stop();
  }

Serial.println()は動作確認用です。クライアントが接続した場合に『new clilent』とシリアル出力します。不要な場合は削除して構いません。

クライアントに対して文字列を出力する場合は、WiFiClient.print()関数WiFiClient.println()関数を使用します。使い方はSirial.print()関数/Sirial.println()関数とまったく同じです。

この例では、『Hello,World!』+改行文字という文字列をクライアントに送信しています。

このサーバの仕様は『telnetで接続すると”Hello,World!”という文字列をクライアントに送信して直ちに切断する』なので、文字列送信後には client.stop()関数でクライアントとの通信を終了(コネクションを切断)します。

以上で、クライアントと接続した場合の処理は終了です。

実行してみよう

Windowsのtelnetコマンドで接続する場合
1.サーバ起動

Arduinoでプログラムを実行すると、自動的に無線LANに接続します。数秒程度でDHCPからIPアドレスを取得、結果が表示されます。この例では『192.168.0.15』がArduinoに割り当てられたIPアドレスです。

2.telnetでArduinoに接続

telnetクライアントを用いて、ArduinoのIPアドレスに接続します。

上の画面写真では、もっとも手軽に使用できるtelnetクライアントとしてWindowsのコマンドプロンプトから telnetコマンド を使用しています。telnetコマンドは、

Bash
telnet  接続先のIPアドレス  [ポート番号]

の書式で使用します。ポート番号の指定はオプションで、今回は使用していません(Arduinoのサーバプログラム側でtelnetコマンドのデフォルト使用ポートで待ち受けするようになっているため)。

※Windowsでは、初期状態ではtelnetコマンドが無効化されています。有効化の方法は別記事

3.サーバから文字列”Hello,World!”が送信されてくる

telnetクライアントは、サーバから送信されてきた文字列をそのまま表示します。このプログラムでは、サーバはクライアントと接続すると『Hello,World!』という文字列を送信し、すぐに通信を終了しますので、telnetクライアント泡では『Hello,World!』という文字列表示され、接続が切断されます。

このように動作すれば成功です。

動画
TeraTermで接続する場合

ターミナルエミュレータの定番、TeraTermでも実験することができます。ただTeraTermは初期設定ではサーバとの通信が切断されると自動的にウィンドウを閉じてしまうため『Hello,World!』というメッセージを確認できません。そこで、あらかじめウィンドウを閉じないように設定を変更します。

1.『新しい接続』のウィンドウをいったん閉じる

TeraTermを起動すると自動的に『新しい接続』ダイアログが開くので、『キャンセル』をクリックしていったんダイアログを閉じます。

2.『TCP/IP設定』ダイアログ

メニューの『設定』→『TCP/IP』を選択します。

『TCP/IP設定』ダイアログが開くので、

①『自動的にウィンドウを閉じる』のチェックを外します。
②『OK』ボタンをクリックします。

3.Arduinoに接続

メニューの『ファイル』→『新しい接続』を選択します。

『新しい接続』ダイアログが開くので、

①TCP/IPを選択します。
②Arduinoに割り当てられたIPアドレスを入力します。
③『サービス』で『Telnet』を選択します。
④『OK』ボタンをクリックします。

4.サーバから文字列”Hello,World!”が送信されてくる

『Hello,World!』という文字列を送信し、すぐに通信を終了します。

まとめ

  • Arduino UNO R4 WiFi で無線LANに接続するには、WiFiS3.h を使用するのが簡単
  • サーバプログラムを作るには、WiFiServerクラスを使うと簡単

コメント

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