Arduino UNO R4 WiFiをクライアントにする

Arduino

簡単なクライアントを作成する

ごく簡単なクライアントプログラムの例として、例によってWebクライアントを作成します。繰り返しになりますが、HTTPを実験に使うメリットを挙げると、

  • クライアントとサーバのやりとりが一往復で完結する単純なプロトコル
  • テキストでコマンドや結果がやりとりされるので理解しやすい
  • 個人で実験的にサーバを用意するのも簡単
  • 普段からよく使っているサービスだけに親しみやすい

などがあります。もちろんArduinoではブラウザのように画面をレンダリングするのは難しいので、受信したhtmlドキュメントをシリアルモニタ等で確認するようなプログラムを作成します。

早速実験

サーバの準備

xampp等で自宅LAN内にサーバを設置すると、プログラムの不具合などで不正アクセスと疑われるような心配もなく気兼ねなく実験できます。

PC 上に XAMPP をインストール&起動し、Wifi接続するネットワーク上のホストからアクセス可能なようにファイアウォールを適切に設定しておきます。また、取得するファイルとして、ドキュメントルート直下に以下のファイル test.html を配置します。

HTML
<!DOCTYPE html>
<html>
  <head>
    <title>Web Test</title>
  </head>
  <body>
    <h1>Web Test Page</h1>
  </body>
</html>

プログラム

C++
#include "WiFiS3.h"

// 無線LAN接続情報は別ファイルに移した
#include "arduino_secrets.h"
char ssid[] = _SSID;
char pass[] = _PASS;

// 接続先の情報
char host_name[] = "192.168.0.22";
int  host_port   = 80;
char host_file[] = "/test.html";

// クライアントのインスタンスの生成
WiFiClient client;

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

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

  // WiFiファームウェアバージョンの確認
  String fv = WiFi.firmwareVersion();
  if(fv < WIFI_FIRMWARE_LATEST_VERSION){
    Serial.println("ファームウェアが最新のものではありません");
  }

  // 無線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());

  if(client.connect(host_name, host_port)){
    // サーバにリクエストを送信
    char sendBuffer[128];
    sprintf(sendBuffer, "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n", host_file, host_name);
    client.print(sendBuffer);
  }
  while(true){
    while(client.available()){
      // サーバからのデータを受信
      char c = client.read();
      Serial.print(c);
    }
    if(!client.connected()){
      // サーバ側から切断されたら終了
      Serial.println("disconnected");
      client.stop();
      break;
    }
  }
}

void loop() {

}

サーバの実験の時と同様、同じディレクトリにSSIDとPASSWORDの情報を記述したファイル arduino_secrets.h を作成します。

C++
#define _SSID "your SSID"
#define _PASS "your PASSWORD"

“your SSID” および “your PASSWORD” の部分は、ご利用の無線LANのSSIDおよびパスワードを記述してください。

プログラム解説

無線LANへの接続部分はサーバの時とほとんど同じですので、相違点のみ説明していきます。

接続先の情報を定義
C++
// 接続先の情報
char host_name[] = "192.168.0.22";
int  host_port   = 80;
char host_file[] = "/test.html";

今回のプログラムでは、可能な限り簡単なコードにするため、接続先のサーバも取得するファイルも固定です。ここでは、接続先関係を指定する変数を3つ定義しています。

変数意味値の例
host_namechar[]接続先ホストのIPアドレス、
またはFQDN
IPなら “192.168.0.22”
FQDNなら “workshop.aaa-plaza.net”
など
host_portint接続先ホストのポート番号80
host_filechar[]取得するファイルのパス“/test.html”

このプログラム例では、192.168.0.22 のIPアドレスでWebサーバが稼働していることを想定しています。

自前で実験用サーバが用意できない場合は、本サイトのファイルをご利用下さい。
サーバのFQDN:workshop.aaa-plaza.net
ファイルのパス:/text.html

です。

※利用は常識の範囲ないでお願いします。不正なコマンドを使用したり、短時間に何度もアクセスするなど問題のあったIPアドレスは、故意・不具合を問わずブロックするかもしれません。

クライアントのインスタンスを生成する
C++
// クライアントのインスタンスの生成
WiFiClient client;

クライアントとして動作させるには、WiFiClientクラスのインスタンスを用意します。

※Javaとは異なり、C++では変数として宣言するだけでインスタンスが生成されます。

サーバへ接続する
C++
  if(client.connect(host_name, host_port)){
    // サーバにリクエストを送信
    char sendBuffer[128];
    sprintf(sendBuffer, "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n", host_file, host_name);
    client.println(sendBuffer);
  }

サーバ名とポート番号を指定してサーバに接続します。接続にはWiFiClient.connect()関数を使用します。

書式
C++
int WiFiClient.connect(char* host, uint16_t port)
または
int WiFiClient.connect(IPAddress ip, uint16_t port)
引数
名前意味
hostchar*接続先のサーバのFQDNを表す文字列。
IPアドレスをIPAddress型ではなくchar*型の文字列として与えても動作する。
ipIPAddress接続先のサーバのIPアドレス。
portuint16_t接続先のTCPポート番号を表す数値。
返却値
意味
0接続に失敗した
1接続に成功した

true/falseの論理値ではなくint型で返却されます。

今回のプログラムでは、client.connect()で接続が成功した場合のみサーバに対してリクエストを送信するように if文を使用しています。

サーバに対してリクエストを送信
C++
  if(client.connect(host_name, host_port)){
    // サーバにリクエストを送信
    char sendBuffer[128];
    sprintf(sendBuffer, "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n", host_file, host_name);
    client.print(sendBuffer);
  }

Webサーバに送信するリクエストの文字列を組み立てています。リクエストの書式は以下の通りです。

Plaintext
GET 要求するファイルのパス HTTP/1.1
Host: WebサーバのFQDN

1行目のGET~がWebサーバに対して送信されるコマンドです。GETはWebサーバ上にあるリソースをクライアントが取得するためのものです。

2行目のHostフィールドは、1台のホスト(同じIPアドレス)が複数のサーバ名で運用されている場合にどのサーバに対するリクエストなのかを識別するためのものです。HTTP/1.0ではそのホストのサーバ名が1つなら省略可能でしたが、HTTP/1.1では必須になりました。

3行目の空行が必須であることに注意してください。

なお、httpなどインターネット上のアプリケーションレベルプロトコルでテキストによるコマンドを送信するタイプのものは、原則として改行には CR+LF(C++での記法では“\r\n“)を使用します。

サーバに対して文字列を送信するには、WiFiClient.println()関数を使用します。

自分がサーバの時に接続したクライアントに対して文字列を送信するときも WiFiClien.print()関数を使用しましたが、今回は自分がクライアントでサーバに送信するのにも同じ WiFiClient.print()関数を使用します。ちょっと不思議な感じがしますがこれでちゃんと動きます。

サーバからのレスポンスを受信する
C++
  while(true){
    while(client.available()){
      // サーバからのデータを受信
      char c = client.read();
      Serial.print(c);
    }
    if(!client.connected()){
      // サーバ側から切断されたら終了
      Serial.println("disconnected");
      client.stop();
      break;
    }
  }

今回は、サーバからのレスポンスについては、フォーマットなど考えずにひたすら1文字ずつ受信してはシリアル出力に送出する、ということを繰り返しています(はっきりいって手抜きです!)。

ここでも、『(自分はサーバで)クライアントから送信されてきたデータを読み取る』のとまったく同じように、 WiFiClient.available()関数で受信データがあるかチェックをして WiFiClient.read()関数で1バイトずつ受信する、という処理を繰り返しています。今回は『(自分がクライアントで)サーバから送信されてきたデータを読み取る』処理でデータの流れる向きが逆なのですが、これでちゃんと動きます。

httpの場合、サーバからのデータ送信が終わった時点でサーバは通信を切断してしまうので、WiFiClient.connected()関数で通信状態をチェックして通信が切断されていたらループから脱出します。その際、WiFiClient.stop()関数でクライアント側でも通信切断の処理を行います。

受信データが複数のパケットに分割されている可能性もあるので、受信処理全体をwhile(true)で繰り返すようにしています。

loop()ではなにもしない
C++
void loop() {

}

今回もsetup()関数内だけですべて処理が終わってしまったので、loop()関数の中身は空です。

実行

プログラムを実行するとシリアルモニタにこのように表示されるはずです。赤矢印の範囲がサーバから受信した内容です。

まとめ

  • クライアントとして動作するには、WiFiClientクラスのインスタンスを用意する
  • WiFiClient.connect()関数でサーバと接続する
  • 受信は、自分がサーバ側の場合と同じく、WiFiClient.read()関数を使う
  • 送信は、自分がサーバ側の場合と同じく、WiFiClient.print()関数を使う

コメント

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