2016年7月19日 星期二

★ 使用 ESP8266 傳送 Twitter 訊息

我去年買了一塊 Wiznet5100 乙太網擴充板疊在老張借我的 Arduino UNO 板上, 進行過一次透過 Ethernet 傳送 Twitter 的實驗, 參考 :

# Arduino 網路測試 : 發送 Twitter

但是自從學會使用 ESP8266 讓 Arduino 上網之後, 這塊又貴又大 (比起 ESP8266) 的板子就連同 UNO 一起放在零件盒裡不見天日了. 能用 Wifi 上網幹嘛還要用需要拉網路線的乙太網呢?

今天在 Oreilly  "Arduino Cookbook 錦囊妙計" 這本書的 15.12 節又看到傳送訊息到 Twitter 的介紹, 那今晚就用 ESP8266 來重作這個實驗吧!

在上面 2015/4/2 的 Twitter 實驗中, 我是依照 "Arduino 自造指南" 這本書的作法, 透過 arduino-tweet 這個第三方網站所提供的函式庫來傳送訊息給 Twitter. 不過那個函式庫是為乙太網寫的, 沒辦法用在 ESP8266. 在歐萊里出版的 "Arduino Cookbook 錦囊妙計這本書裡的 15.12 節有介紹利用 Thingspeak 的 ThingTweet API 來當中繼站傳送 Tweet 訊息到自己的推特.

首先登入 Thingspeak 帳戶, 切換到 Apps 頁籤即可看到 ThingTweet 入口 :


點 "ThingTweet" 後按 "Link Twitter Account" 按鈕 :


按 "授權應用程式" 鈕 :


這樣就表示已授權 Thingspeak 傳送訊息到我的推特了.


按 "Back to ThingTweet" 就可以看到 API Key 了, 這個要記錄下來, 程式中要用到 :


接下來就可以撰寫程式, 讓 Arduino 利用 ESP8266 的 Wifi 連線功能向 Thingspeak 發出連線要求, 傳送 Tweet 訊息, 由 ThingTweet API 代為轉發到我們的推特上. 首先必須與 Thingspeak (網址 api.thingspeak.com) 建立 TCP 連線, 其 AP 指令如下 :

AT+CIPSTART="TCP","api.thingspeak.com",80

然後告訴 ESP8266 接下來要傳送的資料長度 :

AT+CIPSEND=字元數

最後傳送資料 (HTTP 內容), Thingspeak 網站的範例使用 HTTP 的 POST 方法, 下面這篇文章則是使用 GET 方法, 參考 :

Beginner's guide to ESP8266 and tweeting using ESP8266 

這裡為求簡單也使用 GET 方法, 傳送的資源字串格式為 :

GET /apps/thingtweet/1/statuses/update?api_key=ThingTweet識別碼&status=要傳送的推特訊息

完整的程式如下 :

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
String ThingTweet_API="B2B4JF5WEUXGCOUY";
int i=0; //counter

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  sendData("AT+GMR\r\n",1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  }

void loop() {
  Serial.println("Connecting Thingspeak ...");
  sendData("AT+CIPSTART=\"TCP\",\"api.thingspeak.com\",80\r\n",1000,DEBUG);
  String data="GET /apps/thingtweet/1/statuses/update?api_key=" + ThingTweet_API
              "&status=Hello! " + i + " times.\r\n";
  sendData("AT+CIPSEND=" + (String)data.length() + "\r\n",1000,DEBUG); 
  String res=sendData(data,3000,DEBUG);  
  res.replace("\r\n",""); //remove all line terminator   
  if (res.indexOf("Unlink") == -1) { //if no auto unlink
    sendData("AT+CIPCLOSE\r\n",2000,DEBUG); //close session
    }
  delay(60000);
  i++;
  }

String sendData(String command, const int timeout, boolean debug) {
  String res="";
  esp8266.print(command);
  long int time=millis();
  while ((time + timeout) > millis()) {
    while(esp8266.available()) {res.concat((char)esp8266.read());}
    }
  if (debug) {Serial.print(res);}
  return res;
  }

此程式中我使用了一個發送次數計數器來避免每次發文內容相同, 這是推特的限制之一, 即連續兩次推文不可雷同, 否則第二則推文不會發送成功. 另外推特還有一個限制, 就是一分鐘內只能發送一個推文, 因此我在 loop() 最底下用了 delay(60000) 讓程式到此停止一分鐘以滿足推特之限制. 此程式每分鐘會發出 "Hello! x times." 的推文到我的 Twitter 上. 另外, length() 傳回的是整數, 因此必須用 (String) 強制轉型.

程式上傳執行後, 我手機果然每分鐘收到推文了 :


串列埠監控視窗擷取訊息如下 :

AT+RST


OK

bB�鑭b禕S��"愃L�侒��餾�
[System Ready, Vendor:www.ai-think
192.168.43.40

OK
Connecting Thingspeak ...
AT+CIPSTART="TCP","api.thingspeak.com",80


OK
Linked
AT+CIPSEND=88

> GET /apps/thingtweet/1/statuses/update?api_key=B2B4JF5WEUXGCOUY
SEND OK

+IPD,1:1
OK

OK
Unlink


Connecting Thingspeak ...
AT+CIPSTART="TCP","api.thingspeak.com",80


OK
Linked
AT+CIPSEND=88

> GET /apps/thingtweet/1/statuses/update?api_key=B2B4JF5WEUXGCOUY
SEND OK

+IPD,1:1
OK

OK
Unlink

可見每次傳送完畢, Thingspeak 通常都會自動拆線, 但萬一沒有自動拆線的話, 就必須下 AT+CIPCLOSE 的指令強制拆線. 所以上面程式中對最後一次呼叫 sendData() 的傳回值進行字串處理, 若沒有找到 "Unlink"  字眼就強制拆線.

2016-07-27 補充 :

上面範例程式使用我自製的 IOT 模組, 已經事先切換到設定模式, 以手機設定好要連線之 AP 後, 再切回工作模式, 因 ESP8266 特性是除非寫入新的設定值, 否則它就保存最近一次的寫入值, 因此上面範例程式中不須使用 AT+CWJAP 指令去設定要連線之 AP.

如果要在程式中自行設定連線 AP 要怎麼做? 下列範例程式中我另外寫了一個 connectWifi() 函數, 只要傳入 ssid 與 pwd, 它會向 ESP8266 送出 AT+CWJAP 指令, 若回應中有 "OK", 就回傳 true, 否則 false.

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
String ssid="H30-L02-webbot";
String pwd="1234567890";
String ThingTweet_API="B2B4JF5WEUXGCOUY";
int i=0; //counter

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  while (!connectWifi(ssid, pwd)) {
    Serial.println("Connecting WiFi ... failed");
    delay(2000);
    }
  sendData("AT+GMR\r\n",1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  }

void loop() {
  Serial.println("Connecting Thingspeak ...");
  sendData("AT+CIPSTART=\"TCP\",\"api.thingspeak.com\",80\r\n",1000,DEBUG);
  String data="GET /apps/thingtweet/1/statuses/update?api_key=" + ThingTweet_API +
              "&status=Hello! " + i + " times.\r\n";
  sendData("AT+CIPSEND=" + (String)data.length() + "\r\n",1000,DEBUG);
  String res=sendData(data,3000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("Unlink") == -1) { //if no auto unlink
    sendData("AT+CIPCLOSE\r\n",2000,DEBUG); //close session
    }
  delay(60000);
  i++;
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",8000,DEBUG); 
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}  
  }

String sendData(String command, const int timeout, boolean debug) {
  String res="";
  esp8266.print(command);
  long int time=millis();
  while ((time + timeout) > millis()) {
    while(esp8266.available()) {res.concat((char)esp8266.read());}
    }
  if (debug) {Serial.print(res);}
  return res;
  }

在 setup() 中我們用 while 來檢查 connectWifi() 是否傳回 true, 是的話表示連線成功, 否則就一直印出連線失敗訊息, while 迴圈不會結束, 因此沒機會到 loop() 中執行 twitter 訊息之發布. 注意, 在 connectWifi() 函數中, AT+CWJAP 的等待時間若太短的話, 可能會一直無法連上基地台, 因為連線需要一些時間. 我之前用 6 秒, 發覺不是很穩定, 調成 8 秒後就沒問題了.

參考 :

How to tweet from an Arduino using the wifi sheild
# 以 Arduino+ESP8266 物聯網模組重作 DHT11 溫溼度實驗
# How to tweet with your Arduino
Twitter controlled Arduino Outputs - no PC - LCD Display + Sensor data to Twitter
# TweetControl: Control Anything with Twitter


6 則留言 :

Tom 提到...

酷~~

Tom 提到...

版主您好
我想使用以下方法讓推特自動發文

String data="GET /apps/thingtweet/1/statuses/update?api_key=" + ThingTweet_API;
data += "&status=濕度: " + (String)h +"%";
data += "% 溫度: " + (String)t + "*C.\r\n";
data += "\r\n";

如果只有濕度是可以的((String)h後面加上"%"就不行 其他字就可以)
DEBUG一直回傳無法正常發文的訊息 請問要如何在一篇推特文章中加入2個由ARDUINO取得的變數呢?

還有想問如何讓推特文章換行?

小狐狸事務所 提到...

林兄您好, url 字串中 % 字元有特殊用途 (表示編碼), 要傳送 '%' 字元本身要改用 '%25', 而跳行則要改用 '%0d%0a'.
參考 : http://www.convertstring.com/zh_TW/EncodeDecode/UrlEncode 與 https://openhome.cc/Gossip/Encoding/URLEncoding.html :

"在URI的規範中定義了一些保留字元(Reserved character),像是「:」、「/」、「?」、「&」、「=」、「@」、「%」等字元,在URI中都有它的作用,如果你要在請求參數上表達URI中的保留字元,必須在%字元之後以十六進位數值表示方式,來表示該字元的八個位元數值。"

Tom 提到...

Tony Huang 感謝您的回覆,終於藉由編碼打出%和換行了
真的非常感謝您!!

Unknown 提到...

可以跟你問一下 為甚麼 我加按鈕上去 變成無法傳送文字上去 是因為delay問題嗎>?

小狐狸事務所 提到...

按鈕有機械彈跳現象需處理, 參考 :

http://yhhuang1966.blogspot.tw/2016/09/arduino_11.html
http://yhhuang1966.blogspot.tw/2016/09/arduino-interrupt.html