本文旨在補充《超圖解物聯網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”:
在背地裡,瀏覽器將傳遞如下的HTTP訊息和資料給網站伺服器(註:實際的HTTP訊息通常更複雜):
透過HTML表單網頁傳遞資料時,我們的程式不用理會背後的HTTP訊息,瀏覽器會搞定一切。但若採用「Arduino程式」取代「瀏覽器」,直接從Arduino程式傳遞資料,那麼,與網站伺服器通訊的HTTP訊息,就必須寫在Arduino程式裡面。
從Arduino以POST方式傳送JSON資料
假設要透過Arduino乙太網路程式,以POST方法傳遞JSON格式的溫濕度資料給位於192.168.1.25的網站伺服器,HTTP訊息可以寫成:
對應的Arduino乙太網路程式如下(client是EthernetClient物件):
傳遞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板,即可在命令列(終端機)視窗看到類似下圖的結果:
你好,想請問一下如果傳遞圖檔也能用 JSON檔嗎?
第一次使用不太熟悉ESP8266 ˊˇˋ
hi chi:
JSON是一種描述資訊的語法,常用於網路訊息交換,跟電腦系統或微控器沒有關聯。JSON和書本2-20頁提到的CSV與XML格式一樣,都是文字格式。
我們通常不會在JSON或XML裡面夾帶圖檔,而是在訊息中描述圖檔所在位置,就像HTML網頁透過底下的標籤指令,告訴瀏覽器到伺服器的”/img”路徑下載”pict.png”圖檔,而不是把圖檔直接包夾在HTML裡面。
<img src=”/img/pict.png”>
如果要夾帶圖檔,必須先把圖像轉換成文字編碼(Base64編碼),才能附加到文字訊息當中。
thanks,
jeffrey
趙老師您好
看了超圖解IOT第三章的溫濕度傳值的實驗,同一個區網下,Arduino傳了一筆溫濕度,並在電腦執行node來請問如果我想蒐集某一段時間內多筆溫溼度資料該怎麼做呢?
因為我希望能把Arduino拿到戶外感測並儲存,我想過用SPI模組存到SD卡,或是直接EEPROM存,但是當回到這個區網後該怎麼讓大量資料經由WiFi傳給電腦,然後我想再用第8章ejs樣板的方法來表列這些資料
麻煩老師提示一下
因為整個應用環節,除了從Arduino傳遞資料给電腦之外,並沒有使用到網路功能,我覺得乾脆用序列方式傳遞資料比較省事。設置一個按鈕,當Arduino接上電腦USB時,按下它,開始執行底下的程式:
thanks,
jeffrey
老師 您好:
不好意思上面的問題我已經測試出來了,只是我之後想把產生的字串從Arduino傳到NODE端那應該怎麼去修改?
不了解你的意思,你的程式不是已經把資料傳入Node了嗎?
thanks,
jeffrey
老師 您好:
不好意思,沒有表達清楚,我只是測試以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請問如果是要傳陣列轉成的字串該怎麼修改內容
因為Arduino送出的內容類型不是JSON,所以content-type要改成上文第2張圖裡的application/x-www-form-urlencoded。送出的資料也要改成「參數=值」的形式,如同3-39頁的表單頁面,以及3-40頁的Node程式碼。
thanks,
jeffrey
老師 您好:
參考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);
POST上傳資料可大於2MB指的是使用電腦架設網站伺服器,或者使用電腦、手機、平板等裝置上傳資料的情況。上傳的資料會先暫存在記憶體,而Arduino Uno採用用的ATmega328微控器的SRAM才2KB。如果你有大量資料傳輸的需求,建議使用樹莓派之類的高階微控板。
MQTT是另一個例子,它的規格制定訊息本體最大允許256MB,而Arduino的PubSubClient程式庫預設只給了128位元組,看似很少,但已足夠絕大多數物聯網裝置使用。
舉個比較生活化的例子,一般汽車上的速度儀表都可以顯示到時速180Km以上,可是在台灣,道路的最高速限應該在120Km吧。如果需要更快速到達目的,就要更換交通工具,像高鐵或飛機。
thanks,
jeffrey
老師 您好:
謝謝您的建議,目前在MCU的部分採用的是Arduino Due,規格SRAM有96KB,因此硬體應該還夠用,至於您提的另外一個例子關於程式庫的部分,意思是說程式庫的部分已經限制傳輸的資料量,所以無法進行傳輸,那這部分有解決的辦法嗎?
不是,我的意思是主記憶體容量不足,另一個程式庫是MQTT用的,請當作我沒提~
記憶體不夠用,應該要回頭檢討使用情境和控制板的選用,是否累積太多資料?是否要調整資料的傳送頻率?是否改用其他資料傳輸方式?不然的話,應該只能分批多次傳送一途了。
thanks,
jeffrey
老师,我用3d扫描得知物体的体积以及质量,此3d扫描仪是把数据存储为文本的,那么我的Arduino怎样才能得到这个数据? 急用 望老师帮我回答
我猜想那个3D扫描仪的文档是STL格式,你可以用Windows 10自带的3D Builder开启它,或者将它上传到TinkerCAD网站,也能看到3D成像。
thanks,
jeffrey
請問能不能用line機器人那篇的方法
將本篇的nodejs程式部署到heroku,再對那個webhook用post方法呢?
只要你的node.js程式有處理post的路由即可,跟佈署在哪裡無關。
thanks,
jeffrey
請教一下
我是看網路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;
有高手可以解答一下嗎?