Arduino CC3000 WiFi シールドでHTTP POST通信を試してみます。以下はArduino UNOに接続した状態です。
Arduino CC3000 WiFi シールドの概要、導入については以下のページで書きました。
Arduino CC3000 WiFi シールドを使ってみた
上記の記事では、製品概要、ライブラリの準備から提供されているサンプルプログラムでHTTP GETでのWEBページ取得を確認しました。
今回は続きになる内容ですが、このArduino CC3000 WiFi シールドでHTTP POSTを使ってArduinoからのWebへのデータ送信をテスト、確認します。
HTTP POST通信の確認
Arduinoでの通信の前にまずは簡単にですが、HTTP POST通信をFirefoxアドオンで確認します。ここでは、以下のアドオンを使いました。同様のツールは他にもいろいろとあると思います。またもちろん、htmlのフォームを作成してPOSTすることも出来ます。
HTTP Resource Test
先にサーバ側の準備をします。ここでは以下のPHPスクリプトを用意しました。単純にPOSTされたデータをパラメータ単位で表示するだけです。
<?php foreach($_POST as $key => $val){ print('{' . $key . '} ' . ' => ' . '{' . $val . '} '); print("\n"); } ?>
このサーバサイドのスクリプトにFirefoxアドインからPOSTします。アドインがインストール出来ているとして、firefoxのツールメニューから、HTTP Resource Testを起動します。以下のような画面になります。
まずは、URIを入力して、MethodをPOSTに変更します。
そうすると、Requestの入力が可能になるのでここでは適当にパラメータを入力します。以下では、prm1=abc&prm2=xyz&prm3=999と入力しました。
Headersのタブを選択してヘッダを追加します。AddボタンでContent-Typeを選択して、application/x-www-form-urlencodedを選択(初期値)します。
これで、Submitを実行します。そうすると以下のように結果が表示されました。確かにRequetで設定したパラメータがサーバサイドで取得出来ました。Bodyの部分がサーバサイドのPHPでの表示結果です。
ArduinoからのHTTP POST
前項でFirefoxのアドオンからPOSTしたことを次は本題のArduinoから実行します。ここではサンプルスケッチのWebClientを使って、GETの部分をPOSTに変更しました。以下がそのスケッチです。
/**************************************************************** WebClient.ino CC3000 WebClient Test Shawn Hymel @ SparkFun Electronics March 1, 2014 https://github.com/sparkfun/SFE_CC3000_Library Manually connects to a WiFi network and performs an HTTP GET request on a web page. Prints the contents of the page to the serial console. The security mode is defined by one of the following: WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA, WLAN_SEC_WPA2 Hardware Connections: Uno Pin CC3000 Board Function +5V VCC or +5V 5V GND GND GND 2 INT Interrupt 7 EN WiFi Enable 10 CS SPI Chip Select 11 MOSI SPI MOSI 12 MISO SPI MISO 13 SCK SPI Clock Resources: Include SPI.h, SFE_CC3000.h, and SFE_CC3000_Client.h Development environment specifics: Written in Arduino 1.0.5 Tested with Arduino UNO R3 This code is beerware; if you see me (or any other SparkFun employee) at the local, and you've found our code helpful, please buy us a round! Distributed as-is; no warranty is given. 2015/12/10 Goro Nishida @ Independence Systems Yokohama For HTTP POST Test. ****************************************************************/ #include <SPI.h> #include <SFE_CC3000.h> #include <SFE_CC3000_Client.h> // Pins #define CC3000_INT 2 // Needs to be an interrupt pin (D2/D3) #define CC3000_EN 7 // Can be any digital pin #define CC3000_CS 10 // Preferred is pin 10 on Uno // Connection info data lengths #define IP_ADDR_LEN 4 // Length of IP address in bytes // Constants char ap_ssid[] = "SSID"; // SSID of network char ap_password[] = "PASSWORD"; // Password of network unsigned int ap_security = WLAN_SEC_WPA2; // Security of network unsigned int timeout = 30000; // Milliseconds char server[] = "yourdomain.com"; // Remote host site // Global Variables SFE_CC3000 wifi = SFE_CC3000(CC3000_INT, CC3000_EN, CC3000_CS); SFE_CC3000_Client client = SFE_CC3000_Client(wifi); void setup() { ConnectionInfo connection_info; int i; // Initialize Serial port Serial.begin(115200); Serial.println(); Serial.println("---------------------------"); Serial.println("SparkFun CC3000 - WebClient"); Serial.println("---------------------------"); // Initialize CC3000 (configure SPI communications) if ( wifi.init() ) { Serial.println("CC3000 initialization complete"); } else { Serial.println("Something went wrong during CC3000 init!"); } // Connect using DHCP Serial.print("Connecting to SSID: "); Serial.println(ap_ssid); if (!wifi.connect(ap_ssid, ap_security, ap_password, timeout)) { Serial.println("Error: Could not connect to AP"); } // Gather connection details and print IP address if ( !wifi.getConnectionInfo(connection_info) ) { Serial.println("Error: Could not obtain connection details"); } else { Serial.print("IP Address: "); for (i = 0; i < IP_ADDR_LEN; i++) { Serial.print(connection_info.ip_address[i]); if ( i < IP_ADDR_LEN - 1 ) { Serial.print("."); } } Serial.println(); } // Make a TCP connection to remote host Serial.print("Performing HTTP POST of: "); Serial.println(server); if ( !client.connect(server, 80) ) { Serial.println("Error: Could not make a TCP connection"); } // Make a HTTP POST request client.println("POST /postdisplay.php HTTP/1.1"); client.print("Host: "); client.println(server); // Content-Type client.println("Content-Type: application/x-www-form-urlencoded"); //Body part (url encoded variables) String body = String("prm1=abc&prm2=xyz&prm3=efg"); //数値から文字列への変換のテスト //HTTP POSTには関係ないです int num = 987; String numstr = String(num); body = body + numstr; // Content-Length client.print("Content-Length: "); //no need CR LF client.println(body.length()); // Close field client.println("Connection: close"); client.println(); //body client.println(body); Serial.println("setup finished."); } void loop() { // If there are incoming bytes, print them if ( client.available() ) { char c = client.read(); Serial.print(c); } // If the server has disconnected, stop the client and wifi if ( !client.connected() ) { //Serial.println(); // Close socket if ( !client.close() ) { Serial.println("Error: Could not close socket"); } // Disconnect WiFi if ( !wifi.disconnect() ) { Serial.println("Error: Could not disconnect from network"); } // Do nothing Serial.println("Finished WebClient test"); while (true) { delay(1000); } } }
スケッチの構成は元々のWebClientのままです。今回変更したのは、setup()の // Make a HTTP POST request // POSTの実行 のコメントの部分からです。前項で確認した内容と同様に、ここではArduinoのライブラリでPOSTしています。ヘッダーの送信、Content-Lengthの送信、Bodyの送信と続けています。
コメントにも書きましたが、POSTとは関係ないですが、数値を文字列に変換してBody部分に追加する処理も書いてみました。
これをArduinoから実行すると、シリアルモニタに以下のように表示されました。
--------------------------- SparkFun CC3000 - WebClient --------------------------- CC3000 initialization complete Connecting to SSID: SHL23_AP56 IP Address: 192.168.43.73 Performing HTTP POST of: independence-sys.net setup finished. HTTP/1.1 200 OK Date: Thu, 10 Dec 2015 12:13:00 GMT Server: Apache Vary: User-Agent,Accept-Encoding Connection: close Transfer-Encoding: chunked Content-Type: text/html 39 {prm1} => {abc} {prm2} => {xyz} {prm3} => {efg987} 0 Finished WebClient test
ここで気になったのが39と0という数字です。これも正常なレスポンスです。ポイントは、Transfer-Encoding: chunked です。この Transfer-Encoding: chunked の場合は、メッセージボディをチャンク(Chunk、大きいかたまりといった意味) 単位に分けて、チャンク毎のサイズとともにレスポンスを返します。このレスポンスの場合の39がチャンクのサイズです。0はチャンクは終了という意味です。
静的なコンテンツの場合は、Content-Length ヘッダを生成しやすいと思いますが、動的コンテンツの場合は、始めに、Content-Length を生成するのは難しいです。その場合にこの Transfer-Encoding: chunked が使えます。Transfer-Encoding: chunked を使う場合は、 Content-Length は指定しないという仕様です。実際にここでのレスポンスには Content-Length は存在しません。
このようなHTTPのレスポンスをArduinoで解析する場合、あまり複雑なレスポンスではプログラムも複雑になり、解析結果のアウトプット(表示)も難しいと思います。Arduinoではプロトタイプと考えると、サーバ側の処理へ繰り返しPOSTしていき、必要な処理をサーバ側で行うと流れになるかと思います。
URLエンコード
ここでは、”Content-Type: application/x-www-form-urlencoded” を指定しました。つまり必要な場合はURLエンコードを行ってPOSTをする必要があります。そこで以下のパラメータを試してみました。
スペースと!をURLエンコードしない場合
prm1=Hello World!
PHPからのレスポンス
{prm1} => {Hello World!}
スペースと!をURLエンコードした場合
prm1=Hello+Arduino%20World%21
PHPからのレスポンス
{prm1} => {Hello Arduino World!}
まあいずれの場合でも今回のPHPの環境では何とか取得出来るようです。それではArduinoのスケッチで、URLエンコードを行うとすると例えば以下のようにエンコード出来ます。
これをString型での関数にすると以下のようになります。
String URLEncode(const char* msg) { const char *hex = "0123456789abcdef"; String encodedMsg = ""; while (*msg!='\0'){ if( ('a' <= *msg && *msg <= 'z') || ('A' <= *msg && *msg <= 'Z') || ('0' <= *msg && *msg <= '9') ) { encodedMsg += *msg; } else { encodedMsg += '%'; encodedMsg += hex[*msg >> 4]; encodedMsg += hex[*msg & 15]; } msg++; } return encodedMsg; }
これをスケッチに組み込んで以下のように使います。
String body = String("prm1=") + URLEncode("Hello Arduino World!!");
これで、以下のレスポンスが取得出来ました。
{prm1} => {Hello Arduino World!!}
以上で、ArduinoからのPOSTが確認出来ました。今回は、ここまでとします。また応用的な内容等が書けたら公開したいと思います。