Webクライアントから送信されたデータをATP3011にしゃべらせる(I2C通信)

[Arduinoで遊ぶ]

作成 : 2013/01/27

"サーバの実験室"の検索


Webクライアントから送信されたデータをATP3011にしゃべらせる(I2C通信)

Webクライアントから送信されたテキストをLCDに表示するプッシュスイッチを押すとATP3011が決められた言葉をしゃべる(I2C通信)を組み合わせて、Webクライアントから送信したデータをATP3011にしゃべらせます。

ArduinoとEthernetシールドとATP3011 I2C通信 接続

/*********************

ArduinoをWEBサーバとして動作させ、フォームからPOSTで送信されたテキストをATP3011に送信して発声させる。

  Arduino Uno
    http://www.arduino.cc/en/Main/arduinoBoardUno
  Arduino Ethernet Shield
    http://arduino.cc/en/Main/ArduinoEthernetShield
  ATP3011
    http://www.a-quest.com/products/aquestalkpicolsi.html

**********************/

#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>

// ATP3011F4のI2Cアドレス(デフォルト値のまま)
#define ADDR 0x2E

// POSTで読み取るデータサイズを定義
#define BUFSIZE 256
// ATP3011F4に送れるのは127byte+'\r'
#define MAX_BYTE 127
// WIREで1回に送れるのは32byte
#define MAX_WIRE_BYTE 32

// Arduino Ethernet ShieldのMACアドレス
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x99, 0x50 };
// Arduinoに設定するTCP/IP情報
byte ip[] = { 192, 168, 1, 200 };
byte gateway[] = { 192, 168, 1, 1 };
byte subnet[] = { 255, 255, 255, 0 };
EthernetServer server(80);





// 最初に1回だけ実行
void setup() {
  // シリアル通信の転送レートを設定
  Serial.begin(9600);

  // I2Cを初期化
  Wire.begin();

  // リスナを開始
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
}



// 繰り返し実行
void loop() {

  // ATP3011からのレスポンスを格納する変数
  char respStr[10];

  // クライアントオブジェクトを取得
  EthernetClient client = server.available();

  // クライアントが存在するとき、clientはtrue
  if (client) {
    Serial.println("new client");

    // クライアントからPOSTメソッドで呼ばれたらtrue
    boolean isPOST = false;
    // リクエストヘッダに改行(\r)以外が含まれたらfalse
    boolean currentLineIsBlank = true;
    // リクエストヘッダの終了(\r\n\r\n)を検出したらtrue
    boolean reqHeaderIsEnd = false;
    // リクエストデータを格納
    // 最大サイズはBUFSIZEで規定
    char reqParam[BUFSIZE];
    // リクエストデータを読むときに使用するインデックス
    long idx = 0;
    // POSTで送信されたテキストを格納
    char* wordValue;
    // ATP3011に送信するデコード済みのテキストを格納
    char sendStr[MAX_BYTE] = "";

    // クライアントが存在する間、ぐるぐるまわる
    while (client.connected()) {
      // クライアントから受信するデータが存在するときはtrueを返す
      if (client.available()) {
        char c = client.read();
        Serial.write(c);

        // (\r)\n(\r)\nのパターンがあらわれた場合、リクエストヘッダの終わりと見なしてreqHeaderIsEndをtrueにする
        if (c == '\n' && currentLineIsBlank) {
          reqHeaderIsEnd = true;
        }
        if (c == '\n') {
          currentLineIsBlank = true;

          // 行末にきたらidxをリセット
          idx = 0;
          // 行頭が"POST "ではじまっていたら、isPOSTをtrueにする
          if (strncmp(reqParam, "POST ", 5) == 0) {
            isPOST = true;
          }
        } 
        else if (c != '\r') {
          currentLineIsBlank = false;
          // リクエストデータ1行分をreqParamに格納する
          // 1行のデータサイズがPOSTデータの規定最大サイズBUFSIZE-1(-1は'\0'付加を考慮した分)を超える場合は、データを破棄
          if (idx < BUFSIZE - 1) {
            reqParam[idx] = c;
            idx++;
          }
        }
      }
      // クライアントから受信するデータがなくなったらループを抜ける
      else {
        break;
      }
    }
    Serial.println();

    // POSTで呼び出された場合のデータ解析とデコード処理
    if (isPOST) {
      // POSTデータを'&'で分割したパラメタを格納
      char* splitReqParam;
      // POSTデータをstrtok_rでパラメタに分割するときに使用
      char* reqParamPtr;
      // パラメタを'='で分割した左辺(パラメタ名)を格納
      char* paramName;
      // パラメタを'='で分割した右辺(パラメタ値)を格納
      char* paramValue;
      // パラメタをstrtok_rで名前と値に分割するときに使用
      char* nameValuePtr;

      // reqParamに格納した文字列の後ろにNULLを追加して、文字列の終わりを示す
      reqParam[idx] = '\0';

      // POSTデータを'&'で分割してsplitReqParamに格納
      for (splitReqParam = strtok_r(reqParam, "&", &reqParamPtr); splitReqParam != NULL; splitReqParam = strtok_r(NULL, "&", &reqParamPtr)) {
        // パラメタsplitReqParamを'='で分割して、名前をparamName、値をparamValueに格納
        // ネストしたいので、strtokの代わりにstrtok_rを使用
        char* paramName = strtok_r(splitReqParam, "=", &nameValuePtr);
        char* paramValue = strtok_r(NULL, "=", &nameValuePtr);

        // パラメタ名が"word"であれば、テキストとみなしてwordValueに値を格納
        if (strcmp(paramName, "word") == 0) {
          wordValue = paramValue;
        }
      }

      // POSTで送信されたデータはパーセントエンコーディングされているため、デコードする関数を呼ぶ
      // デコードされた文字列はsendStrに格納
//      sendStr = strdup(wordValue);
      urlDecode(wordValue, sendStr);
    }

    // POSTで呼び出された場合、atp3011_speak関数を呼んでデータを送信し、レスポンスをrespStrに格納
    if (isPOST) {
      atp3011_speak(sendStr, respStr);
    }

    //レスポンスの末尾は'>'になるので、"&gt;"に変換
    if (respStr[strlen(respStr) -1] == '>') {
      int respStrLen = strlen(respStr);
      respStr[respStrLen - 1] = '&';
      respStr[respStrLen] = 'g';
      respStr[respStrLen + 1] = 't';
      respStr[respStrLen + 2] = ';';
      respStr[respStrLen + 3] = '\0';
    }

    // クライアントにデータを返す
    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: text/html");
    client.println("Connnection: close");
    client.println();
    client.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
    client.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
    client.println("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"ja\" lang=\"ja\">");
    client.println("<head>");
    client.println("<meta http-equiv=\"content-type\" content=\"text/html\" />");
    client.println("<title>");
    client.println("Arduino Web Server");
    client.println("</title>");
    client.println("</head>");
    client.println("<body>");
    client.println("<form action=\"http://192.168.1.200/\" method=\"post\">");
    client.println("<p>");
    client.print("<input type=\"text\" name=\"word\" />");
    client.println("</p>");
    // ATP3011に送信したデータをクライアントに表示
    client.println("<p>");
    client.print("Send message:"); client.println(sendStr);
    client.println("</p>");
    // ATP3011から受信したレスポンスをクライアントに表示
    client.println("<p>");
    client.print("Responce:"); client.println(respStr);
    client.println("</p>");
    client.println("<p><input type=\"submit\" value=\"送信\" /></p>");

    client.println("</body>");
    client.println("</html>");

    // クライアントをサーバから切断
    delay(1);
    client.stop();
    Serial.println("client disconnected");
  }
}



/*
 42 : '*'
 62 : '>'
 69 : 'E'
*/



// POSTで読み取ったデータはパーセントエンコーディングされているため、デコードを実施
// char* str : 変換する文字列を格納、 char* dstStr : 変換後の文字列を格納
boolean urlDecode(char* str, char* dstStr) {
  if (str != NULL) {
    long dstIdx = 0;

    for(long idx = 0; idx < strlen(str); idx++) {
      if (str[idx] == '%') {
        char tmpHex[] = { str[idx+1], str[idx+2] };
        dstStr[dstIdx] = strtol(tmpHex, NULL, 16);
        idx += 2;
      }
      else if (str[idx] == '+') {
        dstStr[dstIdx] = ' ';
      }
      else {
        dstStr[dstIdx] = str[idx];
      }
      dstIdx++;
    }
    dstStr[dstIdx] = '\0';
  }

  return true;
}



// ATP3011にデータsendStrを送信して発声させ、レスポンスをrespStrに格納する
// 関数の戻り値は、レスポンスが'E'で始まるエラー系ならfalse、それ以外はtrueを返す
boolean atp3011_speak(char* sendStr, char* respStr) {

  // ATP3011にデータを送信する
  // ATP3011には127byteまで送信できるが、Wireでは32byteしか送信できないため、
  // 長いデータは32byteに分割して送る
  for (int w_loop = 0; w_loop < 4;w_loop++) {
    Wire.beginTransmission(ADDR);
    for (int s_idx = 0;s_idx < MAX_WIRE_BYTE;s_idx++) {
      // データの終わり'\0'がきたらループを抜ける
      if(sendStr[w_loop * MAX_WIRE_BYTE + s_idx] == '\0') {
        break;
      // データの終わり'\0'以外なら送信する(Wire.endTransmissionでまとめて送信される)
      } else {
        Wire.write(sendStr[w_loop * MAX_WIRE_BYTE + s_idx]);
      }
    }
    Wire.endTransmission();
  }
  // 最後に'\r'を送信して、発声を開始させる
  Wire.beginTransmission(ADDR);
  Wire.write('\r');
  Wire.endTransmission();

  Serial.print("ATP3011 Write message:"); Serial.println(sendStr);

  // ATP3011からのレスポンスを受け取る
  // BUSY('*')の間はループする
  do {
    // ATP3011へのポーリングは10ms間隔をあけること
    delay(10);

    // ATP3011から8byte読み取る
    // ATP3011からの最大レスポンス長は"Exxx>"で5byteになるはず
    Wire.requestFrom(ADDR,8);

   int r_idx=0;
    // ATP3011から読み取るデータがある間、ぐるぐる回る
    while (Wire.available()) {
      // ATP3011から1byte読み取る
      char c = Wire.read();
      // 読み取ったデータがBUSY'*'または'>'だったら、データをrespStrに格納してループを抜ける
      if (c == '*' ||  c == '>') {
        respStr[r_idx] = c;
        break;
      // 読み取ったデータがそれ以外だったら、データをrespStrに格納する
      } else {
        respStr[r_idx] = c;
        r_idx++;
      }
    }
    // respStrの最後に'\0'を付加して文字列の終わりを示す
    respStr[r_idx+1] = '\0';
  // レスポンスがBUSY"*"の間、ぐるぐる回る
  } while (!strcmp(respStr,"*"));

  Serial.print("ATP3011 Responce:"); Serial.println(respStr);

  // レスポンスがエラー"Exxx>"の場合はfalseを返す
  // レスポンスがエラー">"や"Vxx>の場合はtrueを返す
  if (respStr[0] == 'E') {
    return false;
  } else {
    return true;
  }
}

[Arduinoで遊ぶ]