ArduinoでESP-WROOM-02Uを使う その2(TCPクライアントにする)

Arduino

ATコマンドでTCP接続をしてみる

それでは、ESP-WROOM-02U を使用して TCP 通信を行ってみましょう。まずは ATコマンドを手打ちしてWebサーバに接続し、htmlファイルを取得してみます。

準備

テスト用htmlファイルの準備

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

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

製作

スケッチ

Arduino IDEで以下のスケッチをコンパイル&実行して下さい。内容は先の通信速度設定のプログラムから、シリアルモニタでATコマンドを実行できるようにした部分だけを抜き出したものです。

C++
#include <SoftwareSerial.h>

SoftwareSerial wifiSerial(2, 3); // RxD=2, TxD=3

void setup() {
  Serial.begin(9600);
  wifiSerial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    wifiSerial.write(Serial.read());
  }
  if (wifiSerial.available()) {
    Serial.write(wifiSerial.read());
  }
}

実行

それでは、動作テストの時と同様、Arduinoのシリアルモニタを使って以下のコマンドを順番に実行してみましょう。

コマンド:AT+CWMODE=1

AT+CWMODE コマンドで、ステーションモードに切り替えます。詳しい説明は前記事をご覧下さい。

コマンド:AT+CWJAP=”ssid“,”password

AT+CWJAP コマンドは、アクセスポイントに接続します。詳しい説明は前記事をご覧下さい。

コマンド:AT+CIPSTART=”TCP”,”host_address“,80

AT+CIPSTART コマンドは、指定のホストに接続します。コマンドの書式は、

AT+CIPSTART="protocol","host_address",port

です。指定する値は以下の3つです。

項目意味値の例
protocol接続プロトコルを表す文字列“TCP”
host_address接続先のIPアドレスまたはドメインを表す文字列“192.168.0.1”
port接続先のポート番号を表す数値80

接続すると、

Plaintext
CONNECT

OK

とメッセージが返ってきます。

※自前で実験用サーバが用意できない場合は、本サイトをご利用下さい。接続コマンドは

PowerShell
AT+CIPSTART="TCP","workshop.aaa-plaza.net",80

です。

コマンド:AT+CIPMODE

AT+CIPMODE は、ATコマンドモードまたはパススルーモードを設定します。パラメータはモードを表す数値です。

0 を指定するとコマンドモードになります。ESP-WROOM-02U にシリアル送信したデータ(文字列)は、コマンドとして解釈されます。

1 を指定するとパススルー(トランスペアレント)モードになります。このモードでは、ESP-WROOM-02U にシリアル転送されたデータは、そのままWifiでネットワークに送出されます。

この実験ではパススルーモードに変更するので、コマンドは

PowerShell
AT+CIPMODE=1

となります。モード変更に成功すると

Plaintext
OK

というメッセージが返ってきます。

コマンド:AT+CIPSEND

AT+CIPSEND コマンドは、データをネットワークに送信するコマンドです。

コマンドモード(CIPMODE=0)の場合はコマンドのパラメータとして送信データを記述しますが、
パススルーモード(CIPMODE=1)の場合はパラメータなしでこのコマンドを実行した後、

Plaintext
OK

>

とメッセージが返され、これ以降の入力がWifiでネットワークに送出されるようになります。

リクエスト文字列:GET /test.html HTTP/1.0

これは ESP-WROOM-02U でコマンドとして解釈される文字列ではなく、Wifiでネットワークに送出され、Webサーバに届く HTTP のリクエストです。

Plaintext
GET /test.html HTTP/1.0

2行目に空行があることに注意して下さい。『GET~』の行を入力した後、もう一度シリアルモニタの送信文字列入力欄で Enter を空打ちし、CR+LF を送信します。ここまでのATコマンド入力と異なり、『GET~』の部分はエコーバック(受信欄に表示)されません。

成功すると、以下のようなWebサーバからのレスポンスが表示されます。

HTML
HTTP/1.1 200 OK
Date: Tue, 03 Oct 2023 14:57:45 GMT
Server: Apache/2.4.27 (Win32) OpenSSL/1.0.2l PHP/7.1.9
Last-Modified: Mon, 02 Oct 2023 08:06:30 GMT
ETag: "75-606b74079bbe2"
Accept-Ranges: bytes
Content-Length: 117
Connection: close
Content-Type: text/html

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

※本サイトのサーバで実験する場合は、リクエストヘッダに Host: フィールドが必須ですので、送信する文字列は以下のようになります。

Plaintext
GET /test.html HTTP/1.0
Host: workshop.aaa-plaza.net

3行目が空行であることに注意して下さい。1行目・2行を入力した後、もう一度シリアルモニタの送信文字列入力欄が空のまま Enter キーを空打ちし、CR+LFを送信します。

+++(パススルーモードの終了)

パススルーモードでは(当然ながら)コマンドを受け付けません。パススルーモードを終了するには、改行コードを『なし』に設定した状態で『+++』(半角のプラスを3つ)を送信して1秒待つ、という特殊な手順が必要です。

コマンド:AT+CIPCLOSE

AT+CIPCLOSE コマンドは、通信を終了します。コマンド入力の前に、『改行なし』に変更したのを『CRおよびLF』に戻すのを忘れないようにして下さい。

成功すると

Plaintext
CLOSED

OK

というメッセージが返ってきます。

これでATコマンド手打ちによるTCP接続の実験は終了です。

プログラムからTCP接続する(クライアントとして)

では今度は、プログラムから自動的に通信してみましょう。最初の例として、ATコマンドで試したのと同様にWebサーバに対してクライアントとして接続するプログラムを作成します

準備

ライブラリのインストール

Arduino上のプログラムから ESP-WROOM-02 を Wifiモジュールとして扱うには、もちろんSofwareSerial.print(“AT+CWMODE=1”); などとATコマンドを送信するプログラムを作成しても動くことは動くのですが、さすがにプログラミングの手間が大きくなります。そこで、簡単にESP-WROOM-02(というかESP8266)を利用できるライブラリをインストールしてしまいましょう。

ダウンロード

まず、こちらのサイトにアクセスして下さい。

GitHub - itead/ITEADLIB_Arduino_WeeESP8266: An easy-to-use Arduino ESP8266 library besed on AT firmware.
An easy-to-use Arduino ESP8266 library besed on AT firmware. - GitHub - itead/ITEADLIB_Arduino_WeeESP8266: An easy-to-us...

ライブラリファイル『ITEADLIB_Arduino_WeeESP8266-master.zip』をダウンロードします。

インストール

Arduino IDEを起動し、メニューの『スケッチ』→『ライブラリをインクルード』→『ZIP型式のライブラリをインストール』を順にクリックします。

ファイル選択画面が表示されたら、先ほどダウンロードした『ITEADLIB_Arduino_WeeESP8266-master.zip』を選択します。

ソースを修正

当サイトのWifi関係の実験では、Arduino と ESP-WROOM-02 との通信に SoftwareSerial を使用する予定です。しかしインストールしたライブラリは、デフォルトでは SoftwareSerial に対応していません。対応させるためには、少々のソースの修正が必要です。

とはいっても、修正は簡単です。ライブラリがインストールされたフォルダ(Windowsの場合、デフォルトでは ドキュメント\Arduino\libraries\ITEADLIB_Arduino_WeeESP8266-master\)の中にある ESP8266.h をテキストエディタで開き、27行目の行頭にある『//』を削除して上書き保存するだけです。コンパイル等は不要です。

修正前

↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

修正後

以上で、Arduino IDEでWifi(ESP8266)用のライブラリを使用できるようになります。

スケッチ

今回のスケッチは以下の通りです。

C++
#include "ESP8266.h"
#include <SoftwareSerial.h>

// 以下は各自の環境に合わせて下さい
#define SSID ”HOMENETWORK"
#define PASSWORD "wifipassword"
#define HOST_ADDR "192.168.0.15"
#define HOST_PORT 80
#define HOST_FILE "test.html"

SoftwareSerial wifiSerial(2, 3);    // RxD=2, TxD=3
ESP8266 wifi(wifiSerial);

void setup(void)
{
  Serial.begin(9600);
  wifiSerial.begin(9600);

  // wifi restart
  Serial.print("wifi restart.");
  while(!wifi.restart()){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("ok");

  // kick (check alive)
  Serial.print("kicking.");
  while(!wifi.kick()){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("ok");

  // set station mode
  Serial.print("set station mode.");
  while(!wifi.setOprToStation()){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("ok");

  // join to AP(SSID,PASSWORD)
  Serial.print("join to AP.");
  while(!wifi.joinAP(SSID, PASSWORD)){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("ok");

  // set single mode
  Serial.print("set single mode.");
  while(!wifi.disableMUX()){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("ok");

  // TCPサーバへ接続
  Serial.print("connect to TCP server.");
  while(!wifi.createTCP(HOST_ADDR, HOST_PORT)){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("ok");
  Serial.println();

  // Webサーバへリクエストを送信
  char sendBuffer[128];
  sprintf(sendBuffer, "GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",HOST_FILE,HOST_ADDR);
  wifi.send(sendBuffer, strlen(sendBuffer));

  // Webサーバよりレスポンスを受信
  char recvBuffer[512];
  int len;
  if((len=wifi.recv(recvBuffer,sizeof(recvBuffer)-1,10000))>0){
    recvBuffer[len]='\0';
    Serial.println(recvBuffer);
  }

  // TCP接続を解除(HTTPなのでサーバ側から既に切られているはずだけど念のため)
  Serial.print("TCP disconnect.");
  wifi.releaseTCP();
  Serial.println("ok");

  // 無線LAN切断
  Serial.print("Leave from AP.");
  Serial.println("ok");
  wifi.leaveAP();
}

void loop(void)
{

}

長いですが、実は78行目以外の Serial.print() や Serial.println() はプログラム自体の動作確認用なので、機能には関係ありません。ESP8266 関係の関数は実行に少々時間がかかるものが多く、実験に用いた環境だとAPに接続完了するまでに数十秒かかることがあるのですが、どこまで進んだか判らないままずっと待たされるのが嫌な性分なので…。不要なら省略して下さい。

コードの解説

include
C++
#include "ESP8266.h"
#include <SoftwareSerial.h>

ESP8266.h には、ESP-WROOM-02U をはじめとする ESP8266搭載のWifiモジュールを利用するための関数群 が定義されています。先にインストールした ITEADLIB_Arduino_WeeESP8266-master に含まれています。

SoftwareSerial は、Arduino と ESP8266(ESP-WROOM-02U)が通信するために必要です。

定数定義
C++
#define SSID ”HOMENETWORK"
#define PASSWORD "wifipassword"
#define HOST_ADDR "192.168.0.15"
#define HOST_PORT 80
#define HOST_FILE "test.html"

アクセスポイントおよびWebサーバについての情報です。お使いの環境に合わせて書き換えて下さい。

定数名意味値の例
SSID接続するアクセスポイントのSSID“HOMENETWORK”
PASSWORD接続するアクセスポイントのパスワード“wifipassword”
HOST_ADDR接続するWebサーバのIPアドレスまたはサーバ名“192.168.0.15”
または
“workshop.aaa-plaza.net”
HOST_PORTWebサーバのポート番号80
HOST_FILE取得するファイルのファイル名“test.html”
ESP8266オブジェクトの生成
C++
SoftwareSerial wifiSerial(2, 3);    // RxD=2, TxD=3
ESP8266 wifi(wifiSerial);

ESP8266 を制御するためには、ESP8266クラス のオブジェクトを使用します。ESP8266クラスは内部的に SoftwareSerial を使用するため、先に SoftwareSerialクラスのオブジェクト を生成し、次にwifiSerialを引数としてESP8266クラスのオブジェクト を生成する、という手順になります。

SoftwareSerialクラスのオブジェクトの生成では、引数として『RxDとして利用する端子の番号』と『TxDとして利用する端子の番号』を与えます。この例ではRxD=2、TxD=3として、SoftwareSerialクラスのオブジェクト wifiSerial を生成しています。

次に、ESP8266クラスのオブジェクトの生成では、引数としてArduino と ESP8266 の通信に使用する SoftwareSerial のクラスを与えます。この例では wifiSerial を与えて ESP8266クラスのオブジェクト wifi を生成しています。

シリアル通信速度の設定
C++
void setup(void)
{
  Serial.begin(9600);
  wifiSerial.begin(9600);

setup() 関数内の最初で、PC ~ Arduino 間および Arduino ~ ESP-WROOM-02U 間でのシリアル通信速度を設定しています。

Serial.begin() が PC ~ Arduino 間、
wifiSerial.begin() が Arduino ~ ESP-WROOM-02 間

です。この例ではともに 9600bps です。

ESP8266 の再起動

ここからが ESP8266 での通信のためのスクリプトです。

C++
  Serial.print("wifi restart.");
  while(!wifi.restart()){
    Serial.print(".");
    delay(1000);
  }
  Serial.println("ok");

まず、ESP8266 を再起動します。再起動には ESP8266::restart() 関数を使用します。書式は以下の通りです。

bool ESP8266::resart(void)
引数

なし

返却値
意味
true再起動に成功
false再起動に失敗
解説

このメソッドは ESP8266 を再起動します。処理に3秒程度かかります。

※電源起動直後でも『再起動』です。このrestart()の他にstart()メソッドがあるわけではありません。

内部的には、『AT+RST』コマンドを送信して、一定時間内に『OK』を含むリプライが返ってくるかを確認しているようです。

この例では、falseが返って来た場合は 1000 ms=1秒待ってからリトライするようにしています。また Serial.print() や Serial.println() で動作確認メッセージを表示するようにしていますが、これらが不要の場合は、最小限

C++
wifi.restart();

だけでも動きます。

これ以降、『確認メッセージ表示』および『失敗したら1秒待ってリトライ』はすべて同じなので、ESP8266.h で定義されているメソッドのみ解説します。

ESP8266の動作確認

ESP8266 が動作しているかを確認するには、ESP8266::kick() 関数を使用します。書式は以下の通りです。

bool ESP8266::kick(void)
引数

なし

返却値
意味
true動作している
false動作していない
解説

内部的には、『AT』コマンドを送信して、一定時間内に『OK』を含むリプライが返ってくるかを確認しているようです。

ステーションモードに設定

今回の実験では、ESP8266 をステーションモード(ESP8266 を既存のアクセスポイントに接続するクライアント側として使用するモード)に設定します。

この設定には ESP8266::setOprToStation() 関数を使用します。書式は以下の通りです。

bool ESP8266::setOprToStation(void)
引数

なし

返却値
意味
trueステーションモードへの切り替えに成功
falseステーションモードへの切り替えに失敗
解説

この関数は、内部的には『AT+CWMODE=1』を送っています。

アクセスポイントへ接続

ESP8266をアクセスポイントに接続します。

接続には ESP8266::joinAP() 関数を使用します。書式は以下の通りです。

bool ESP8266::joinAP(String ssid, String password)
引数
名前意味
ssidString接続するアクセスポイントのSSID
passwordString接続するアクセスポイントのパスワード
返却値
意味
true接続に成功
false接続に失敗
解説

この処理には、数秒~数十秒の時間がかかります。

シングル(単一接続)モードに設定

ここから先は TCP/UDP に関する設定です。

まず、今回の実験では ESP8266 を TCPクライアントとして動作させるので、TCP/UDP において一度に単一の相手とだけ接続する『シングルモード』に設定をします。

この設定には ESP8266::disableMUX() 関数を使用します。書式は以下の通りです。

bool ESP8266::disableMUX(void)
引数

なし

返却値
意味
tureシングルモードへの切り替えに成功
falseシングルモードへの切り替えに失敗
TCPコネクションの作成(クライアントとして)

指定されたサーバに対して、ESP8266 を TCPクライアントとしてシングルモードでコネクションを作成(接続)します。

接続には ESP8266::createTCP() 関数を使用します。書式は以下の通りです。

bool ESP8266::createTCP(String addr, uint32_t port)
引数
名前意味
addrString接続先サーバのIPアドレス、またはホスト名
portuint32_t接続先のポート番号

※addrにはインターネット上のサーバのFQDNを与えることもできます

返却値
意味
trueTCP接続に成功
falseTCP接続に失敗
解説

今回のスケッチでは、引数として与えるアドレスやポート番号は、スケッチ冒頭部でそれぞれ #define HOST_ADDR および #define HOST_PORT で定数として定義しています。

Webサーバに接続するのでportは80としていますが、実験環境など異なるポート番号でWebサーバを運用している場合にはスケッチ冒頭部の #define HOST_PORT の値を書き換えて下さい。

サーバにデータを送信する

TCP接続に成功したら、Webサーバに対してHTTPリクエストメッセージを送信します。

送信するデータ

今回の実験では、Webサーバに対してHTTPリクエストメッセージ(GETメソッド)を送信します。具体的には以下のようなものです。

Plaintext
GET サーバ上のファイル名 HTTP/1.0
Host: ホスト名

1行目の『GET~』は、『サーバ上の、指定したファイルの内容を送れ』というものです。HTTPリクエストでは、最初に『GET~』のように、Webサーバに対して『○○してくれ』という内容の行が来ます。これを『リクエスト行』といいます。リクエスト行は先頭に必ず1行だけ存在します。

2行目以降には、0行以上の『ヘッダ』が続きます。これはリクエストにあたって付加的な情報をサーバに伝えるためのものです。今回の実験では、『Host~』という行を追加しています。これはサーバ側が同じIPアドレスで複数のホストを運用していた場合に、どのホストに対するリクエストかを区別するためのものです。

ヘッダの後には空行が必要です。Webサーバは、クライアントから改行文字だけの行が送られてくるまで、まだヘッダの続きがあるものとして受信待ちをし、レスポンスを返してくれません。上記送信文字列例でも3行目に改行文字だけの行があることに注意して下さい。

※詳しくは、HTTPについて解説されている書籍やサイトを参照して下さい

サーバ上のファイル名は、スケッチ冒頭の #define HOST_FILE で定義しています。

この送信文字列を組み立てているのが、

C++
char sendBuffer[128];
sprintf(sendBuffer, "GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",HOST_FILE,HOST_ADDR);  

の部分です。char型配列変数sendBuffer内に送信文字列を組み立てています。最後の”\r\n\r\n”で空行を追加しています(2行目の行末”\r\n”+3行目の空行”\r\n\r\n”)。

送信コマンド

メッセージの送信には ESP8266::send() 関数を使用します。書式は以下の通りです。

bool ESP8266::send(const uint8_t *buffer, uint32_t len)
引数
名前意味
bufferconst uint8_t送信するデータバッファ(uint8_tのデータ列)へのポインタ
lenuint32_t送信するデータの長さ
返却値
意味
true送信に成功
false送信に失敗
解説

このプログラムでは、

C++
wifi.send(sendBuffer, strlen(sendBuffer));

としてsendBufferのデータを送信しています。

サーバからのデータを受信する

クライアントからサーバへHTTPリクエストメッセージを送信すると、サーバはクライアントにHTTPレスポンスメッセージを返信します。HTTPレスポンスメッセージもリクエストメッセージと同様の構造があるのですが、今回は受信したメッセージの分析はしないので説明は省略します。

サーバからのメッセージを受信するには、ESP8266::recv() 関数を使用します。書式は以下の通りです。

uint32_t ESP8266::recv(uint8_t *buffer, uint32_t buffer_size  [ , uint32_t timeout = 1000])
引数
名前意味
bufferuint8_t*受信バッファへのポインタ
sizeuint32_t受信バッファのサイズ
timeoutuint32_tタイムアウトまでの時間をmsで指定。省略すると 1000 ms となる。
返却値
意味
uint32_t受信したデータの長さ(バイト数)
解説

このプログラムでは、

C++
  char recvBuffer[512];
  int len;
  if((len=wifi.recv(recvBuffer,sizeof(recvBuffer)-1,10000))>0){
    recvBuffer[len]='\0';
    Serial.println(recvBuffer);
  }

としています。まず512バイトのchar型配列 recvBuffer を宣言して受信のための領域を確保しています。

recv() メソッドはメッセージを受信するか、またはタイムアウトするまで待ちますが、タイムアウトだった場合は受信したメッセージのバイト数が0になるのでif文で分岐し、受信したメッセージがあった場合のみその処理を行っています。

recv() メソッドで受信したデータが文字列だったとしても、文字列終端を表す ‘\0’(ヌル文字)が付かないため、

C++
recvBuffer[len]='\0';

として終端に’\0’を追加しています。

このプログラムでは、受信したHTTPレスポンスをそのまま(ヘッダもデータ本体もすべて) Serial.println() でPCに転送しています。

TCPコネクションの解放

通信が終了したら、TCPコネクションを解放(切断)します。

※HTTPの場合、リクエスト/レスポンスでデータが一往復したらサーバ側から切断されてしまうので必ずしもこの処理は必要ではないかもしれません。

TCPコネクションを解放するには、 ESP8266::releaseTCP() 関数を使用します。書式は以下の通りです。

bool ESP8266::releaseTCP(void)
引数

なし

返却値
意味
true解放に成功
false解放に失敗
アクセスポイントから切断

最後に、アクセスポイントから切断します。

アクセスポイントからの切断には、ESP8266::leaveAP() 関数を使用します。書式は以下の通りです。

bool ESP8266::leaveAP(void)
引数

なし

返却値
意味
true切断に成功
false切断に失敗

本プログラムは setup() の中の処理だけで完結しています。loop() 内は空です。

実行

Arduino と PC をUSBケーブルで接続し、Arduino IDE でシリアルモニタを開いた状態でスケッチを実行sしてみましょう。

このような結果が得られれば成功です(レスポンスヘッダの内容は環境によって異なります)。

次の記事では Arduino+ESP8266 をTCPサーバとして動作させます。

まとめ

  • ATコマンドでTCP接続できる
  • ESP8266.h のようなライブラリを利用すると比較的巻単位WiFi接続・TCP接続ができる

コメント

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