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が確認出来ました。今回は、ここまでとします。また応用的な内容等が書けたら公開したいと思います。
