從Arduino透過POST方法傳遞JSON資料給Node.js伺服器程式的補充說明

本文旨在補充《超圖解物聯網IoT實作入門》第3章「接收與處理POST資料」一節,說明從Arduino控制板以POST方法傳送JSON資料給Node.js網站伺服器解析的程式寫法。

使用ESP8266控制板,經由Wi-Fi無線網路以POST方法傳遞JSON資料的語法,和本文不同,實作方式請參閱《超圖解物聯網IoT實作入門》第12章「使用POST方法傳遞JSON資料」一節(12-44頁)。

若要使用GET方法,從Arduino傳送JSON資料給Node.js網站伺服器程式,請參閱這一篇留言

單純使用Arduino解析或包裝JSON資料,可採用Arduino JSON程式庫,相關說明請參閱這一篇留言

使用POST方法傳送資料給網站伺服器

假設用戶端(瀏覽器)傳遞一個「點餐」的表單資料給swf.com.tw網站,而處理此表單資料的伺服器端程式叫做“order.js”:

從瀏覽器以POST方法傳送表單資料

在背地裡,瀏覽器將傳遞如下的HTTP訊息和資料給網站伺服器(註:實際的HTTP訊息通常更複雜):

POST請求的HTTP協議內容

透過HTML表單網頁傳遞資料時,我們的程式不用理會背後的HTTP訊息,瀏覽器會搞定一切。但若採用「Arduino程式」取代「瀏覽器」,直接從Arduino程式傳遞資料,那麼,與網站伺服器通訊的HTTP訊息,就必須寫在Arduino程式裡面。

從Arduino以POST方式傳送JSON資料

假設要透過Arduino乙太網路程式,以POST方法傳遞JSON格式的溫濕度資料給位於192.168.1.25的網站伺服器,HTTP訊息可以寫成:

以POST方法傳遞JSON格式的溫濕度資料的HTTP訊息

對應的Arduino乙太網路程式如下(client是EthernetClient物件):

Arduino乙太網路POST程式

傳遞POST命令的HTTP協議,只有下列資訊是必要的:

  • 下達命令的方法敘述(POST)
  • 內容類型
  • 內容長度
  • 資料

因此,上面的HTTP訊息可簡短地寫成這樣:

client.println("POST /temp HTTP/1.1");
client.println("Content-Type: application/json");
client.print("Content-Length: ");
client.println(jsonStr.length());
client.println();
client.print(jsonStr);

以《超圖解物聯網IoT實作入門》第3章的DHT11Client_2.ino檔為例,讀者可以把其中的loop()和httpSend()函式改成:

String jsonStr = "{\"temp\":23.4,\"humid\":56.7}";  // 定義JSON字串

void loop() {
  if (millis() - past > interval) {
    httpSend();   // 每隔5秒發送一次JSON資料
  }
}

void httpSend() {
  client.stop();

  // 連線到指定伺服器的5438埠號
  if (client.connect(server, 5438)) {
   Serial.println("connected");
   // 開始傳送JSON資料
    client.println("POST /temp HTTP/1.1");
    client.println("Content-Type: application/json");
    client.print("Content-Length: ");
    client.println(jsonStr.length());
    client.println();
   client.print(jsonStr);
   
    past = millis();
  } else {
    Serial.println("connection failed");
  }
}

接收並解析經POST方法傳送的JSON資料的Node.js程式

超圖解物聯網IoT實作入門》第3章的post.js程式(位於form資料夾)已經包含引用處理POST資料的body-parser模組,因此底下的程式以它為藍本,請在post.js裡的處理「其他所有資源請求」的敘述(亦即,app.get(‘*’))之前,加入底下的程式片段:

app.use(bodyParser.json());       // 解析POST參數的JSON資料

app.post('/temp',function(req,res){   // JSON資料的處理程式
  var json=req.body;   // 取出POST資料本體

  console.log("溫度:" + json.temp);   // 在控制台顯示溫度值
  console.log("濕度:" + json.humid);  // 顯示濕度值
});

請先執行此post.js程式,再啟動Arduino板,即可在命令列(終端機)視窗看到類似下圖的結果:

執行Node.js程式接收與解析JSON

Posts created 467

17 thoughts on “從Arduino透過POST方法傳遞JSON資料給Node.js伺服器程式的補充說明

  1. 你好,想請問一下如果傳遞圖檔也能用 JSON檔嗎?
    第一次使用不太熟悉ESP8266 ˊˇˋ

    1. hi chi:

      JSON是一種描述資訊的語法,常用於網路訊息交換,跟電腦系統或微控器沒有關聯。JSON和書本2-20頁提到的CSV與XML格式一樣,都是文字格式。

      我們通常不會在JSON或XML裡面夾帶圖檔,而是在訊息中描述圖檔所在位置,就像HTML網頁透過底下的標籤指令,告訴瀏覽器到伺服器的”/img”路徑下載”pict.png”圖檔,而不是把圖檔直接包夾在HTML裡面。

      <img src=”/img/pict.png”>

      如果要夾帶圖檔,必須先把圖像轉換成文字編碼(Base64編碼),才能附加到文字訊息當中。

      thanks,
      jeffrey

  2. 趙老師您好

    看了超圖解IOT第三章的溫濕度傳值的實驗,同一個區網下,Arduino傳了一筆溫濕度,並在電腦執行node來請問如果我想蒐集某一段時間內多筆溫溼度資料該怎麼做呢?
    因為我希望能把Arduino拿到戶外感測並儲存,我想過用SPI模組存到SD卡,或是直接EEPROM存,但是當回到這個區網後該怎麼讓大量資料經由WiFi傳給電腦,然後我想再用第8章ejs樣板的方法來表列這些資料

    麻煩老師提示一下

    1. 因為整個應用環節,除了從Arduino傳遞資料给電腦之外,並沒有使用到網路功能,我覺得乾脆用序列方式傳遞資料比較省事。設置一個按鈕,當Arduino接上電腦USB時,按下它,開始執行底下的程式:

      void readSD() {
        File dataFile = SD.open("data.log");
        
        if (dataFile) {
          // 從序列埠輸出data.log內容
          while (dataFile.available()) {
            Serial.write(dataFile.read());
          }
          dataFile.close();
        } else {
          Serial.println("error opening data.log");
        }
      }
      

      thanks,
      jeffrey

  3. 老師 您好:

    不好意思上面的問題我已經測試出來了,只是我之後想把產生的字串從Arduino傳到NODE端那應該怎麼去修改?

  4. 老師 您好:

    不好意思,沒有表達清楚,我只是測試以JSON格式傳入NODE,我現在想傳輸將陣列轉成的字串,程式如下
    Arduino程式
    int data[] = {123,456,789}; //假設的陣列
    String str = “”;
    byte leng = sizeof(data)/sizeof(int);
    for (byte i=0;i<leng;i++) {
    str = str + data[i] + \',\';
    }
    Arduino程式
    String jsonStr =”{\”temp\”:23.4,\”humid\”:56.7}”; //定義字串
    if (client.connect(server, 5438)) {
    Serial.write(myFile.read());

    client.println(“POST /temp HTTP/1.1”);
    client.println(“Content-Type: application/json”);
    client.print(“Content-Length: “);
    client.println(str.length());
    client.println();
    client.print(str);
    past = micros();
    }
    NODE端程式
    var express = require(“express”);
    var bodyParser = require(“body-parser”);
    var app = express();

    app.get(‘/’, function(req, res) {
    res.sendFile(__dirname + ‘/www/index.html’)
    });

    app.post(“/temp”,function(req,res){ // JSON資料的處理程式

    var str=req.body; // 取出POST資料本體

    console.log(“字串:” +str); // 在控制台顯示溫度值

    });

    app.get(‘*’, function(req, res) {
    res.status(404);
    res.send(‘找不到網頁!’);
    });

    app.listen(5438, function(req, res) {
    console.log(“網站伺服器在5438埠口開工了!”);
    });
    我把app.use(bodyParser.json()); // 解析POST參數的JSON資料
    //app.use(bodyParser.urlencoded({ extended: true })); 移除得到結果為undefined請問如果是要傳陣列轉成的字串該怎麼修改內容

    1. 因為Arduino送出的內容類型不是JSON,所以content-type要改成上文第2張圖裡的application/x-www-form-urlencoded。送出的資料也要改成「參數=值」的形式,如同3-39頁的表單頁面,以及3-40頁的Node程式碼。

      thanks,
      jeffrey

  5. 老師 您好:

    參考3-39及3-40的內容,目前已經可以傳輸長度短的字串資料,但如果傳輸2000以上的資料長度就會發生request aborted的問題,但根據3-38頁表示POST上傳資料大於2MB,是因為程式有哪部分的資料未定義清楚嗎?
    Arduino端程式
    client.println(“POST /temp HTTP/1.1”);
    client.println(“Content-Type: application/x-www-form-urlencoded”);
    client.print(“Content-Length: “);
    client.println(jsonStr.length());
    client.println();
    client.print(jsonStr);

    1. POST上傳資料可大於2MB指的是使用電腦架設網站伺服器,或者使用電腦、手機、平板等裝置上傳資料的情況。上傳的資料會先暫存在記憶體,而Arduino Uno採用用的ATmega328微控器的SRAM才2KB。如果你有大量資料傳輸的需求,建議使用樹莓派之類的高階微控板。

      MQTT是另一個例子,它的規格制定訊息本體最大允許256MB,而Arduino的PubSubClient程式庫預設只給了128位元組,看似很少,但已足夠絕大多數物聯網裝置使用。

      舉個比較生活化的例子,一般汽車上的速度儀表都可以顯示到時速180Km以上,可是在台灣,道路的最高速限應該在120Km吧。如果需要更快速到達目的,就要更換交通工具,像高鐵或飛機。

      thanks,
      jeffrey

  6. 老師 您好:

    謝謝您的建議,目前在MCU的部分採用的是Arduino Due,規格SRAM有96KB,因此硬體應該還夠用,至於您提的另外一個例子關於程式庫的部分,意思是說程式庫的部分已經限制傳輸的資料量,所以無法進行傳輸,那這部分有解決的辦法嗎?

    1. 不是,我的意思是主記憶體容量不足,另一個程式庫是MQTT用的,請當作我沒提~

      記憶體不夠用,應該要回頭檢討使用情境和控制板的選用,是否累積太多資料?是否要調整資料的傳送頻率?是否改用其他資料傳輸方式?不然的話,應該只能分批多次傳送一途了。

      thanks,
      jeffrey

  7. 老师,我用3d扫描得知物体的体积以及质量,此3d扫描仪是把数据存储为文本的,那么我的Arduino怎样才能得到这个数据? 急用 望老师帮我回答

    1. 我猜想那个3D扫描仪的文档是STL格式,你可以用Windows 10自带的3D Builder开启它,或者将它上传到TinkerCAD网站,也能看到3D成像。

      thanks,
      jeffrey

  8. 請問能不能用line機器人那篇的方法
    將本篇的nodejs程式部署到heroku,再對那個webhook用post方法呢?

    1. 只要你的node.js程式有處理post的路由即可,跟佈署在哪裡無關。

      thanks,
      jeffrey

  9. 請教一下
    我是看網路youtube 要使用LINE Notify 發送通知
    他是直接POST LINE Notify API
    也有提供下載 我直接套用 卻無法使用
    https://www.youtube.com/watch?v=mzz_zJo_vOg&t=75s

    void Line_Notify1(String message) {
    WiFiClientSecure client;
    if (!client.connect(“notify-api.line.me”, 443)) {
    Serial.println(“connection failed”);
    return;
    }
    String req = “”;
    req += “POST /api/notify HTTP/1.1\r\n”;
    req += “Host: notify-api.line.me\r\n”;
    req += “Authorization: Bearer ” + String(LINE_TOKEN_PIR) + “\r\n”;
    req += “Cache-Control: no-cache\r\n”;
    req += “User-Agent: ESP8266\r\n”;
    req += “Content-Type: application/x-www-form-urlencoded\r\n”;
    req += “Content-Length: ” + String(String(“message=” + message1).length()) + “\r\n”;
    req += “\r\n”;
    req += “message=” + message1;
    // Serial.println(req);
    client.print(req);

    delay(20);

    好像都是卡在這裡
    if (!client.connect(“notify-api.line.me”, 443)) {
    Serial.println(“connection failed”);
    return;

    有高手可以解答一下嗎?

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top