株式会社インデペンデンスシステムズ横浜

システム開発エンジニアの西田五郎が運営しております。Raspberry Pi や Arduino その他新規開発案件のご依頼をお待ちしております。

Arduino 通信制御

Arduino CC3000 WiFi シールドでHTTP POST

投稿日:2015年8月24日 更新日:

Arduino CC3000 WiFi シールドでHTTP POST通信を試してみます。以下はArduino UNOに接続した状態です。

wifishld1

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を起動します。以下のような画面になります。
0001

まずは、URIを入力して、MethodをPOSTに変更します。
0005

そうすると、Requestの入力が可能になるのでここでは適当にパラメータを入力します。以下では、prm1=abc&prm2=xyz&prm3=999と入力しました。
0006

Headersのタブを選択してヘッダを追加します。AddボタンでContent-Typeを選択して、application/x-www-form-urlencodedを選択(初期値)します。
0007

これで、Submitを実行します。そうすると以下のように結果が表示されました。確かにRequetで設定したパラメータがサーバサイドで取得出来ました。Bodyの部分がサーバサイドのPHPでの表示結果です。
0009

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

AdSense

AdSense

-Arduino, 通信制御

執筆者:

関連記事

LeafonyでRCカーを作ってみました(その2)RCカー編

前回の続きです。LeafonyでRCカーを作ってコントロールしてみるというテーマで書いています。前回まででLeafonyでのBluetooth Low Energy(ここからはBLEと表記します。)と …

ESP-WROOM-02でもセンサネットワーク稼働試験

ESP-WROOM-02でもセンサネットワークを稼働してみます。ESP-WROOM-02でもというのは今までRaspberry Piで稼働してきたのでESP-WROOM-02でも稼働してみようというこ …

温度センサADT7410(その1)i2C通信とは

温度センサのADT7410を使ってみます。ADT7410はアナログ・デバイセズ社の製品ですが、実際には以下の秋月さんのモジュールを使います。 ADT7410使用 高精度・高分解能 I2C・16Bit …

感染症対策のためのIoT技術(その6)M5StickC PIR Hat(AS312搭載)人感センサー

感染症対策のためのIoT技術について書いています。今回は人感センサーを使ってみました。使ったのは M5StickC に直接接続して使うタイプで比較的簡単に使うことが出来ます。スイッチサイエンスさんの販 …

温度センサADT7410(その2)Arduinoでの利用

温度センサADT7410の2回目です。前回はI2C通信の簡単な概要とADT7410モジュールをRaspbery Piに接続してアドレスの確認まで行いました。今回はArduinoでADT7410を使って …