2014年5月1日木曜日

twitterからarduinoに指示を出す





外出先から家のarduinoにアクセスする方法の一つに、イーサネットライブラリを使いwebサーバーを立てる方法があります。
PHPも使えばいろいろできそうです。

が、ルーター外部からLAN内に接続するにはハードル高いです。
1つに、ルーターのインターネット側のIPは一般的に動的に割り当てられているので、いつ変わってもおかしくない。
IPが変わってもアクセスするにはDDNSサービスに登録するのが手軽。
無料サービスもあります。これは登録しました。

arduinoで立てたwebサーバーに接続するには、webサービスに接続するのに使うポート80への接続をarduinoのローカルIPにまわすポートフォワーディングのルーター設定が必要です。
必要のないときに、ポートが開いているのもちょっと心配。

次の方法として、VPN(Virtual Private Network)で接続する方法。
インターネットからセキュアにLANに参加できる方法の一つ。
LAN内にVPNサーバーを管理するPCを用意するか、VPN対応ルーターを使うかです。
自宅のルーターはVPNでログインしてLANに参加できますが、PPTPを使った接続です。
PPTPにはセキュリティーリスクがあるので使わないほうがいいとのこと。
家庭用ルーターでPPTP以外の製品は少ないようです。
VPNサーバーは無料のものがありますが、難しくて断念しました。

で、一番簡単に指示を出せそうなのがtwitterでした。
arduinoにライブラリがあるのですぐ使えそうです。
arduino宛のツイートで指示を出したり、照度、室温をツイートしている実例もあります。
挑戦してみました。

使ったtwitterライブラリはこちら。使い方もこのページだけでわかります。
Arduinoで遊ぼう - OAuthを使って安全につぶやくライブラリ「Stewitter」
http://arms22.blog91.fc2.com/blog-entry-296.html

この記事のスケッチも上記スケッチを元にしました。

twitterアカウントを取得して、StewGate Uでトークンを取得。
http://stewgate-u.appspot.com/u/

スケッチの変更
・MACアドレスを自分のイーサネットシールドの値に変更
・取得したトークンを記述
・String auth_from = "twitterアカウント"; //操作を許可するアカウント@は付けない

スケッチの例では、
@アカウント名 led on
とつぶやくと、7番ピンがHIGHになります。


コマンドを実行した時はarduinoがつぶやきます。

これはtwitterからled と relay(SSR) をon/offした様子。

つぶやくとき、Stewitterに渡す文字列はchar型でないといけません。
取り扱いはString型が便利(SRAMは消費するらしい)なので、つぶやく直前に
char post_msg_char[255];
post_msg.toCharArray(post_msg_char,255);
で変換しています。

twitterサーバーへの問い合わせなど、一定時間内の回数制限があるようなので、delayを60秒入れてるので反応に1分ほどかかることがあります。タイミング次第。
重複ツイートを回避するために、millis()をツイートに含めています。

コマンドがわからなくなった時のために、helpに反応するようにしたらシリアル出力に怪しいところがありました。
SRAM容量の限界かも知れませんので、help機能は削りました。
SRAMを消費するような命令を追加する時は気をつけてください。
外部SRAMも使えるように用意しておこうかな。
ちなみにスケッチをコンパイルすると19,764バイトでした。




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

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };                //MACアドレス
Stewitter twitter("your token");                                    //あなたのトークンで書き換えてください。
String post_msg;                                                    //投稿用メッセージ char型に変換しなければならない

String recieved_msg;                                                //受信したメッセージを格納
String last_msg_id;                                                 //最後に受信したメッセージのID
String auth_from = "コマンドを許可するtwitterID";                   //コマンドを許可する送信者ID @無しで記入
boolean f_done = false;                                             //コマンドを実行したか
boolean f_post = false;                                             //投稿が正常に行われたか
byte pos_splitter_2;                                                //2つめの'|'の場所
String command[] = {"led on", "led off", "relay on", "relay off"};
                                                                    //反応するコマンド一覧。配列の先頭から実行される
                                                                    //追加したら92行目付近のswitch case文も修正

byte pin_led = 7;
byte pin_relay = 6;

void setup(){
  delay(2000);
  Ethernet.begin(mac);
  Serial.begin(9600);
  pinMode(pin_led, OUTPUT);
  pinMode(pin_relay, OUTPUT);
}

void loop(){
  twitter_get();
  delay(60000);                                            //60秒で1回に制限
}

void twitter_get(){
  if (twitter.lastMention()) {                             // twitterに接続して、最後のメンション(@yourname の入ったメッセージ)を取得
    int status = twitter.wait();                           // 完了を待つ
    if (status == 200) {                                   // status が 200なら正常に完了
      recieved_msg = twitter.response();                   // 最後のメンションを取得
                                                           // ex) 20100111082341|874453678|whosaysni|@yourname whats up?
      if( recieved_msg.indexOf("API_CALL_FAILED") == 0){   //ステータス200でもエラーが有る アクセス回数制限等
        Serial.println(recieved_msg);
      }
      else{
        check_msg();
      }
    }
    else {
      Serial.print("failed : code ");
      Serial.println(status);
    }
  }
  else {
    Serial.println("connection failed.");
  }
}

void check_msg(){                                          //新規投稿があったかチェック
  byte pos_splitter_1 = 14;
       pos_splitter_2 = recieved_msg.indexOf('|',pos_splitter_1 + 1);
  byte pos_splitter_3 = recieved_msg.indexOf('|',pos_splitter_2 + 1);

  String latest_msg_id = recieved_msg.substring(pos_splitter_1 + 1, pos_splitter_2 );
  String latest_msg_from = recieved_msg.substring(pos_splitter_2 + 1, pos_splitter_3);

  Serial.println("");
  Serial.println(recieved_msg);
  Serial.println(latest_msg_id);                                          //メッセージID
  Serial.println(latest_msg_from);                                        //送信者
  if(last_msg_id != latest_msg_id ){                                       //新規投稿があったか
    Serial.println("New message.");
    last_msg_id = latest_msg_id;
    if( latest_msg_from == auth_from ){                                    //権限のある送信者がarduinoに送ったなら
      Serial.println("authority OK");
      recieved_msg.replace(auth_from, " ");                                //コマンドがユーザーIDに含まれているかもしれないので除去
      check_command();
    }
  }
  else{
    Serial.println("NO UPDATE");
  }
}

void check_command(){                                                       //メッセージにコマンドが含まれるかチェック
  byte num_command = sizeof(command) / sizeof(command[0]);                  //command[]の要素数
  f_done = false;
  post_msg = "[" + String(millis()) + "] " + "@" + auth_from + " command No.[ ";  //重複投稿で投稿失敗を防ぐ
  for(byte i = 0; i < num_command; i++){
    if( recieved_msg.indexOf( command[i], pos_splitter_2) != -1 ){          //コマンド一覧と受信メッセージの比較
      Serial.print("found command !  >> ");
      Serial.println( command[i] );
      switch(i){                                         //一致したコマンド(配列の添字)で分岐
        case 0:
          digitalWrite(pin_led, HIGH);
          break;
        case 1:
          digitalWrite(pin_led, LOW);
          break;
        case 2:
          digitalWrite(pin_relay, HIGH);
          break;
        case 3:
          digitalWrite(pin_relay, LOW);
          break;
      }
      f_done = true;
      post_msg += String(i) + ", ";
    }
  }
  if(f_done){                                            //コマンドを実行したので報告を投稿
    post_msg += "] has been executed.";
    Serial.println(post_msg);
    f_post = false;
    byte cnt_post = 0;
    while(f_post == false){                              //投稿が成功するまでトライ
      cnt_post +=1;                                      //トライ回数をカウント
      if(cnt_post <= 10){                                //30秒ごとに10回までトライ
        twitter_post();
        if(f_post == false){
          delay(30000);
        }
      }
      else{                                              //10回を超えたら断念
        Serial.println("投稿10回トライしましたが失敗しました");
      }
    }
  }
  else{
    Serial.println("no command in message.");
  }
}

void twitter_post(){
  char post_msg_char[255];
  post_msg.toCharArray(post_msg_char,255);                 //String から char に変換
  if (twitter.post(post_msg_char)) {                       // twitterに接続して、メッセージを送信
    int status = twitter.wait();                           // 完了を待つ
    if (status == 200) {                                   // status が 200なら正常に完了
      f_post = true;
      Serial.println(twitter.response());                  // レスポンスを表示
    }
    else {
      f_post = false;
      Serial.print("failed : code ");
      Serial.println(status);
    }
  }
  else {
    f_post = false;
    Serial.println("connection failed.");
  }
}




環境:arduinoUNO、arduinoIDE1.0.5、win7(64)



0 件のコメント:

コメントを投稿