2015年9月23日 星期三

使用 Arduino IDE 開發 ESP8266 物聯網應用 - 取回 ThingSpeak 特定 Channel 和 Field 最後一筆資料

網頁最後修改時間:2015/09/22

上一篇 "使用 Arduino IDE 開發 ESP8266 物聯網應用 - ThingSpeak, HTTP GET / POST 資料上傳方法"網頁中提到了使用 HTTP GET 與 HTTP POST 上傳資料到 ThingSpeak IoT Server 的方法;這一篇反過來,要說明如何取回 ThingSpeak IoT Server 特定 Channel 和 Field 上最後一筆資料。

ps. 其實寫到最後發現!只有一個情況下,不管如何只能取回最後一筆資料;但另外一個情況則是,相同的程式碼處理下,取回多筆資料會比取回最後一筆資料來的容易解釋!

為什麼是最後一筆,而不是全部或是其中一點的資料 ? 原因是:除非在資料集中的每一個數據都很重要,不然最後一筆資料就是最重要的數據;另外一點,就是語法都差不多,差異在於後面所接的參數不同而已,只要看過下面所舉的例子之後,舉一反三絕不會是問題!

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
有購買商品的使用者,網頁中所需相關資料已放置於雲端硬碟,請自行下載使用!
其餘的使用者,請自行依照提供之連結下載相關資料,程式碼複製貼上使用!
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

NOTE:本篇網頁所使用的程式經過 ESP8266 Arduino IDE 開發板  (  本文簡稱開發板  )測試通過!不過,同樣適用於其他使用 Arduino IDE 開發的 ESP8266, ESP-## 型號的板子,但請自行測試!
除了與網頁內容相關的討論之外,像是 Arduino IDE 與其他 ESP8266 型號連線的問題等,請使用者自行搞定!!!

*********************************************************************************
本網頁所使用到的零件可至下面連結訂購:
ESP8266 相關商品可至下面連結訂購:
*********************************************************************************

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
在開始此篇網頁之前,建議使用者依序看過下面這兩篇部落格關於 ESP8266 說明網頁後
,再繼續往下看!
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

打開之前所建立的 ThingSpeak 中的一個 Channel,也就是之後要用來取出數值的 Channel
Example: ThingSpeak DHT11 Channel
首先記住上面的 Channel ID ( 我的是 18795 )。接著,若是這個 Channel 是設定為 Public ( 看 Access 欄位 ),則不需要取得 READ API KEY;反之若是 Private,按下 "API Keys" 進入畫面,在畫面左下方按下按鈕 "General New Read API Key" 產生一個新的  READ API KEY,記下它 ( READAPIKEY012345 ),後面要使用這個 Key 才能讀取 Channel 裡面的資料。

HTTP GET / POST 與 ThingSpeak Channels 的關係:

要取回 ThingSpeak IoT Server 特定 Channel 中指定的 Field# ( # = 1 ... 6 ) 中的資料,依照官網上的說明,只能選擇使用 HTTP GET 的方法,相關的格式說明,請點擊下方連結並開啟一個新視窗做為等一下對照之用

ThingSpeak, Doc, Channels

開啟之後,在網頁中找到文字 "Response in:" ( Ctrl+F ),旁邊有不同的響應方式可以選擇 ( 有 TEXT、JSON 和 XML 三種 )。選擇並點擊 JSONJSON 會變為綠色,且相對應的 HTTP GET 格式也會跟著改變。使用這種方式所傳送的 HTTP GET 格式,ThingSpeak 所回應的資料就是 JSON 格式,也是最後我們要處理的東西。

ThingSpeak Doc, Channels 網頁中,關於資料的取回的段落有下面幾個:
  • Get a Channel Feed
    使用這個 HTTP GET 格式,可以取回指定的 Channel 中所有 Fileld1 ~ Field6 的指定時間區隔、數量的數據,並且進行這些數據中有效值的加總、平均和中間值的運算。
    實際範例請參考該網頁。
    .
  • Get Last Entry in a Channel Feed
    使用這個 HTTP GET 格式,可以取回指定的 Channel 中所有 Fileld1 ~ Field6 最後一筆輸入的數據。
    實際範例請參考該網頁。
    .
  • Get Specific Entry in a Channel
    使用這個 HTTP GET 格式,可以取回指定的 Channel 中所有 Fileld1 ~ Field6 第幾個輸入數據的資料。
    實際範例請參考該網頁。
    .
  • Get Channel Field Feed
    使用這個 HTTP GET 格式,可以取回指定的 Channel 中,特定 Fileld1 ~ Field6 其中一個 Filed 的指定時間區隔、數量的數據,並且進行這些數據中有效值的加總、平均和中間值的運算。
    實際範例請參考該網頁,或看下面的額外說明。

    .
  • Get Last Entry in a Field Feed
    使用這個 HTTP GET 格式,可以取回指定的 Channel 中特定 Fileld1 ~ Field6 其中一個] Field 最後一筆輸入的數據。實際範例請參考該網頁,或看下面的額外說明。
    .
上面的各個 HTTP GET 都配置了一些 Key / Value 的參數讓使用者可以選擇性的加入或是移除。下面將會針對最後兩種取回資料的 HTTP GET 格式深入說明,若對於其他 HTTP GET 格式有疑問的話,請自行參考官方網頁的說明。只要能夠了解 ThingSpeak, Doc, Channel 上面關於使用 HTTP GET 取回資料的格式,使用上一篇網頁的程式碼對送出的 HTTP GET 字串做修改,就能開始針對 JSON 做分析,取出相對應的欄位名稱與值作為他用。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* Get Channel Field Feed ( Response in: JSON ):

先看一下此種 HTTP GET 的格式
https://api.thingspeak.com/channels/CHANNEL_ID/fields/FIELD_ID.json 

其中,
  • CHANNEL_ID:就是使用者想要取出的 Channel ID,也就是 18795
  • FIELD_ID       :就是 Channel 中要取出的那一個 Field 的數字,也就是 1 ( 對於 DHT11 來說,代表溫度值 )

網址後面可接的東西就是直接在 HTTP GET 格式之後加上 ? 再加上下面列出的參數與值,例如

{HTTP GET}?[參數1=值]&[參數2=值]...

這個格式的參數有好幾個,每個參數後面都有可輸入的值格式的說明:

  • api_key (string) Read API Key for this specific Channel (optional--no key required for public channels)
  • results (integer) Number of entries to retrieve, 8000 max, default of 100 (optional)
  • days (integer) Number of 24-hour periods before now to include in feed (optional)
  • start (datetime) Start date in format YYYY-MM-DD%20HH:NN:SS (optional)
  • end (datetime) End date in format YYYY-MM-DD%20HH:NN:SS (optional)
  • timezone (string) Timezone identifier for this request (optional)
  • offset (integer) Timezone offset that results should be displayed in. Please use the timezoneparameter for greater accuracy. (optional)
  • status (true/false) Include status updates in feed by setting "status=true" (optional)
  • metadata (true/false) Include Channel's metadata by setting "metadata=true" (optional)
  • location (true/false) Include latitude, longitude, and elevation in feed by setting "location=true" (optional)
  • min (decimal) Minimum value to include in response (optional)
  • max (decimal) Maximum value to include in response (optional)
  • round (integer) Round to this many decimal places (optional)
  • timescale (integer or string) Get first value in this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)
  • sum (integer or string) Get sum of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)
  • average (integer or string) Get average of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)
  • median (integer or string) Get median of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)
  • callback (string) Function name to be used for JSONP cross-domain requests (optional)
Please note that the results parameter is not compatible with timescale, sum, average, or median.
可以指定數據的起始 ( start ) 和結束 ( end ) 時間間隔;指定時間顯示的時區 ( timezone );或是要取回的數據數量 ( results ) ... 等多種的格式組合。

以取回 Channel ID = 18795,Filed1 的最後一筆數據 ( 溫度值 ) 為例,將下面組合之後的 HTTP GET 格式輸入到瀏覽器的網址中
https://api.thingspeak.com/channels/18795/fields/1.json?api_key=READAPIKEY012345&results=1
或是下面將 api_key 變成 key,ThingSpeak 都可以接受
https://api.thingspeak.com/channels/18795/fields/1.json?key=READAPIKEY012345&results=1
成功傳輸的話,ThingSpeak 就會回覆如下類似的 JSON 字串在瀏覽器視窗中
{"channel":{"id":18795,"name":"DHT11","field1":"Temperature ( degC )","field2":"Humidity ( % )","created_at":"2014-11-19T13:53:29Z","updated_at":"2015-08-28T14:08:14Z","last_entry_id":25},"feeds":[{"created_at":"2015-08-28T14:08:14Z","entry_id":25,"field1":"29.5"}]}
將上面 JSON 字串複製並貼到 Json Parser Online 網站上,就可以得到整理之後的 JSON 以條列式的方式表示
  • {
    • "channel":{
      • "id":18795,
      • "name":"DHT11",
      • "field1":"Temperature ( degC )",
      • "field2":"Humidity ( % )",
      • "created_at":"2014-11-19T13:53:29Z",
      • "updated_at":"2015-08-28T14:08:14Z",
      • "last_entry_id":25
      },
    • "feeds":[
      1. {
        • "created_at":"2015-08-28T14:08:14Z",
        • "entry_id":25,
        • "field1":"29.5"
        }
      ]
    }
對照一下下面圖形上最後一個數據點,將上面 feeds: 裡的 created_at:2015-08-28T14:08:14Z 時間加上 +8 小時,就會與圖形所顯示的時間一樣。這是因為伺服器回傳是 UTC 時間而不是本地時間,使用者要根據自己所處區域自己設定,不過可以再深入討論一下
ThingSpeak, Channel ID 18795, Filed1 圖形
若是直接使用 timezone 的功能,則 HTTP GET 格式如下:
https://api.thingspeak.com/channels/18795/fields/1.json?api_key=READAPIKEY012345&timezone=Asia/Taipei&results=1
輸出就會如下面所示
{"channel":{"id":18795,"name":"DHT11","field1":"Temperature ( degC )","field2":"Humidity ( % )","created_at":"2014-11-19T21:53:29+08:00","updated_at":"2015-08-28T22:08:14+08:00","last_entry_id":25},"feeds":[{"created_at":"2015-08-28T22:08:14+08:00","entry_id":25,"field1":"29.5"}]}
將上面 JSON 字串複製並貼到 Json Parser Online 網站上,就可以得到整理之後的 JSON 以條列式的方式表,可看出其中時間部分已經更正為台灣時間
  • {
    • "channel":{
      • "id":18795,
      • "name":"DHT11",
      • "field1":"Temperature ( degC )",
      • "field2":"Humidity ( % )",
      • "created_at":"2014-11-19T21:53:29+08:00",
      • "updated_at":"2015-08-28T22:08:14+08:00",
      • "last_entry_id":25
      },
    • "feeds":[
      1. {
        • "created_at":"2015-08-28T22:08:14+08:00",
        • "entry_id":25,
        • "field1":"29.5"
        }
      ]
    }
至於其他的參數,請自行嘗試!

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
Get Last Entry in a Field Feed ( Response in: JSON )

至於這個部分的 HTTP GET 格式就相對的簡單
https://api.thingspeak.com/channels/CHANNEL_ID/fields/FIELD_ID/last.json
CHANNEL_ID 與 FIELD_ID 前面已經有說過,至於此部分可使用的參數相對比較少,大致上都跟上一節一樣,所以我們直接使用例子來說明。
以取回 Channel ID = 18795,Filed1 的最後一筆數據 ( 溫度值 ) 為例,將下面組合之後的 HTTP GET 格式輸入到瀏覽器的網址中
https://api.thingspeak.com/channels/18795/fields/1/last.json?api_key=READAPIKEY012345&timezone=Asia/Taipei
成功傳輸的話,ThingSpeak 就會回覆如下類似的 JSON 字串在瀏覽器視窗中
{"created_at":"2015-08-28T22:08:14+08:00","entry_id":25,"field1":"29.5"}
將上面 JSON 字串複製並貼到 Json Parser Online 網站上,就可以得到整理之後的 JSON 以條列式的方式表示
  • {
    • "created_at":"2015-08-28T22:08:14+08:00",
    • "entry_id":25,
    • "field1":"29.5"
    }
//**---**///*-/*---*/*---/*-/--*-/*-*--***////*--*/*/*/-*-/*/---/*/-*/*/*/-

上面兩個特別說明的 ThingSpeak HTTP GET 格式,如果還不了解箇中原由的話,請重新回頭看!動手做一下測試,直到清楚為止!

因為,現在才剛要進入主題!


ThingSpeak 的 HTTP GET 回應:

上一段介紹了 HTTP GET 與 ThingSpeak IoT Server 通訊並取回資料的方法,當中看到了由瀏覽器取回的回應的 JSON 字串。既然如此,為什麼還要在這段落再說一次 ?

原因是:眼見不一定為真!

ESP8266 不是瀏覽器,它不會預先幫使用者過濾一些 HTTP GET 回應的封包內容,但是瀏覽器會!就如同 HTML 檔案丟到瀏覽器,顯示出來的是經過瀏覽器處理之後要讓使用者看到的東西,還是顯示出 HTML 原始檔案格式 ?

所以說,上一段落的確是我們最終要得到的 JSON 格式的字串!但是使用 ESP8266 所得到的卻不是只有 JSON 格式的字串,而是 ThingSpeak 完完整整的 HTTP GET 回應的原始檔,裡面包含了 HTTP 傳輸上的一些東西 ( 簡單解釋看這篇 ),一般不會直接看到!但是直接接收來自於 ThingSpeak 的回應,卻是會收到這些額外我們不需要的東西,而這也是我們必須要去處理並且過濾掉的,才能取出我們所需要的 JSON 字串。

在 Arduino IDE 開啟一個新的 Sketch ,複製下面程式碼貼上並且儲存它,命名為 ThingSpealReply
#include <ESP8266WiFi.h>

//*-- Hardware Serial
#define _baudrate   9600

//*-- IoT Information
#define SSID        "路由器名稱"
#define PASS        "路由器連線密碼"
#define HOST        "api.thingspeak.com" // ThingSpeak IP Address: 184.106.153.149
#define PORT        80
#define READAPIKEY  "READAPIKEY012345"  // READ APIKEY for the CHANNEL_ID

void setup() {
    Serial.begin( _baudrate );
    Serial.println( "ESP8266 Ready!" );
    // Connecting to a WiFi network
    Serial.print("Connect to ");
    Serial.println( SSID );
    WiFi.begin( SSID, PASS );

    // 持續等待並連接到指定的 WiFi SSID
    while( WiFi.status() != WL_CONNECTED )
    {
        delay(500);
        Serial.print( "." );
    }
    Serial.println( "" );

    Serial.println( "WiFi connected" );
    Serial.println( "IP address: " );
    Serial.println( WiFi.localIP() );
    Serial.println( "" );

}

void loop() {

    // 每隔多久取一次資料
    retrieveField( 18795, 1 );  // filed_id=1 是 DHT11 的溫度值
    delay( 20000 ); // ms
}

void retrieveField( uint32_t channel_id, uint8_t field_id )
{
    // 設定 ESP8266 作為 Client 端
    WiFiClient client;
    if( !client.connect( HOST, PORT ) )
    {
        Serial.println( "connection failed" );
        return;
    }
    else
    {
        ////// 使用 GET 取回最後一筆 FIELD_ID 資料 //////

        /*** Method 1: ***/
        //
        //-- Get a Channel Field Feed --//
        // To view a Channel's field feed, send an HTTP GET to 
        //
        //  https://api.thingspeak.com/channels/CHANNEL_ID/fields/FIELD_ID.json
        //----
        String GET = "GET /channels/" + String(channel_id) + "/fields/" + String(field_id) + ".json?key=" +
                     READAPIKEY + "&results=1";
        //----

        Serial.println( "**-- Get a Channel Fiels Feed --**" );
        String getStr = GET + " HTTP/1.1\r\n";;
        client.print( getStr );
        client.print( "Host: api.thingspeak.com\n" );
        client.print( "Connection: keep-alive\r\n\r\n" );
        
        delay(10);
        
        // 讀取所有從 ThingSpeak IoT Server 的回應並輸出到串列埠
        while(client.available())
        {
            String line = client.readStringUntil('\r');
            Serial.print(line);
        }

        /*** Method 2: ***/
        //
        //-- Get Last Entry in a Fiels Feed --//
        // To get the last entry in a Channel's field feed, send an HTTP GET to 
        //
        //  https://api.thingspeak.com/channels/CHANNEL_ID/fields/FIELD_ID/last.json
        //----
        GET = "GET /channels/" + String(channel_id) + "/fields/" + String(field_id) + 
                    "/last.json?key=" + READAPIKEY;
        //
        //----

        Serial.println( "**-- Get Last Entry in a Fiels Feed --**" );
        getStr = GET + " HTTP/1.1\r\n";;
        client.print( getStr );
        client.print( "Host: api.thingspeak.com\n" );
        client.print( "Connection: close\r\n\r\n" );
        
        delay(10);
        
        // 讀取所有從 ThingSpeak IoT Server 的回應並輸出到串列埠
        while(client.available())
        {
            String line = client.readStringUntil('\r');
            Serial.print(line);
        }
        client.stop();
    }
}
使用之前,請記得修改 SSIDPASS READAPIKEY 這三個地方的參數設定;另外,一開始就忘了將 Channel ID 定義在前面,所以使用者必須將自己的 Channel ID 輸入到 loop() 裡的 retrieveField() 函式
void loop() {

    // 每隔多久取一次資料
    retrieveField( 18795, 1 );  // filed_id=1 是 DHT11 的溫度值
    delay( 20000 ); // ms
}
都完成後,按下 "Upload" 按鈕編譯與上傳程式到開發板。

打開 "Serial Monitor" 再按下開發板的 RESET1 按鈕,兩種 HTTP GET 方法取得的 ThingSpeak 回應的原始資料,就會完完整整的顯示在輸出視窗中 ( 資料太多,只節錄其中一部分 )
ThingSpealReply.ino "Serial Monitor" 輸出畫面

發送 HTTP GET 需求給 ThingSpeak 後,所得到的回應就是像上面一樣的資料,而 JSON 格式的字串就藏在最後面倒數第二行。

補充說明:
  • 倒數第一行:數字 0,表示輸出完成;應該不會有另外不同的數字出現。
  • 倒數第三行:十六進制的數字表示,指出下一行 ( 倒數第二行 )  有多少個字
詳細的輸出資料,都在下面兩個小節之中。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* Get Channel Field Feed ( Response in: JSON ) ThingSpeak 回應之原始資料:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
X-Cnection: close
Status: 200 OK
X-Frame-Options: ALLOWALL
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS, DELETE, PATCH
Access-Control-Allow-Headers: origin, content-type, X-Requested-With
Access-Control-Max-Age: 1800
ETag: "e5c482641fa6b8d74d546aee0ca3e952"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 88d1901e-790b-4b8a-834c-12816a957507
X-Runtime: 0.007428
X-Powered-By: Phusion Passenger 4.0.57
Date: Tue, 22 Sep 2015 01:51:56 GMT
Server: nginx/1.9.3 + Phusion Passenger 4.0.57

10a
{"channel":{"id":18795,"name":"DHT11","field1":"Temperature ( degC )","field2":"Humidity ( % )","created_at":"2014-11-19T13:53:29Z","updated_at":"2015-08-28T14:08:14Z","last_entry_id":25},"feeds":[{"created_at":"2015-08-28T14:08:14Z","entry_id":25,"field1":"29.5"}]}
0
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
Get Last Entry in a Field Feed ( Response in: JSON ) ThingSpeak 回應之原始資料
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: close
Status: 200 OK
X-Frame-Options: ALLOWALL
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS, DELETE, PATCH
Access-Control-Allow-Headers: origin, content-type, X-Requested-With
Access-Control-Max-Age: 1800
ETag: "ba589660c39a80fd60ecad73df80c219"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 9f6f3443-3760-454c-9dc8-c36b57c07a20
X-Runtime: 0.006541
X-Powered-By: Phusion Passenger 4.0.57
Date: Tue, 22 Sep 2015 01:51:59 GMT
Server: nginx/1.9.3 + Phusion Passenger 4.0.57

43
{"created_at":"2015-08-28T14:08:14Z","entry_id":25,"field1":"29.5"}
0
/*-/*--*/*/*-*---*////**---**/-*-*-/-*-*-/-*-*-*---*-*-*-*-/-**--*-/-/**-/-/-/


解析 JSON 字串:

當知道了回傳的資料格式之後,接下來的動作就是如何處理出 JSON 字串。首先,可以知道這些資料以""的方式輸入,每一行最後面以 \r 結束直到倒數第四行是以 \n 代表為一個空白行。接著,出現代表數字的字串輸入,可以處理也可以不處理,視使用者的需求決定,程式預設是保留但不處理。然後就是 JSON 字串,直接取出並保存到字串變數中即可。最後一行就直接跳過,不于理會。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* Get Last Entry in a Field Feed

延續剛剛的 Sketch 程式碼 ThingSpealReply,只需增加一個處理 JSON 的函式庫標頭檔於最前面的地方

#include <ESP8266WiFi.h>
#include <ArduinoJson.h>

然後複製下面的程式碼取代 retrieveField() 裡面 else{ } 所有的東西,另存新檔為 RetrieveHttpGetM2
    else
    {
        ////// 使用 GET 取回最後一筆 FIELD_ID 資料 //////
        
        /*** Method 2: ***/
        //
        //-- Get Last Entry in a Field Feed --//
        // To get the last entry in a Channel's field feed, send an HTTP GET to 
        //
        //  https://api.thingspeak.com/channels/CHANNEL_ID/fields/FIELD_ID/last.json
        //
        //  replacing CHANNEL_ID with the ID of your Channel and FIELD_ID with the ID of your field. 
        //
        // Example:
        //
       // https://api.thingspeak.com/channels/18795/fields/1/last.json?key=READAPIKEY012345
        //----
       String GET = "GET /channels/" + String(channel_id) + "/fields/" + String(field_id) + 
                    "/last.json?key=" + READAPIKEY;
        //
        //----

        String getStr = GET + " HTTP/1.1\r\n";;
        client.print( getStr );
        client.print( "Host: api.thingspeak.com\n" );
        client.print( "Connection: close\r\n\r\n" );
        
        delay(10);
        
        // 讀取所有從 ThingSpeak IoT Server 的回應並輸出到串列埠
        String section="HEAD";
        while(client.available())
        {
            String line = client.readStringUntil('\r');

            //** parsing the JSON response here ! **//
            // parse the HTML body
            if(section == "HEAD" )  // HEAD
            {
                Serial.print( "." );
                if( line == "\n" )  // 空白行
                {
                    section = "LENGTH";
                }
            }
            else if( section == "LENGTH" )
            {
                // 這裡可以取回 JSON 字串的長度
                // String content_length = line.substring(1);

                /* 有需要處理的寫程式在這裡 */

                section = "JSON";
            }
            else if( section == "JSON" )    // print the good stuff
            {
                Serial.println( "" );

                section = "END";
                String jsonStr = line.substring(1); // 給定一個從索引到尾的字串

                // 開始解析 JSON
                int size = jsonStr.length() + 1;
                char json[size];
                jsonStr.toCharArray(json, size);
                Serial.println( json );
                StaticJsonBuffer<200> jsonBuffer;
                JsonObject& jsonParsed = jsonBuffer.parseObject(json);
                if (!jsonParsed.success())
                {
                    Serial.println("parseObject() failed");
                    return;
                }
                
                const char *createdat = jsonParsed["created_at"];
                int entryid = jsonParsed["entry_id"];
                float field1 = jsonParsed["field1"];
                Serial.println("-- Decoding / Parsing --");
                Serial.print( "Created at: " ); Serial.println( createdat );
                Serial.print( "Entry id: " ); Serial.println( entryid );
                Serial.print( "Field 1: " ); Serial.println( field1, 1 );
            }
        }  
        client.stop();
    }

按下 "Upload" 按鈕編譯與上傳程式到開發板。

打開 "Serial Monitor" 再按下開發板的 RESET1 按鈕,得到的結果就如下面所示
Get Last Entry in a Field Feed 範例程式輸出
程式會一直取回 ThingSpeak 上面 Channel 18795, Field 1 最後一筆資料進行解析,並且輸出解析之後的結果。

-- Decoding / Parsing -- 上面一行是程式取出的 JSON 字串,下面一行開始,就是解析之後的欄位值。

解析之後的欄位名稱與值存放在 jsonparsed 物件中,要取出欄位值必須先指定欄位名稱。例如,要取出 Field 1 的資料,就輸入像下面的程式碼

float field1 = jsonParsed["field1"];

對照一下 JSON 字串裡面的關係,就不難了解到底要輸入什麼在 jsonParsed 物件陣列中取出相關欄位名稱的值了。另外一點,要特別注意取值時,要指定正確的資料格式以取出 jsonParsed 物件陣列的值。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* Get a Channel Field Feed

相對於前一小節 Get Last Entry in a Field Feed,這一小節就比較複雜,因為不管取回的數據有多少,都會被分成 channel:{},feeds"[{},{},...] 的格式,例如取回 3 筆資料
https://api.thingspeak.com/channels/18795/fields/1.json?api_key=READAPIKEY012345&results=3
去除掉前面不需要的部分,只留下 JSON 字串
{"channel":{"id":18795,"name":"DHT11","field1":"Temperature ( degC )","field2":"Humidity ( % )","created_at":"2014-11-19T13:53:29Z","updated_at":"2015-08-28T14:08:14Z","last_entry_id":25},"feeds":[{"created_at":"2014-11-20T03:30:12Z","entry_id":23,"field1":"24"},{"created_at":"2014-11-20T04:03:57Z","entry_id":24,"field1":"66"},{"created_at":"2015-08-28T14:08:14Z","entry_id":25,"field1":"29.5"}]}
將上面 JSON 字串複製並貼到 Json Parser Online 網站上,就可以得到整理之後的 JSON 以條列式的方式表示
  • {
    • "channel":{
      • "id":18795,
      • "name":"DHT11",
      • "field1":"Temperature ( degC )",
      • "field2":"Humidity ( % )",
      • "created_at":"2014-11-19T13:53:29Z",
      • "updated_at":"2015-08-28T14:08:14Z",
      • "last_entry_id":25
      },
    • "feeds":[
      1. {
        • "created_at":"2014-11-20T03:30:12Z",
        • "entry_id":23,
        • "field1":"24"
        },
      2. {
        • "created_at":"2014-11-20T04:03:57Z",
        • "entry_id":24,
        • "field1":"66"
        },
      3. {
        • "created_at":"2015-08-28T14:08:14Z",
        • "entry_id":25,
        • "field1":"29.5"
        }
      ]
    }
這樣的 JSON 字串沒有辦法被原本使用的函式庫處理並解析出每一個欄位值,必須再經過處理。

處理的方式見"人"見智,個人使用的方式:
  1. 刪除字串最後面的 ]}
  2. 尋找字串 },{ 再將其中的逗號更改為空白
  3. 以空白字元作為字串切割的依據,循環切割並處理
切割之後的字串,就是函式庫可以處理的 JSON 格式字串。

同樣的,然後複製下面的程式碼取代 RetrieveHttpGetM2 裡 retrieveField() 裡面 else{ } 所有的東西,另存新檔為 RetrieveHttpGetM1
    else
    {
        ////// 使用 GET 取回最後一筆 FIELD_ID 資料 //////

        // 有兩種方式可以取回 Field_id 中的最後一筆資料
        //
        //  NOTE: 
        //      If your channel is public, you don't need a key. 
        //      If your channel is private, you need to generate a read key.
        //

        /*** Method 1: ***/
        //
        //-- Get a Channel Field Feed --//
        // To view a Channel's field feed, send an HTTP GET to 
        //
        //  https://api.thingspeak.com/channels/CHANNEL_ID/fields/FIELD_ID.json
        //
        //  replacing CHANNEL_ID with the ID of your Channel and FIELD_ID with the ID of your field. 
        //
        // Example:
        // https://api.thingspeak.com/channels/18795/fields/1.json?key=READAPIKEY012345&results=1
        //
        // DHT11, field 1: temperature, field 2: humidity
        //----
        String GET = "GET /channels/" + String(channel_id) + "/fields/" + String(field_id) + ".json?key=" +
                     READAPIKEY + "&results=3";
        //----

        String getStr = GET + " HTTP/1.1\r\n";;
        client.print( getStr );
        client.print( "Host: api.thingspeak.com\n" );
        client.print( "Connection: close\r\n\r\n" );
        
        delay(10);
        
        // 讀取所有從 ThingSpeak IoT Server 的回應並輸出到串列埠
        String section="HEAD";
        while(client.available())
        {
            String line = client.readStringUntil('\r');

            //** parsing the JSON response here ! **//
            // parse the HTML body
            if(section == "HEAD" )  // HEAD
            {
                Serial.print( "." );
                if( line == "\n" )  // 空白行
                {
                    section = "LENGTH";
                }
            }
            else if( section == "LENGTH" )
            {
                // 這裡可以取回 JSON 字串的長度
                // String content_length = line.substring(1);

                /* 有需要處理的寫程式在這裡 */

                section = "JSON";
            }
            else if( section == "JSON" )    // print the good stuff
            {
                Serial.println( "" );

                section = "END";
 
                int size = line.length();
                String jsonStr = line.substring(1, size - 2);
                size = jsonStr.length() + 1;
                char json[size];
                jsonStr.toCharArray(json, size);
                Serial.println("--Json String --");
                Serial.println( json );

                for(int i = 0; i < size; i++)
                {
                    if(json[i] == '}')
                    {
                        if(json[i+1] == ',')
                        {
                            if(json[i+2]=='{')
                            {
                                json[i+1] = ' ';
                            }
                        }
                    }
                }

                char* feeds = (strstr( json, "\"feeds\"") + 9);
                Serial.println("-- Removed comma inside feeds string --");
                Serial.println(feeds); // 輸出 JSON 字串
                char* field;
                field = strtok( feeds, " " );
                while(field)
                {
                    Serial.println("-- Field --");
                    Serial.println(field);
                    StaticJsonBuffer<200> jsonBuffer;
                    JsonObject& jsonParsed = jsonBuffer.parseObject(field);
                    if (!jsonParsed.success())
                    {
                        Serial.println("parseObject() failed");
                        return;
                    }
                    const char *createdat = jsonParsed["created_at"];
                    int entryid = jsonParsed["entry_id"];
                    float field1 = jsonParsed["field1"];
                    Serial.println("-- Decoding / Parsing --");
                    Serial.print( "Created at: " ); Serial.println( createdat );
                    Serial.print( "Entry id: " ); Serial.println( entryid );
                    Serial.print( "Field 1: " ); Serial.println( field1, 1 );

                    field = strtok( NULL, " " );
                }               
            }
        }  
        client.stop();
    }

按下 "Upload" 按鈕編譯與上傳程式到開發板。

打開 "Serial Monitor" 再按下開發板的 RESET1 按鈕,得到的結果就如下面所示
Get a Channel Field Feed 範例程式輸出
原本只要取回最後一筆數據處理,但是最後發現:反正程式碼都一樣!就多取回幾筆數據做分析,多筆可以完成解析,單筆也不會有問題!

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
JSON 函式庫下載
程式使用 5.0.2 版本編譯、測試通過!

要用哪一個版本由使用者自己選擇,但請注意的是!因為這個函式庫並不是專為 ESP8266 撰寫,因此存在一些隱藏的 BUG,使用者必須自己處理這個問題!
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*


結論:

使用開發板在 Arduino IDE 做開發非常容易,只需要一條 MicroUSB 與電腦連接,就能做到程式碼的上傳以及 UART 除錯,使用上便利性很高!由於又開放多個 IO,可以瞬間將電子週邊瞬間升級為無線網路裝置,搭配上 IoT Server 則可說就是開始物聯網的應用。

IoT Server 上資料的上傳與下載有其限制所在,不同的 IoT Server 之間要看該網站中的文件說明。以 ThingSpeak 為例,建議上傳間隔不小於 15 秒 ,可以使用 HTTP GET 或是 HTTP POST ( 又或是其他方法 );下載資料則使用 HTTP GET ( 右或是有其他方法 ),根據所需要的回應方式而有不同的輸入格式 ( TEXT、JSON 或是 XML )。

本篇屬於下載資料的方法與程式碼說明,雖然只針對其中兩種,但是其輸入的 HTTP GET 格式只存在於些許不同,可選擇的配合參數卻是大同小異,多或少而以!雖然其他的 HTTP GET 格式可以說就只是其擴充或是縮減的版本而以,沒那麼複雜!

但對於程式碼來說,下載資料的格式以及所選用的 HTTP GET 格式,卻會影響到程式碼解析資料以及處理的複雜性。以 JSON 回應格式為例,即便回應的是 JSON 格式字串,但是因為所使用的 JSON 函式庫只接受某一格式類型的 JSON 格式輸入,所以在解析原始 JSON 回應字串時就必須再做簡化的處理,而不是一昧地將資料丟給 JSON 函式庫,因為這是行不通的!

在這網頁中所提出的兩種下載方法,其處理的過程和程式碼,可以作為其他下載方法處理的過程與程式的依據。

希望這篇網頁中的內容,對於有需要或是想要以其他下載方法取得 ThingSpeak IoT Server 資料的使用者,能夠起拋磚引玉的效果!

<< 部落格相關網頁連結 >>


37 則留言:

  1. 這裡的範例是以thingSpeak為例取回資料.但如果要取回的網站來源為自己的api時,如:www.hello.com.tw/read.aspx?id=1時.而回傳也是json方式.我該如何修改這段程式碼呢.string Get="要如何寫呢";謝謝您的回答

    回覆刪除
    回覆
    1. 如果這一段 www.hello.com.tw/read.aspx?id=1 是正確的,如下做修改應該就可以接收 JSON 字串
      #define HOST "www.hello.com.tw/read.aspx" // 這裡要改成做邊這樣
      #define PORT 80 // 如果不同自己改

      String GET = "GET " + "?id=1";

      是試吧!

      刪除
  2. 請問如果單獨買arduino 和ESP8266 兩個接在一起,是否也可以下載資料?
    目前這邊已經可以實做上傳,下載的部分還沒研究出來

    回覆刪除
    回覆
    1. 這邊使用的方式不是使用 AT 指令,是直接使用 Arduino IDE 撰寫程式上傳到 ESP8266,是不需要額外 Arduino 板子的!
      如果要使用 AT 方式,只要先解決使用 AT 指令連線到 thingspeak 的問題之後,大部分程式碼都差不多,可以自己研究一下!

      刪除
  3. 請問如果是分開買Arduino+ESP8266,接在一起,是否也可以做出資料的下載?
    目前已實做出資料的上傳,但下載的部分卡住

    回覆刪除
    回覆
    1. 當然可以!只要成使用 AT 指令連線的方式,其他的方式都一樣!對照一下部落格中關於上傳資料到 thingspeak 兩篇網頁就會比較清楚,再自己研究一下吧!

      刪除
  4. 請問一下,如果是用您賣的版子,想利用下載之後的資料去做改變接腳輸出,假使我給他#define _gate 13
    那GPIO的部分是ARDUINO的GPIO13還是ESP8266的GPIO13?

    回覆刪除
    回覆
    1. 對應開發板上的IO;網頁上面有標示。

      刪除
  5. Arduino:1.6.5 (Windows 8.1), 板子:"Arduino Pro or Pro Mini, ATmega328 (5V, 16 MHz)"

    建置選項已變更,重建所有

    ThingSpealReply.ino: In function 'void setup()':
    ThingSpealReply:20: error: 'wifi' was not declared in this scope
    ThingSpealReply:23: error: 'WiFi' was not declared in this scope
    ThingSpealReply:23: error: 'WL_CONNECTED' was not declared in this scope
    ThingSpealReply:32: error: 'WiFi' was not declared in this scope
    ThingSpealReply.ino: In function 'void retrieveField(uint32_t, uint8_t)':
    ThingSpealReply:47: error: 'WiFiClient' was not declared in this scope
    ThingSpealReply:47: error: expected ';' before 'client'
    ThingSpealReply:48: error: 'client' was not declared in this scope
    'wifi' was not declared in this scope

    以上是我編譯遇到的問題...
    我的"ESP8266wifi.h"程式碼在這裡抓的
    https://github.com/ekstrand/ESP8266wifi/blob/master/ESP8266wifi.h
    請問版主的ESP8266wifi.h在哪抓的呢?爬過版主的文章都找不到..
    麻煩了!感謝

    回覆刪除
    回覆
    1. 這不是給 Arduino Pro 相關板子用的程式;這是給 ESP8266 用的!

      刪除
  6. 請問我在編譯時出現這樣的錯誤碼
    Arduino:1.7.6 (Windows 7), 板子:"Arduino Mega or Mega 2560, ATmega2560 (Mega 2560)"

    WARNING: library ESP8266WiFi claims to run on [esp8266] architecture(s) and may be incompatible with your current board which runs on [avr] architecture(s).



    In file included from C:\Users\WaKuei\Documents\Arduino\libraries\ESP8266WiFi\src/ESP8266WiFi.h:33:0,

    from sketch_feb17a.ino:1:

    C:\Users\WaKuei\Documents\Arduino\libraries\ESP8266WiFi\src/ESP8266WiFiType.h:26:19: fatal error: queue.h: No such file or directory

    #include

    ^

    compilation terminated.

    編譯時發生錯誤

    請問我是哪邊出了問題?!
    不好意思 麻煩你了~

    回覆刪除
    回覆
    1. arduino.cc 所出的 Arduino IDE 新版本 1.6.7 有相容性的問題,請使用 Arduino IDE V1.6.5-r5 來做程式的撰寫與編譯,不要使用 arduino.org 所出的 Arduino IDE 版本。

      刪除
    2. 不好意思,沒有看清楚!

      這個網頁裡面所使用的硬體(http://ruten-proteus.blogspot.tw/2015/09/iot-esp8266-arduino-send.html) 就只有 ESP8266。您看到的那一片其實是已經內建 ESP-12E 的板子且不需要任何其他微控制器,因為 ESP8266 就是控制器且具有WiFi 功能。

      如果要使用 ESP8266, ESP-01 來做這個實驗的話,只需要這個網頁 (http://ruten-proteus.blogspot.tw/2014/11/internet-of-thing-arduino-esp8266.html) 中的硬體就可以,直接在 Arduino IDE 撰寫程式,然後直接上傳到 ESP-01 就可以!

      範例可以參可下面這個網頁中的說明
      http://ruten-proteus.blogspot.tw/2015/09/esp8266-kits-support-arduino-ide.html,

      刪除
  7. 您好,想請教,我在匯入GITHUB的 程式庫,會出現"指定的目錄/zip檔並未含有合法的程式庫"這段訊息,然後不給我匯入,請問該如何解決呢

    回覆刪除
    回覆
    1. 既然這樣就手動安裝就可以。
      https://www.arduino.cc/en/Guide/Libraries
      Ctrl+F 找 Manual install 這段,下面就是說明。

      刪除
  8. 您好,我想請教有關收資訊的部分,在收取thingspeaks 字串為求方便,我先寫死了如下(以AT指令在Serial monitor下測試沒問題):
    String GET = "GET /channels/126861/fields/1/last.json?api_key=2TUA6G8KZUICSJBA&timezone=Asia/Taipei\r\n";

    接著在撰寫程式將上面的字串送出去如下:
    if(wifi.createTCP(HOST_NAME, HOST_PORT))
    Serial.print("create tcp ok\r\n");
    else
    Serial.print("create tcp err\r\n");

    esp01.print("AT+CIPSEND=");
    esp01.println(GET.length());
    if(esp01.find(">")){
    Serial.print("start sentdata");
    esp01.print(GET);
    delay(200);
    }else{
    Serial.print("fail");
    esp01.println("AT+CIPCLOSE");
    }

    接下來收thingspeaks的資訊

    while (esp01.available())
    {
    Serial.write(esp01.read());
    }

    但是我都只收到「Recv 87 bytes」字串,而不是結果,請問要再加什麼指令嗎?

    謝謝!

    回覆刪除
    回覆
    1. 請問你的硬體環境設置是 Arduino + ESP8266-01 還是這個網頁中的那個板子 ?
      如果 "Arduino + ESP8266-01",那程式碼裡面為什麼要呼叫 wifi.createTCP() 指令;如果是使用網頁中的那塊 "ESP8266 Arduino IDE 開發板",那為什麼要輸入 AT 指令 ?
      關於 ESP8266 與 ThingSpeak 網站取值和丟值的網頁有很多篇,但是硬體使用不一樣,所以建議您再次確認您的硬體架構,再選擇部落格與你硬體相同或是類似的網頁看一下!
      有問題發問時,最好把輸出一次給,這樣會比較方便釐清問題 !

      刪除
    2. 感謝您的回覆,
      我是使用Arduino uno+ ESP8266-01,鮑率改為57600,並且使用ESP8266函式庫,如下網址所示:
      https://github.com/itead/ITEADLIB_Arduino_WeeESP8266

      此函式庫主要是將AT指令包裝起來使用,所以我才會某些程式使用AT指令來測試。其原本接收的函式如下:
      uint32_t len = wifi.recv(buf, sizeof(buf), 10000);
      if (len > 0) {
      Serial.print("Received:[");

      for(uint32_t i = 0; i < len; i++)
      Serial.print((char)buf[i]);

      Serial.print("]\r\n");
      }
      測試到剛剛,使用此函數庫時,大概要送5~10才有一次收得到ThingSpeaks的資訊,每一次的週期是15秒 。

      刪除
    3. 最好是附上全部的程式碼,有點上下文連不上!
      -------
      你應該是參考 HTTGET.ino 這個例子 ? 如果是:

      1.) 最前面這個地方先做好要使用 SoftwareSerial 的定義:
      SoftwareSerial Serial1(3, 2); /* RX:D3, TX:D2,隨便選兩個不用的接腳也可以 */
      ESP8266 wifi(mySerial1);
      2.) Baudrate 最好在沒有測試成功前不要貿然去改,使用預設的設定就好 !
      3.) 5-10物才會收到一次資訊,表示資料傳送的太頻繁,所以被 ThingSpeak 伺服器踢掉了!
      loop() 函式中,把 delay(15000) 加在這兩行程式上面:
      delay(15000);
      char *hello = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n";
      wifi.send((const uint8_t*)hello, strlen(hello));
      ----------
      如果你接線與其他程式碼都沒錯誤的話,3.) 的方法該就可以解決這個問題 !

      刪除
    4. 您好,跟據您上文的說明,目前測試結果,在Google和百度上的測試是100%可以接收到,但是thingspeak還是有點問題,跟上面一樣,約5-10次才會收到一次 。下面是我的程式碼

      #include "SoftwareSerial.h"
      #include "ESP8266.h"
      #include "String.h"

      #define HOST_NAME "184.106.153.149"
      #define HOST_PORT (80)

      bool isdebug = true; //顯示資訊用

      SoftwareSerial esp01(4,5);
      ESP8266 wifi(esp01, uint32_t(57600));

      /*
      由於ESP8266-01會自動連線,所以我就沒有重覆寫連上wifi的程式。
      */
      void setup(){
      bool issuccess = false;
      Serial.begin(57600);
      issuccess = wifi.kick(); //下AT,測試ESP8266是否還有回應。

      if(issuccess == true)
      Serial.print("ESP8266 Stand by\r\n");
      else
      Serial.print("Fail");
      }

      void loop()
      {
      uint8_t buf[256] = {0};
      bool issuccess = false;

      if(wifi.createTCP(HOST_NAME, HOST_PORT))
      Serial.print("create tcp ok\r\n");
      else
      Serial.print("create tcp err\r\n");

      delay(15000);
      String http = "";
      http += "GET /channels/126861/fields/1/last.json?api_key=2TUA6G8KZUICSJBA&timezone=Asia/Taipei";
      http += " HTTP/1.1\r\nHost: api.thingspeak.com\r\nConnection: close\r\n\r\n";
      issuccess = wifi.send((const uint8_t*)http.c_str(), http.length());
      /*
      為了方便測試,所以我把指令都寫死,比較好debug
      */
      if(issuccess == true)
      Serial.print("send data ok\r\n");
      else
      Serial.print("send data err\r\n");

      uint32_t len = wifi.recv(buf, sizeof(buf), 10000);
      if (len > 0) {
      Serial.print("Received:[");

      for(uint32_t i = 0; i < len; i++)
      Serial.print((char)buf[i]);

      Serial.print("]\r\n");
      }

      delay(100);

      if(wifi.releaseTCP())
      Serial.print("realse tcp ok\r\n");
      else
      Serial.print("realse tcp err\r\n");

      delay(2000);
      }

      刪除
    5. 請附上 Serial Monitor 的輸出

      刪除
  9. 想請問一個問題,我使用是Arduino UNO+ESP8266,我目前遇到的問題是,在retrieveField()下的if( !client.connect( IP, 80 ) )我一直無法通過這關道下一段else,我的上傳資料已經可以了,就插取值的問題,以下是我的程式碼:
    #include
    #include
    #include
    #define _baudrate 9600
    #define _rxpin 2
    #define _txpin 3
    SoftwareSerial debug( _rxpin, _txpin ); // RX, TX

    #define SSID "Silent.Xiaomi_2.4G"
    #define PASS "我的WIFI密碼"
    #define IP "184.106.153.149"
    #define PORT 80
    #define READAPIKEY "LME9X58DHRVT0QRY"

    void setup() {
    Serial.begin( _baudrate );
    debug.begin( _baudrate );
    sendDebug("AT");
    Loding("sent AT");
    connectWiFi();
    }
    void loop() {
    delay(10000); // 60 second
    retrieveField( 141948, 1 );
    }
    boolean connectWiFi()
    {
    debug.println("AT+CWMODE=1");
    Wifi_connect();
    }

    void retrieveField( uint32_t channel_id, uint8_t field_id )
    {
    // 設定 ESP8266 作為 Client 端
    String cmd = "AT+CIPSTART=\"TCP\",\"";
    cmd += IP;
    cmd += "\",80";
    sendDebug(cmd);

    if( Serial.find( "Error" ) )
    {
    Serial.print( "RECEIVED: Error\nExit1" );
    return;
    }
    WiFiClient client;
    if( !client.connect( IP, 80 ) )
    {
    Serial.println( "connection failed" );
    return;
    }
    else
    {
    ////// 使用 GET 取回最後一筆 FIELD_ID 資料 //////

    String GET = "GET /channels/" + String(channel_id) + "/fields/" + String(field_id) + ".json?key=" +
    READAPIKEY + "&results=1";

    client.println( "**-- Get a Channel Fiels Feed --**" );
    String getStr = GET + " HTTP/1.1\r\n";;
    client.print( getStr );
    client.print( "Host: api.thingspeak.com\n" );
    client.print( "Connection: keep-alive\r\n\r\n" );
    delay(10);

    // 讀取所有從 ThingSpeak IoT Server 的回應並輸出到串列埠
    while(client.available())
    {
    String line = client.readStringUntil('\r');
    Serial.print(line);
    }

    GET = "GET /channels/" + String(channel_id) + "/fields/" + String(field_id) +
    "/last.json?key=" + READAPIKEY;

    Serial.println( "**-- Get Last Entry in a Fiels Feed --**" );
    getStr = GET + " HTTP/1.1\r\n";;
    client.print( getStr );
    client.print( "Host: api.thingspeak.com\n" );
    client.print( "Connection: close\r\n\r\n" );
    delay(10);

    // 讀取所有從 ThingSpeak IoT Server 的回應並輸出到串列埠
    while(client.available())
    {
    String line = client.readStringUntil('\r');
    Serial.print(line);
    }
    client.stop();
    }
    }
    void Wifi_connect()
    {
    String cmd="AT+CWJAP=\"";
    cmd+=SSID;
    cmd+="\",\"";
    cmd+=PASS;
    cmd+="\"";
    sendDebug(cmd);
    Loding("Wifi_connect");
    }
    void Loding(String state){
    for (int timeout=0 ; timeout<10 ; timeout++)
    {
    if(debug.find("OK"))
    {
    Serial.println("RECEIVED: OK");
    break;
    }
    else if(timeout==9){
    Serial.print( state );
    Serial.println(" fail...\nExit2");
    }
    else
    {
    Serial.print("Wifi Loading...");
    delay(500);
    }
    }
    }
    void sendDebug(String cmd)
    {
    Serial.print("SEND: ");
    Serial.println(cmd);
    debug.println(cmd);
    }
    謝謝您,麻煩您了

    回覆刪除
    回覆
    1. Arduino + ESP8266 則所有的動作都是必須經由 AT 指令完成 WiFi 的連接以及取值.
      WiFiClient client; 這指令不應該出現在這個程式裡面,因為現在你的程式是使用 AT 指令控制 ESP8266。

      使用 Arduino 傳送 AT 指令控制 ESP89266 從 ThingSpeak 取值回來,這部分我沒寫;這網頁用的只有 ESP8266 並使用 Arduino IDE 寫程式來取值。所以若要你的方式,那程式就先要把不應該在的程式碼地除掉,單純只使用 AT 指令。

      另外,你的 ESP8266 如果與 Arduino 的 D2 和 D3 接在一起,那你的傳輸速率應該不會是 9600 吧 ?

      刪除
    2. 謝謝您這麼快地回復,好的,我在自己去做點功課,謝謝您告訴我WiFiClient client以及其他程式碼不應該出現的問題

      刪除
  10. 請教您文章中提到的"存在一些隱藏的 BUG,使用者必須自己處理這個問題!",是不是只會在接收端收到一個"{" , 若是,請問您後來有解嗎? 還是自己組字串? 謝謝

    回覆刪除
    回覆
    1. 5.0.2 的 JSON 函式庫在寫這篇網頁的時候是有 BUG,不過由賣場硬碟中下載的函式庫是經過修改過的,所以沒有這個 BUG!
      因為函式庫都會更新,所以新版的 JSON 程式應該將這問題修復了,可以下載新版本的回來測試,應該再遇到的機會不大 !

      刪除
  11. error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0

    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    error: failed sending 0xC0
    error: failed sending 8 bytes
    error: failed sending 36 bytes
    error: failed sending 0xC0
    warning: espcomm_sync failed
    error: espcomm_open failed
    error: espcomm_upload_mem failed

    不好意思版主 我在上傳的同時 遇到這些錯誤 請問要如何解決呢? 謝謝~
    P.S:我的開發版是選擇"Generic ESP8266 Module",序列阜也是正確的...

    回覆刪除
    回覆
    1. 關於這問題的回覆可以看這網頁裡面的詳細說明
      https://learn.sparkfun.com/tutorials/esp8266-thing-hookup-guide/introduction

      簡單說,就是通訊出現問題,所以沒辦法上傳程式。若硬體接線什麼的都沒有問題,看一下 Tools 選單下面的參數設定是否真的選擇正確;若 Serial 的速度設很快,可以降一些下來;還有,電源一定要使用外掛的,不能直接使用 USB 電源。

      刪除
  12. 請問若我想直接從自己架的網站網頁去抓thingspeak資料,而不是使用arduino IDE去抓,可以跟我大概說明下嗎
    謝謝

    回覆刪除
    回覆
    1. 使用 JavaScript 會容易許多,而且 JSON 處理也很方便。下面是一個範例,參考上面網頁對於取值的字串怎麼寫,改一下下面程式碼,將資料丟進去就可將值取回來;<> 已被換成 []
      -------------------------------
      [!DOCTYPE html]
      [html]
      [body]

      [div id="id01"][/div]

      [script]
      var xmlhttp = new XMLHttpRequest();
      var url = "GET 字串寫在這邊";

      xmlhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
      var myArr = JSON.parse(this.responseText);
      myFunction(myArr);
      }
      };
      xmlhttp.open("GET", url, true);
      xmlhttp.send();

      function myFunction(arr) {
      var out = "";
      var i;
      for(i = 0; i < arr.length; i++) {
      out += '['%20+%20arr%5Bi%5D.url%20+%20']' +
      arr[i].display + '';
      }
      document.getElementById("id01").innerHTML = out;
      }
      [/script]

      [/body]
      [/html]
      -------------------------------
      知道怎麼寫 Javascript 之後,深入的取值問題或是應用,看下面兩個網頁中的資料
      https://www.mathworks.com/help/thingspeak/index.html
      https://www.mathworks.com/help/thingspeak/

      刪除
  13. 請教一下,若除了將所有資料json parse後,想再特別抓出 field1 或 field2的值呈現,譬如在網頁做以下輸出:
    目前溫度:25 ; 目前濕度:75% 這樣要如何去抓呢?謝謝

    回覆刪除
  14. 看文件 https://www.mathworks.com/help/thingspeak/get-a-channel-feed.html 裡面描述的方法
    Example GET:
    GET https://api.thingspeak.com/channels/9/feeds.json?results=1
    這可取回 Channel 裡面所有的 Field 的最後一筆資料,看你的溫度在哪一個 filed,濕度在哪一個 field,解析之後就是你要的溫濕度。
    其他就在仔細研讀一下資料,裡面都有範例可以參考。只要能在網址列取回你要的資料,就可使用程式來完成你要的動作。

    回覆刪除
  15. 版主擬好!
    我輸入thingspeak 讀取資料的網址(https://api.thingspeak.com/channels/223012/fields/4.json?api_key=WE7DFPRX0WG3PZ1M&results=1)
    可是迴傳時卻出現"field4:null"
    雖然不是經常,但是有甚麼辦法可以解決?
    這是為何才會出現這個錯誤?

    回覆刪除
    回覆
    1. 我連續以及過一段時間連續重新整理那個網址,回傳的資料都是正常的,沒有你所謂的 field4:null 的情況出現!
      像這種情形,可能跟你的瀏覽器有關係,清一下緩衝或是什麼的或許會有幫助! 實際取資料的時候,有資料就會有資料回傳,不會有資料回傳 NULL。

      刪除
    2. 恩......又突然好了...
      但是原本field3正常,但是現在又出現null
      https://api.thingspeak.com/channels/223012/fields/3.json?api_key=WE7DFPRX0WG3PZ1M&results=1

      刪除
    3. 輸入 https://api.thingspeak.com/channels/223012/fields/3.json?api_key=WE7DFPRX0WG3PZ1M 得到下面的結果,field3 就是沒有接收到資料,所以才會是 null。
      要去檢查你上傳的程式哪裡出錯,去看你的 Serial monitor 輸出是什麼,對比一下在什麼情況之下會上傳 null。

      {"channel":{"id":223012,"name":"藥盒提醒","description":"早上的藥盒\r\n","latitude":"0.0","longitude":"0.0","field1":"morn","field2":"noon","field3":"eve","field4":"bed","created_at":"2017-02-04T15:10:42Z","updated_at":"2017-02-28T15:07:48Z","last_entry_id":33},"feeds":[{"created_at":"2017-02-04T15:14:54Z","entry_id":1,"field3":"no"},{"created_at":"2017-02-04T15:16:21Z","entry_id":2,"field3":"no"},{"created_at":"2017-02-04T15:17:27Z","entry_id":3,"field3":"2"},{"created_at":"2017-02-04T15:19:06Z","entry_id":4,"field3":"0"},{"created_at":"2017-02-04T15:20:05Z","entry_id":5,"field3":"0"},{"created_at":"2017-02-13T14:38:07Z","entry_id":6,"field3":null},{"created_at":"2017-02-13T14:39:21Z","entry_id":7,"field3":null},{"created_at":"2017-02-25T13:20:35Z","entry_id":13,"field3":null},{"created_at":"2017-02-
      // ... 中間省略 ...
      27T15:09:31Z","entry_id":14,"field3":"2"},{"created_at":"2017-02-28T05:22:48Z","entry_id":15,"field3":null},{"created_at":"2017-02-28T05:23:32Z","entry_id":16,"field3":null},{"created_at":"2017-02-28T05:24:19Z","entry_id":17,"field3":"1"},{"created_at":"2017-02-28T05:24:40Z","entry_id":18,"field3":null},{"created_at":"2017-02-28T05:25:15Z","entry_id":19,"field3":"1"},{"created_at":"2017-02-28T05:25:31Z","entry_id":20,"field3":null},{"created_at":"2017-02-28T05:26:33Z","entry_id":21,"field3":null},{"created_at":"2017-02-
      // ... 中間省略 ...
      28T28T12:18:31Z","entry_id":29,"field3":null},{"created_at":"2017-02-28T13:25:25Z","entry_id":30,"field3":null},{"created_at":"2017-02-28T13:25:41Z","entry_id":31,"field3":null},{"created_at":"2017-02-28T13:26:33Z","entry_id":32,"field3":null},{"created_at":"2017-02-28T15:07:48Z","entry_id":33,"field3":null}]}

      刪除