Webクライアントから送信されたテキストをLCDに表示する

[Arduinoで遊ぶ]

作成 : 2013/01/03

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


Webクライアントから送信されたテキストをLCDに表示する

Arduino UnoとEthernet Shieldを組み合わせてWEBサーバにします。 クライアントからテキストとLCDバックライト色をPOSTメソッドで受け取って、LCDに表示します。 LCDに表示可能な文字数を超えるテキストを受け取った場合は、LCDの表示をスクロールします。

リクエストされたURIの解析は実施していません。 リクエストメソッドがPOSTかそれ以外かのチェックだけしています。 POSTの直後に"GET /favicon.ico"を投げるクライアントへに対応するためです。

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

ArduinoをWEBサーバとして動作させ、フォームからPOSTで送信されたテキストをLCDに表示し、バックライト色を変更する。
テキストがLCDで表示可能な文字数を超える場合は、スクロールして表示する。

  Arduino Uno
    http://www.arduino.cc/en/Main/arduinoBoardUno
  Arduino Ethernet Shield
    http://arduino.cc/en/Main/ArduinoEthernetShield
  RGB LCD Shield Kit w/ 16x2 Character Display
    http://www.adafruit.com/products/714
**********************/

#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>
// Adafruit RGB LCD Shield Kit用ライブラリ
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>

// 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);

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();
// LCDのバックライト色を定義
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#define WHITE 0x7

// POSTで読み取るデータサイズを定義
#define BUFSIZE 256

// LCDに表示するデコード済みのテキストを格納
char* decodeWordValue;
// LCDの表示をスクロールするためのインデックス
long dispMsgIdx = 0;

// LCDに表示可能な文字数を定義
#define LCDWIDTH 16

// 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;
}

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

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

  // LCDを初期化
  lcd.begin(LCDWIDTH, 2);

  // LCDの表示を初期化
  lcd.setCursor(0, 0);
  lcd.print(Ethernet.localIP());
  lcd.setCursor(0, 1);
  lcd.print("server is ready");
  lcd.setBacklight(3);
}

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

  // クライアントオブジェクトを取得
  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;
    // POSTで送信されたLCDバックライト色を格納
    // デフォルトは7(白)
    int blValue = 7;

    // クライアントが存在する間、ぐるぐるまわる
    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;
      }
    }

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

      // reqParamに格納した文字列の後ろにNULLを追加して、文字列の終わりを示す
      reqParam[idx] = '\0';
      // dispMsgIdxに0を設定し、LCD表示のスクロールを初期化する
      dispMsgIdx = 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);

        // パラメタ名が"bl"であれば、LCDバックライト色とみなしてblValueに値を格納
        if (strcmp(paramName, "bl") == 0) {
          if ((blValue = atoi(paramValue)) ==0) {
            blValue = 7;
          }
        }
        // パラメタ名が"word"であれば、テキストとみなしてwordValueに値を格納
        else if (strcmp(paramName, "word") == 0) {
          wordValue = paramValue;
        }
      }

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

    // クライアントにデータを返す
    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>");
    // クライアントから送信されたLCDバックライト色をフォームに反映
    client.print("<input type=\"radio\" name=\"bl\" value=\"1\""); if (blValue == 1) { client.print(" checked=\"checked\""); } client.println(" />赤");
    client.print("<input type=\"radio\" name=\"bl\" value=\"2\""); if (blValue == 2) { client.print(" checked=\"checked\""); } client.println(" />緑");
    client.print("<input type=\"radio\" name=\"bl\" value=\"4\""); if (blValue == 4) { client.print(" checked=\"checked\""); } client.println(" />青");
    client.print("<input type=\"radio\" name=\"bl\" value=\"7\""); if (blValue == 7) { client.print(" checked=\"checked\""); } client.println(" />白");
    client.println("</p>");
    client.println("<p>");
    // クライアントから送信されたテキストをフォームに反映
    client.print("<input type=\"text\" name=\"word\" value=\""); if (isPOST) { client.print(decodeWordValue); } client.print("\" />");
    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");

    // POSTで呼び出された場合、LCDの表示を更新
    if (isPOST) {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("message received");
      lcd.setCursor(0, 1);
      // dispMsgはLCDに表示する文字列を格納する変数
      // 変数のサイズはLCDに表示可能な文字数+1('\0'を考慮して1文字分多く確保)
      char dispMsg[LCDWIDTH + 1];
      // テキストからLCDに表示可能な文字数を切り取ってdispMsgに格納
      strncpy(dispMsg, decodeWordValue, LCDWIDTH);
      // strncpyは'\0'をつけてくれないので、自分でつける
      dispMsg[LCDWIDTH] = '\0';
      lcd.print(dispMsg);
      lcd.setBacklight(blValue);
    }
  }

  // スクロール間隔を調整するため、250msec待つ
  delay(250);

  // テキストがLCDで表示可能な文字数を超えた場合、テキストを左にスクロールして表示
  // forとかwhileで実装すると、ぐるぐるまわっている間はクライアントからの接続を受け付けなくなる
  // Adafruit_RGBLCDShieldライブラリのscrollDisplayLeftでは、40文字までしか全体を表示できないっぽい
  if (strlen(decodeWordValue) > LCDWIDTH) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("message received");
    lcd.setCursor(0, 1);
    // dispMsgはLCDに表示する文字列を格納する変数
    // 変数のサイズはLCDに表示可能な文字数+1('\0'を考慮して1文字分多く確保)
    char dispMsg[LCDWIDTH + 1];
    // テキストからLCDに表示可能な文字数を切り取ってdispMsgに格納
    // dispMsgIdxで文字列を切り取る位置をずらすことでスクロールさせる
    strncpy(dispMsg, decodeWordValue + dispMsgIdx, LCDWIDTH);
    // strncpyは'\0'をつけてくれないので、自分でつける
    dispMsg[LCDWIDTH] = '\0';
    lcd.print(dispMsg);

    // dispMsgIdxをインクリメント
    // テキストの終わりにきたらdispMsgIdxをリセットして、テキストの先頭から表示
    if (++dispMsgIdx > strlen(decodeWordValue) - 1) {
      dispMsgIdx = 0;
    }
  }
}


実行してみた動画を貼っておきます。


[Arduinoで遊ぶ]