《超圖解物聯網IoT實作入門:使用JavaScript/Node.JS/Arduino/Raspberry Pi/ESP8266/Espruino》零件清單

本文列舉《超圖解物聯網IoT實作入門》書本所使用的電子零組件。底下是假設採用麵包板組裝,重複使用零件所需要的最少數量。相較於《超圖解Arduino互動設計入門》,本書更著重於程式設計,所以電子零件比較少而且盡量使用現成的模組,但微電腦控制板的款式比較多樣。

SoC系統晶片 VS MCU微控制器

全部零件清單

微電腦控制板

Arduino UNO控制板 1片
Raspberry Pi(以下統稱「樹莓派」 1片,建議採用第2代或更新版。
Espruino控制板(或採用STM32F103RCT6微控器的控制板) 1片
ESP-8266 ESP-01型控制板(1MB快閃記憶體) 1片
採用ESP-12E模組(4MB快閃記憶體)的NodeMCU控制板 1片

電子模組和擴展板(shield)

Arduino Ethernet乙太網路擴展板(採用W5100晶片) 1片
DHT11溫濕度感測模組 1片
樹莓派專用相機模組 1片
PIR人體紅外線感測器模組 1片
MAX7219 LED矩陣模組 1片
5V轉3.3V電源轉換模組 1片
超音波感測器模組 1片
HC-05或HC-06藍牙序列通訊板 1片
L298N直流馬達驅動板 1片
I2C介面的OLED顯示器(2.5吋) 1片
SD或MicroSD記憶卡插座模組或SD轉接卡 1片

其他電子零組件

麵包板 1塊,建議購買165 mm x 55 mm (2.2″ x 7″)、830孔的規格。
伺服馬達(型號:SG90) 2個,書本採用9g微型款式
直流馬達(FA-130或RE-140) 2個,建議直接購買包含變速齒輪的動力機械模型玩具組合或小車底盤。
開關(樣式不拘) 1個
LM35溫度感測器 1個
2N7000(N通道MOSFET電晶體) 1個
LED 3個,顏色不拘,但最好分成三種顏色。
光敏電阻 1個
10KΩ可變電阻 1個
220Ω(紅紅棕) 2個
330Ω(橙橙棕)電阻 3個
680Ω(藍灰棕)電阻 1個
2.2KΩ(紅紅紅)電阻 2個
4.7KΩ(黃紫紅)電阻 1個
10KΩ(棕黑紅)電阻 1個
0.1µF 電容 2個

按章節劃分的零件清單

底下是各章節的零件清單,讀者可依照自己感興趣的單元,逐一添購零件。

第2章:從Arduino輸出純文字溫度、輸出JSON訊息

Arduino UNO控制板 1片
Arduino Ethernet乙太網路擴展板(採用W5100晶片) 1片
DHT11溫濕度感測模組 1個

第2章:調整燈光亮度的網頁介面

Arduino UNO控制板 1片
Arduino Ethernet乙太網路擴展板(採用W5100晶片) 1片
LED(顏色不拘) 1個
220Ω(紅紅棕)~680Ω(藍灰棕) 1個

第3章:從Arduino傳遞溫溼度值給Node網站

實驗材料與電路皆與2章「從Arduino輸出純文字溫度」單元相同。

Arduino UNO控制板 1片
Arduino Ethernet乙太網路擴展板(採用W5100晶片) 1片
DHT11溫濕度感測模組 1個

第4章:Node.js序列埠通訊

Arduino UNO控制板 1片
電腦或者樹莓派控制板 1片

第4章:樹莓派GPIO整合Arduino控制板

若要依照書本4-36頁的電路,自己DIY一個連結樹莓派的Arduino板(相關圖文請參閱Raspberry PI 2 + 自製Arduino + Motorola Lapdock 小改造),請參閱《圖解Arduino互動設計入門》書本附錄B

Arduino UNO控制板(或Arduino Mini) 1片
樹莓派控制板 1片
4.7KΩ(黃紫紅)電阻 1個

第4章:透過紅外線感測模組拍攝照片

樹莓派控制板 1片
PIR人體紅外線感測器模組 1個
樹莓派專用相機模組 1個

第4章:MOSFET電晶體實驗

2N7000 1個
LED(顏色不拘) 1個
10KΩ(棕黑橙)電阻 1個
680Ω(藍灰棕)電阻 1個

第5章:用霹靂五號指揮Arduino閃爍LED

Arduino UNO控制板 1片

第5章:啟用上拉電阻並讀取開關訊號

Arduino UNO控制板 1片
開關(樣式不拘) 1個

第5章:類比輸入與PWM輸出程式實驗

Arduino UNO控制板 1片
LED(顏色不拘) 1個
10KΩ可變電阻 1個

第5章:檢測溫度

Arduino UNO控制板 1片
LM35溫度感測器 1個

第5章:控制伺服馬達

Arduino UNO控制板 1片
伺服馬達(書本採用9g微型款式) 2個

第5章:控制LED矩陣顯示圖像

Arduino UNO控制板 1片
MAX7219 LED矩陣模組 1個

第6章:雲端蒐證:拍照自動寄送e-mail

實驗材料與電路皆與4章「透過紅外線感測模組拍攝照片」實驗相同。

樹莓派控制板 1片
PIR人體紅外線感測器模組 1個
樹莓派專用相機模組 1個

第6章:臉孔偵測與伺服馬達連動

Arduino UNO控制板 1片
具備攝影機的電腦(可用USB攝影機) 1個
伺服馬達(書本採用9g微型款式) 2個

第7章:使用光敏電阻製作小夜燈

Espruino控制板(或相容板) 1片
光敏電阻 1個
10KΩ(棕黑橙)電阻 1個

第7章:呼吸燈效果

Espruino控制板(或相容板) 1片
330Ω(橙橙棕)電阻 1個
LED(顏色不拘) 1個

第7章:超音波控制燈光亮度

Espruino控制板(或相容板) 1片
330Ω(橙橙棕)電阻 1個
LED(顏色不拘) 1個
超音波感測器模組 1個

第7章:藍牙控制LED

Espruino控制板(或相容板) 1片
330Ω(橙橙棕)電阻 3個
LED(顏色不拘) 3個
HC-05或HC-06藍牙序列通訊板 1個

第7章:藍牙遙控車(馬達控制)

Espruino控制板(或相容板) 1片
L298N直流馬達驅動板 1個
直流馬達 2個
0.1µF 電容 2個
HC-05或HC-06藍牙序列通訊板 1個

第7章:利用SD記憶卡紀錄溫濕度變化

Espruino控制板(或相容板) 1片
DHT11溫濕度感測器 1個
SD或MicroSD記憶卡插座模組或SD轉接卡 1個
330Ω(橙橙棕)電阻 2個
LED(顏色不拘) 2個

第7章:使用Espruino控制伺服馬達

Espruino控制板(或相容板) 1片
光敏電阻 1個
SD或MicroSD記憶卡插座模組或SD轉接卡 1個
10KΩ(棕黑橙)電阻 1個
伺服馬達(書本採用9g微型款式) 1個

第7章:替STM32控制板燒錄Espruino韌體

採用STM32F103RCT6微控器的控制板 1片
USB轉TTL序列轉換板(或者Arduino UNO板) 1片

第8章:儲存Arduino上傳的溫溼資料

實驗材料與電路皆與3章「從Arduino傳遞溫溼度值給Node網站」實驗相同。

Arduino UNO控制板 1片
Arduino Ethernet乙太網路擴展板(採用W5100晶片) 1片
DHT11溫濕度感測模組 1個

第9章:使用圖表動態顯示感測器數據

Arduino UNO控制板 1片
光敏電阻 1個
10KΩ(棕黑橙)電阻 1個

第10章:手機App網路控制Arduino

Arduino UNO控制板 1片
Arduino Ethernet乙太網路擴展板(採用W5100晶片) 1片
220Ω(紅紅棕)~680Ω(藍灰棕)電阻 1個
LED(顏色不拘) 1個

第11章:手機藍牙App控制LED開關

Arduino UNO控制板 1片
藍牙序列通訊模組(HC-05或HC-06) 1片
2.2KΩ(紅紅紅)電阻 1個

第11章:手機加速度感測器控制伺服馬達

Arduino UNO控制板 1片
藍牙序列通訊模組(HC-05或HC-06) 1片
2.2KΩ(紅紅紅)電阻 1個
伺服馬達(書本採用9g微型款式) 2個

第11章:透過手機USB介面連接Arduino板

支援USB OTG介面的Android手機 1支
Micro USB OTB轉接線 1條
Arduino UNO控制板 1片
光敏電阻 1個
10KΩ(棕黑紅)電阻 1個

第12章:透過ESP-01的AT命令建立HTTP伺服器

Arduino UNO控制板 1片
ESP-01模組(快閃記憶體1MB) 1個
5V轉3.3V電源轉換模組 1個

第12章:使用ESP8266WiFi程式庫連接無線網路、上傳資料

NodeMCU控制板(採用ESP-12E模組,內建4MB快閃記憶體) 1片

第13章:使用ESP-01模組開發Arduino物聯網

Arduino UNO控制板 1片
ESP-01模組(快閃記憶體1MB) 1個
5V轉3.3V電源轉換模組 1個
220Ω(紅紅棕)電阻 2個
2.2KΩ(紅紅紅)電阻 2個
LED(顏色不拘) 2個

第13章:使用OLED顯示器呈現IP位址和溫濕度值

NodeMCU控制板 1片
ESP-01模組(快閃記憶體1MB) 1個
I2C介面的OLED顯示器 1個
DHT11溫濕度感測模組 1個
Posts created 467

102 thoughts on “《超圖解物聯網IoT實作入門:使用JavaScript/Node.JS/Arduino/Raspberry Pi/ESP8266/Espruino》零件清單

  1. 赵老师:《超圖解物聯網IoT實作入門》一书大陆简体版有卖吗? 繁体版在哪里可以买到?我在台湾岛对岸。

    1. 赵老师:上述您说的台湾购物网站我在大陆这边好像都无法访问到,有什么其他途径可以买到此书吗?

    2. 蛤……這些購物網站也都被數位長城檔下?香港也不行嗎?

      這……或許要翻牆吧~

      thanks,
      jeffrey

  2. 老師你好
    我買超圖解arduino互動設計入門第2版,在執行第16章使用Webduino程式庫建立微型網站在執行diy16_01.ino時, 在程式compile時都出現P(homePage)中homePage未宣告錯誤訊息,並在diy16_02.ino及在diy16_1_1.ino都出現類似P(htmlHead)中htmlHead未宣告錯誤訊息此問題請教老師

  3. 期待趙老師簡體版《超圖解物聯網iot實作入門》能儘快上市,這對大陸讀者是福音哦。此前的《完美圖解arduino互動設計入門》一書很是喜歡。

  4. 趙老師你好:
    在《超圖解物聯網IoT實作入門:使用JavaScript/Node.JS/Arduino/Raspberry Pi/ESP8266/Espruino》書中的 2-37頁,瀏覽器用戶端傳送json資料給Arduino伺服器端,
    書中的3-45頁,你使用查詢字串的方式將Arduino用戶端傳送資料給Node.js伺服器端,請問
    Arduino用戶端可以傳送json資料給Node.js伺服器端嗎? 程式碼該如何改呢?

    1. hi alex:

      當然可以,如同2-24頁「Arduino輸出JSON訊息」一節提到的,你只要把JSON資料當成字串輸出即可,也就是把3-46頁的client.println()內容,改成JSON字串即可。

      thanks,
      jeffrey

  5. 趙老師您好:
    3-46頁的Arduino是用戶端的角色,利用client.println()是可以輸出json字串,如下

    client.print(“{\”t\”:”);
    client.print(temp);
    client.print(“,\”h\”:”);
    client.print(hum);
    client.print(“}\n”);

    可是Node.js要如何接收呢?(因為如果Node.js用3-47頁用express模組的方式,似乎是要有URL

    如果改成client.println(“GET /th?{\”t”\:temp, \”h\”:hum}”);
    似乎Node.js無法解析

    1. hi alex:

      前端一定要知道伺服器端的位址,才能與它相連。假設前端採用GET方式,每隔5秒送出 data={“temp”:22.3,”humid”:58.6} 這樣的資料,請把DHT11Client_2.ino檔裡的loop()和httpSend()改成:

      void loop() {
       if (millis() - past > interval) {
           httpSend();
        }
      }
      
      void httpSend() {
        client.stop();
      
        // 連線到指定伺服器的5438埠號
        if (client.connect(server, 5438)) {
          Serial.println("connected");
          // 發送HTTP請求
          // client.println("GET /t?data={\"temp\":23.4,\"humid\":56.7} HTTP/1.1");
          // 底下3行等同於上面一行
          client << "GET /t?data={\"temp\":" << 23.4
                 << ",\"humid\":" << 56.7
                 << "} HTTP/1.1\r\n";  // 最後記得加上"\r\n"和分號結尾
          client.println();
         
          past = millis();
        } else {
          Serial.println("connection failed");
        }
      }
      

      Node.js網站伺服器端的程式碼,請加入處理”/t”請求的get()函式,它將在控制台顯示接收到的溫溼度值:

      app.get("/t", function(req, res) {
        var json=JSON.parse(req.query.data);  // 解析JSON字串資料
      
        console.log("溫度:" + json.temp);  // 顯示23.4
        console.log("濕度:" + json.humid); // 顯示56.7
      });
      

      thanks,
      jeffrey

  6. 趙老師您好:
    我按照書上2-30頁的diy2_3.ino 和 2-38頁的slide_1_ajax.html 改成Arduino Yun的版本如下

    Arduino Yun的程式

    #include //加入Bridge.h程式庫
    #include //加入YunServer.h程式庫
    #include //加入YunClient.h程式庫
    #include

    float temp;
    float hum;
    DHT dht(2,DHT11);
    YunServer server; //Yun的網路伺服器監聽預設在port 5555將會發送所有HTTP指令給我們

    void setup() { // 建立初始化區塊
    Bridge.begin(); // 初始化Bridge,連接ATmega和AR9331
    server.listenOnLocalhost(); // 只監聽連接本地主機的指令,不能連接其他額外的網路
    server.begin();
    }
    void loop() {
    YunClient client = server.accept(); // 從伺服器取得Client
    if (client) { // 是否是新client?
    process(client); // 如果是的話,跳入process副程式處理指令
    client.stop(); // 關閉連接並釋放資源
    }
    delay(50); // 每隔50毫秒輪詢一次
    }
    void process(YunClient client) {
    String command = client.readStringUntil(‘/’); // 讀取指令,讀取指令的字串直到遇到 / 符號

    client.println(“Status: 200”);
    client.println(“Access-Control-Allow-Origin: *”);
    client.println(“Access-Control-Allow-Methods: GET”);
    client.println(“Content-Type: text/html”);
    client.println(“Connection: close”);
    client.println();
    if (command == “pwm”) {
    getCommand(client); // 是的話執行getCommand副程式
    }
    }
    void getCommand(YunClient client) {
    int pin, value;
    pin=client.parseInt();
    pinMode(pin, OUTPUT);
    if(client.read()==’/’) {
    value=client.parseInt();
    analogWrite(pin, value);
    }
    client.print(“{\”pin\”:”);
    client.print(pin);
    client.print(“,\”value\”:”);
    client.print(value);
    client.print(“}”);
    }

    前端瀏覽器的程式

    滑動燈光控制器

    #swatch {
    width: 120px;
    height: 100px;
    margin: 15px;
    background-color:#808080;
    }
    #slider {

    width: 300px;
    margin: 15px;
    }

    LED亮度

    請按F12鍵,從控制台查看輸出亮度值

    var light = 127;

    function refreshSwatch() {
    light = $(“#slider”).slider(“value”);

    var str = light + ‘,’ + light + ‘,’ + light;
    $( “#swatch” ).css( “background-color”, “rgb(” + str + ‘)’);
    }
    function upload() {
    $.ajax({
    method: “GET”,
    url: “http://192.168.43.107/arduino/pwm/9/” + light, // 請將此位址改成你的Arduino位址
    dataType: “json”,
    success: function(data) {
    console.log(“arduino傳回腳位:” + data.pin + “, 亮度:” + data.value);
    }
    });
    console.log(“傳送亮度值:” + light);
    }
    $(function() {
    $( “#slider” ).slider({
    orientation: “horizontal”,
    range: “min”,
    max: 255,
    value: 127,
    slide: refreshSwatch,
    change: upload
    });
    });

    上面的程式可以運作且是用REST的方式去解析,想請問趙老師Arduino Yun是否可以像書上2-30頁的diy2_3.ino 和 2-38頁的slide_1_ajax.html 改成瀏覽器傳送json資料給Arduino Yun,Arduino Yun也可以解析json資料控制LED的明亮呢? 謝謝趙老師的幫忙

    1. hi alex:

      Benoit Blanchon先生開發的Arduino JSON程式庫,能在Arduino上解析或者包裝(encode)JSON資料。

      使用此程式庫解析JSON資料,有三道主要敘述:

      1. 宣告暫存解析後的JSON資料的記憶體空間(此例為200位元組):

      StaticJsonBuffer<200> jsonBuffer;

      2. 假設實際的JSON字串存在json變數,底下敘述裡的root將參照到解析後的JSON資料:

      JsonObject& root = jsonBuffer.parseObject(json);

      3. 透過root取出JSON的實際資料:

      long temp = root[“屬性名稱”];

      底下是Arduino JSON程式庫提供的”JsonParserExample”,我將裡面的註解翻譯成中文提供你參考:

      #include <ArduinoJson.h>
      
      void setup() {
        Serial.begin(9600);
        while (!Serial) {
          // 等待序列埠完成初始化
        }
      
        // 替JSON資料設置暫存記憶體區域
        //
        // 在角括號(<和>)裡的200代表暫存記憶體容量為200bytes大小,
        // 處理比較複雜的JSON物件時,你需要增大此數值。
        StaticJsonBuffer<200> jsonBuffer;
      
        // StaticJsonBuffer會 stack(堆疊)規劃記憶體,
        // 這個敘述可改用在 heap(堆積)規劃記憶體的DynamicJsonBuffer指令,
        // 後者比較簡單,但是效率比較差:
        //
        // DynamicJsonBuffer  jsonBuffer;
      
        // 設定JSON資料字串
        //
        // 最好像底下這樣使用 char[] 來宣告字串
        // 如果你使用 const char* 或者 String,
        // ArduinoJson將得在 JsonBuffer 裡面複製一份JSON字串資料。
        char json[] =
            "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
      
        // 物件樹的根
        //
        // 參照到JsonObject,實際的資料,連同物件樹的節點,都位於JsonBuffer裡面。
        // 記憶體將在 jsonBuffer 離開有效範圍(scope)之後被回收。
        JsonObject& root = jsonBuffer.parseObject(json);
      
        // 測試解析JSON資料是否成功
        if (!root.success()) {
          Serial.println("parseObject() failed");
          return;
        }
      
        // 擷取資料
        //
        const char* sensor = root["sensor"];
        // 通常,讓編譯器自動轉換類型(implicit casts)即可,
        // 你也可以明確地指定類型: root["time"].as<long>();
        long time = root["time"];
        double latitude = root["data"][0];
        double longitude = root["data"][1];
      
        // 列出資料值
        Serial.println(sensor);
        Serial.println(time);
        Serial.println(latitude, 6);
        Serial.println(longitude, 6);
      }
      
      void loop() {
        // 這裡沒有程式碼
      }
      

      thanks,
      jeffrey

  7. 趙老師您好:

    在書中6-11頁的拍照與6-24頁的串流視訊都是用樹莓派執行外部指令的方法來執行拍照和串流視訊,
    想請問如果在Windows 10的PC上,如何使用Node.js來執行外部指令(或者Node.js可以安裝拍照的模組和串流視訊的模組)
    來操控USB camera?

    另外在書中10-3頁提到Cordova是一個Web瀏覽器核心,加上外掛程式組成的框架,走的是http協定,
    也就相當於客製化的Web瀏覽器,在11-2看到Cordova也可以裝藍芽序列部的外掛來開發APP,
    所以想請問Cordova如何安裝TCP或UDP的外掛來開發TCP或UDP的APP,也就是想請問如何用Cordova開發TCP或UDP協定(socket)
    而不走http協定(websocket)來直接和Arduino Uno+5100乙太網路擴充卡、Arduino Yun及NodeMCU等有網路的控制板
    執行資料傳輸或操控Arduino Uno+5100乙太網路擴充卡、Arduino Yun及NodeMCU等有網路的控制板

    1. hi alex:

      如同3-28頁提到的,我們可以在npmjs.com查詢到所有Node.js模組的相關資料。使用webcam關鍵字搜尋,可找到許多模組和套件,我大致瀏覽了一下,node-webcam有支援Linux和Mac OS X,但Windows因為驅動程式問題,尚不支援。

      至於透過Socket連線,請參閱5-27「使用Socket.io建立即時連線」一節說明,我近期會再更新補充文件。

      thanks,
      jeffrey

  8. 趙老師您好

    在書中10-48頁的程式如下:

    $(document).on(“pageshow”, “#ctrlPage”, pageEvt);
    function pageEvt (e) {
    $( “#pwmSlider” ).on( “slidestop”, function( e ) {
    var pwm = Math.ceil($(this).val() * 2.55);
    var pwmData = {“pin”:9, “pwm”:pwm};
    // 向第9腳送出PWM訊號
    $.ajax({
    url: “http://” + app.host + “:” + app.port + “/pwm”,
    data: pwmData,
    success: function( d ) {
    app.showMsg(“收到伺服器回應:” + d );
    }
    });
    });

    $(document).off(“pageshow”, “#ctrlPage”, pageEvt);
    }

    上列程式中的一行如下
    var pwmData = {“pin”:9, “pwm”:pwm};

    原本我以為上面的這一行是傳送json資料,可是我看到10-49頁說明,傳送的格式如下:

    http://192.168.1.19:80/pwm?pin=9&pwm=128 (查詢字串的方法)

    所以想請問如果真的要傳送json格式給(Arduino Uno+W5100)的話,Arduino Uno+W5100是否可以解析下列程式的json資料呢
    按照之前趙老師的熱心解答修改上面程式如下

    $(document).on(“pageshow”, “#ctrlPage”, pageEvt);
    function pageEvt (e) {
    $( “#pwmSlider” ).on( “slidestop”, function( e ) {
    var pwm = Math.ceil($(this).val() * 2.55);
    var pwmData = {“pin”:9, “pwm”:pwm};
    // 向第9腳送出PWM訊號
    $.ajax({
    url: “http://” + app.host + “:” + app.port + “/pwm?data=” + pwmData
    success: function( d ) {
    app.showMsg(“收到伺服器回應:” + d );
    }
    });
    });

    $(document).off(“pageshow”, “#ctrlPage”, pageEvt);
    }

    1. 透過AJAX傳送資料的get(), post()和ajax()介紹,請參閱2-37頁,如該頁底下的語法說明,get()方法的資料為物件格式(你也可以說它是JSON格式),只是透過get()方法傳送的資料,都會被附加在網址後面,變成查詢字串的格式。

      至於Arduino解析JSON資料,請參閱這一篇留言說明。

      thanks,
      jeffrey

  9. 趙老師您好

    在書中3-47頁的Node.js程式中,如下所示

    // 確認有收到溫度和濕度值(兩者都不是undefined)
    if (temp != undefined && humid != undefined) {
      console.log(“溫度: ” + temp + “,濕度: ” + humid);
      res.send(“溫度: ” + temp + “°C,濕度: ” + humid +”%”);
    } else {
    console.log(“沒收到資料!”);
    }

    上面程式的其中一行如下所示,是把這行字串送出給Arduino,
    res.send(“溫度: ” + temp + “°C,濕度: ” + humid +”%”);

    可是在書中3-46頁的Arduino的程式中如下所示,並沒有去接收Node.js 用res.send送出的字串,所以想請問要如何讓Arduino去接收Node.js用res.send送出的字串呢?

    // 連線到指定伺服器的5438埠號
    if (client.connect(server, 5438)) {
    Serial.println(“connected”);
    // 發送HTTP請求
    client.println(“GET /a?pin=10&state=1 HTTP/1.1”);
    client.println();
    } else {
    // if you couldn’t make a connection:
    Serial.println(“connection failed”);
    }

    1. 因為那一節的主題是「從Arduino傳遞溫濕度值給Node網站」,所以Arduino擔任的是前端的角色。

      在Arduino前端接收Node或其他網站的回應,語法和接收Serial資料類似,請在loop()函式裡面加入:

      if (client.available()) {  // 若前端有收到資料…
        char c = client.read();  // 讀取資料
        Serial.print(c);         // 在序列埠監控視窗顯示資料
      }
      

      thanks,
      jeffrey

  10. 趙老師您好

    在書中12-29頁是使用 ESP8266WebServer.h 的程式庫去處理GET和POST的請求

    我從https://www.youtube.com/watch?v=E85RfNlRmHU
    參考了他寫的Wifi remote car 的程式http://wisdomgoody.blogspot.tw/2015/07/node-mcu-esp8266-devkit09-6.html

    其中他使用的協定程式如下所示
    #include
    //#include
    #include

    const char* ssid = “Your wifi”;
    const char* password = “Password your wifi”;
    const char* host = “IP your computer”;

    char message[255];

    unsigned int localPort = 8080;
    WiFiUDP Udp;

    他是使用UDP協定的方式去控制wifi 遙控車,至於他使用UDP協定用Arduino IDE寫好的程式,是否也能用Node.js用UDP協定去連接它,
    並用書上第10章的 Cordova去包裝成手機的APP呢?

    1. 你的需求應該就是「透過 WebSocket 處理即時通訊」(請參閱5-27頁「使用Socket.io建立即時連線」一節),WebSocket通訊協定是基於TCP,不支援UDP。你的應用一定要用UDP嗎?

      手機的Chrome瀏覽器和Cordova都有支援,我下週一之前會再補充Arduino的WeBSocket說明。

      thanks,
      jeffrey

    2. 趙老師您好:

      其實wifi遙控車這個應用來說,使用TCP、UDP或HTTP(也是TCP的一種)都可以,會想用UDP做APP的原因如下:

      1. 基於學習的理由,會想嘗試用各種不同的傳輸協定或各種不同的方法來實作APP、Arduino程式及ESP8266程式,
      甚至也想學習esp8266原本用的Lua語言來撰寫esp8266程式
      2. 以wifi遙控車的應用來說,可以藉這個應用來了解TCP(connection oriented)、UDP(Connectionless communication)、及HTTP在資料傳輸上的差異性及彼此的優缺點

  11. 趙老師您好:

    在書中12-31頁,如果用esp8266重新實作書中2-17頁傳回DHT11溫溼度值的實驗(esp8266當伺服器端),
    想請問下列幾個問題
    1. esp8266如何輸出純文字溫溼度及JSON訊息給用戶端
    2. esp8266如果輸出JSON訊息,如何加上Access-Control-Allow-Origin的標頭,以避免html檔案接收esp8266的資料所產生跨網域的問題

    如果重新實作書中3-45頁傳遞溫溼度給Node網站的實驗(esp8266當用戶端),
    想請問下列幾個問題
    1. ep8266如何傳送查詢字串給Node網站

    另外想問esp8266當網頁伺服器時,解析用戶端傳來的JSON資料也可以用Arduino JSON程式庫來解析嗎?

    1. hi alex:

      1. 請參閱12-44頁「使用POST方法傳遞JSON」資料說明,GET方法請參閱12-43頁的程式碼。
      2. 請參閱12-44頁的範例程式,透過http.addHeader()方法新增HTTP標頭
      3. 請參閱12-43頁的範例程式,把查詢字串附加在「HTTP前端物件.begin()」方法當中的「連線參數」。
      4. 根據”ArduinoJson“程式庫說明頁的描述,它支援所有Arduino系列控制板、ESP8266、Intel Edison和Galileo…等等。

      thanks,
      jeffrey

  12. 想請問老師一下Nodemcu V2.可以接馬達嗎 ? 如可以是怎樣型號的馬達
    想控制電扇(含段數)

    1. hi starry:

      需要透過電晶體或馬達驅動板連接馬達,透過PWM控制馬達的轉速。你可以採用7-29頁「藍牙遙控車(馬達控制)」單元的模組。

      直流馬達控制、電晶體元件和PWM的相關說明,《超圖解Arduino互動設計入門》第10章有較完整的介紹。

      thanks,
      jeffrey

  13. 趙老師您好:

    1. 在書中12-11頁的esp-01的RX及TX標示標錯了
    2. 在書中13-10頁的esp-01的TX標錯了,我參考了http://www.elec-cafe.com/esp8266-esp-01-firmware-update/
    它為了燒錄程式,也是esp8266的RX接Arduino的RX,esp8266的TX接Arduino的TX,不過沒接2.2K歐姆電阻也沒接
    esp-01的reset接腳,如果是這個邏輯的話,在13-12頁的ESP-12E的的RX接Arduino的RX,ESP-12E的TX接Arduino的TX,但書上的好像標錯了
    3. 在書中12-14頁的程式,應該改成ESP.write(Serial.read()) 及 Serial.write(ESP.read()), 否則會印出ASCII數字不會
    印出字元

    1. 1. 的確是畫反了…我本週會在blog上更正。
      2. 我看了你提供的頁面,我不知道為何他的RX和RX是對接的,另外,不加限流電阻,你不覺得怪怪的嗎?13-10頁的電路我會再更正,實際做實驗時,我是直接用12-12頁底下的焊接電路插麵包板。
      3. 12-14頁的序列埠輸出結果,就是用那個程式輸出的畫面,我想應該是韌體不同導致,謝謝!

      thanks,
      jeffrey

  14. 趙老師您好:

    在書中12-31頁,在程式記憶體區儲存靜態網頁並燒錄到NNodeMCU,用到以下

    如果要存取圖片的話,也可以把圖片放在google雲端硬碟,然後用URL去存取這個圖片(譬如https://drive.google.com/open?id=0B54sPA9nxlVpUlBzcXFCTnZ2Q1U

    以上方式是需要NodeMCU連上網際網路才能夠存取,如果我的NodeMCU只能在區網,不能連上網際網路,是否有辦法把以下這些檔案

    jquery-ui.css
    jquery-1.12.1.min.js
    jquery-ui.min.js
    圖片檔(xxx.jpg)

    也燒進NodeMCU的程式記憶體區,然後用瀏覽器去瀏覽網頁呢?(註: 瀏覽器的環境也無法連上網際網路)

  15. 老師您好:

    我參考書上3-13頁,改寫index.js如下
    #!/usr/bin/env node
    console.log(‘你好’);
    console.log(‘%s:%d’,’年份’, 2015);

    執行結果如下
    pi@raspberrypi:~ $ ./index.js
    : No such file or directory

    因為我node放在/usr/local/bin,所以我改寫index.js如下
    #!/usr/local/bin node
    console.log(‘你好’);
    console.log(‘%s:%d’,’年份’, 2015);

    執行結果如下
    pi@raspberrypi:~ $ ./index.js
    -bash: ./index.js: /usr/local/bin: bad interpreter: Permission denied
    pi@raspberrypi:~ $ bash ./index.js
    ./index.js: line 2: syntax error near unexpected token `’你好”
    ‘/index.js: line 2: `console.log(‘你好’);
    pi@raspberrypi:~ $ node index.js
    你好
    年份:2015

    ./index.js 和 bash ./index.js 兩種方式都無法正確執行,只有node index.js可以正確執行,所以想請教老師哪裡出了問題,感謝老師的幫忙

    1. 有些Linxu的套件資源庫(package repository)把node稱作nodejs。請先在終端機視窗執行which指令,確認Node.js可執行檔的路徑:

      which node

      或者:

      which nodejs

      取得路徑之後,再替Node.js於/usr/bin/node路徑建立一個軟連結(請參閱6-18頁說明):

      ln -s 你的node路徑 /usr/bin/node

      這樣就能依照3-13頁方法直接執行Node程式了。

      thanks,
      jeffrey

  16. 老師您好
    關於NodeMCU
    為什麼用ArduinoIDE燒韌體不需要將gpio15 gpio0接地
    其他韌體像是at 就需要這個步驟

    1. 請參閱12-9的NodeMCU上拉和下拉電阻接線,GPIO 15已連接下拉電阻;至於控制「進入一般啟動模式,或者進入燒錄狀態」的GPIO 0,有跟USB轉序列IC的DTR腳位相連,當Arduino IDE準備上傳韌體時,DTR腳將變成低電位,也請參閱4-42頁,燒錄程式「重置」Arduino板的流程說明。

      thanks,
      jeffrey

  17. 老師您好

    我在我的樹苺派3中,將書中8-16的資料新增進sensors資料庫的dht11資料集,如下所示

    pi@raspberrypi:~ $ mongo sensors
    MongoDB shell version: 2.4.10
    connecting to: sensors
    Server has startup warnings:
    Mon Aug 15 10:14:51.356 [initandlisten]
    Mon Aug 15 10:14:51.356 [initandlisten] ** NOTE: This is a 32 bit MongoDB binary.
    Mon Aug 15 10:14:51.356 [initandlisten] ** 32 bit builds are limited to less than 2GB of data (or less with –journal).
    Mon Aug 15 10:14:51.356 [initandlisten] ** See http://dochub.mongodb.org/core/32bit
    Mon Aug 15 10:14:51.356 [initandlisten]
    > db.dht11.find();
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3eb”), “溫度” : 23, “濕度” : 57, “時間” : ISODate(“2015-10-17T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3ec”), “溫度” : 23, “濕度” : 50, “時間” : ISODate(“2015-10-18T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3ed”), “溫度” : 20, “濕度” : 57, “時間” : ISODate(“2015-10-19T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3ee”), “溫度” : 25, “濕度” : 58, “時間” : ISODate(“2015-10-20T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3ef”), “溫度” : 22, “濕度” : 50, “時間” : ISODate(“2015-10-21T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f0”), “溫度” : 21, “濕度” : 61, “時間” : ISODate(“2015-10-22T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f1”), “溫度” : 21, “濕度” : 57, “時間” : ISODate(“2015-10-23T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f2”), “溫度” : 21, “濕度” : 59, “時間” : ISODate(“2015-10-24T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f3”), “溫度” : 20, “濕度” : 58, “時間” : ISODate(“2015-10-25T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f4”), “溫度” : 23, “濕度” : 62, “時間” : ISODate(“2015-10-26T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f5”), “溫度” : 24, “濕度” : 59, “時間” : ISODate(“2015-10-27T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f6”), “溫度” : 23, “濕度” : 59, “時間” : ISODate(“2015-10-28T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f7”), “溫度” : 24, “濕度” : 57, “時間” : ISODate(“2015-10-29T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f8”), “溫度” : 20, “濕度” : 53, “時間” : ISODate(“2015-10-30T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3f9”), “溫度” : 20, “濕度” : 54, “時間” : ISODate(“2015-10-31T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3fa”), “溫度” : 22, “濕度” : 61, “時間” : ISODate(“2015-11-01T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3fb”), “溫度” : 23, “濕度” : 55, “時間” : ISODate(“2015-11-02T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3fc”), “溫度” : 24, “濕度” : 54, “時間” : ISODate(“2015-11-03T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3fd”), “溫度” : 25, “濕度” : 58, “時間” : ISODate(“2015-11-04T03:00:00Z”) }
    { “_id” : ObjectId(“57b1274a5d8b9c107bc2a3fe”), “溫度” : 23, “濕度” : 62, “時間” : ISODate(“2015-11-05T03:00:00Z”) }
    Type “it” for more
    >

    然後將資料匯出成json格式
    pi@raspberrypi:~ $ mongoexport –db sensors –collection dht11 –out dht11.json
    connected to: 127.0.0.1
    exported 25 records
    pi@raspberrypi:~ $ more dht11.json
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3eb” }, “溫度” : 23, “濕度” : 57, “時間” : { “$date” : 1445050800000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3ec” }, “溫度” : 23, “濕度” : 50, “時間” : { “$date” : 1445137200000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3ed” }, “溫度” : 20, “濕度” : 57, “時間” : { “$date” : 1445223600000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3ee” }, “溫度” : 25, “濕度” : 58, “時間” : { “$date” : 1445310000000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3ef” }, “溫度” : 22, “濕度” : 50, “時間” : { “$date” : 1445396400000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f0” }, “溫度” : 21, “濕度” : 61, “時間” : { “$date” : 1445482800000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f1” }, “溫度” : 21, “濕度” : 57, “時間” : { “$date” : 1445569200000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f2” }, “溫度” : 21, “濕度” : 59, “時間” : { “$date” : 1445655600000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f3” }, “溫度” : 20, “濕度” : 58, “時間” : { “$date” : 1445742000000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f4” }, “溫度” : 23, “濕度” : 62, “時間” : { “$date” : 1445828400000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f5” }, “溫度” : 24, “濕度” : 59, “時間” : { “$date” : 1445914800000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f6” }, “溫度” : 23, “濕度” : 59, “時間” : { “$date” : 1446001200000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f7” }, “溫度” : 24, “濕度” : 57, “時間” : { “$date” : 1446087600000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f8” }, “溫度” : 20, “濕度” : 53, “時間” : { “$date” : 1446174000000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3f9” }, “溫度” : 20, “濕度” : 54, “時間” : { “$date” : 1446260400000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3fa” }, “溫度” : 22, “濕度” : 61, “時間” : { “$date” : 1446346800000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3fb” }, “溫度” : 23, “濕度” : 55, “時間” : { “$date” : 1446433200000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3fc” }, “溫度” : 24, “濕度” : 54, “時間” : { “$date” : 1446519600000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3fd” }, “溫度” : 25, “濕度” : 58, “時間” : { “$date” : 1446606000000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3fe” }, “溫度” : 23, “濕度” : 62, “時間” : { “$date” : 1446692400000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a3ff” }, “溫度” : 23, “濕度” : 57, “時間” : { “$date” : 1446778800000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a400” }, “溫度” : 24, “濕度” : 62, “時間” : { “$date” : 1446865200000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a401” }, “溫度” : 24, “濕度” : 58, “時間” : { “$date” : 1446951600000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a402” }, “溫度” : 23, “濕度” : 54, “時間” : { “$date” : 1447038000000 } }
    { “_id” : { “$oid” : “57b1274a5d8b9c107bc2a403” }, “溫度” : 20, “濕度” : 61, “時間” : { “$date” : 1447124400000 } }
    pi@raspberrypi:~ $

    但是如果匯出csv格式,有中文的名稱,似乎無法匯出,如下所示
    pi@raspberrypi:~ $ mongoexport –db sensors –collection dht11 –fields _id,溫度,濕度,時間 –csv –out dht11.csv
    connected to: 127.0.0.1
    exported 25 records
    pi@raspberrypi:~ $ more dht11.csv
    _id
    ObjectID(57b1274a5d8b9c107bc2a3eb)
    ObjectID(57b1274a5d8b9c107bc2a3ec)
    ObjectID(57b1274a5d8b9c107bc2a3ed)
    ObjectID(57b1274a5d8b9c107bc2a3ee)
    ObjectID(57b1274a5d8b9c107bc2a3ef)
    ObjectID(57b1274a5d8b9c107bc2a3f0)
    ObjectID(57b1274a5d8b9c107bc2a3f1)
    ObjectID(57b1274a5d8b9c107bc2a3f2)
    ObjectID(57b1274a5d8b9c107bc2a3f3)
    ObjectID(57b1274a5d8b9c107bc2a3f4)
    ObjectID(57b1274a5d8b9c107bc2a3f5)
    ObjectID(57b1274a5d8b9c107bc2a3f6)
    ObjectID(57b1274a5d8b9c107bc2a3f7)
    ObjectID(57b1274a5d8b9c107bc2a3f8)
    ObjectID(57b1274a5d8b9c107bc2a3f9)
    ObjectID(57b1274a5d8b9c107bc2a3fa)
    ObjectID(57b1274a5d8b9c107bc2a3fb)
    ObjectID(57b1274a5d8b9c107bc2a3fc)
    ObjectID(57b1274a5d8b9c107bc2a3fd)
    ObjectID(57b1274a5d8b9c107bc2a3fe)
    ObjectID(57b1274a5d8b9c107bc2a3ff)
    ObjectID(57b1274a5d8b9c107bc2a400)
    ObjectID(57b1274a5d8b9c107bc2a401)
    ObjectID(57b1274a5d8b9c107bc2a402)
    ObjectID(57b1274a5d8b9c107bc2a403)
    pi@raspberrypi:~ $

    想請問有中文名稱的欄位,有辦法匯出成csv格式嗎?(若改成英文名稱是可以的,譬如將溫度改成tem,濕度改成hum,時間改成time)

    1. 我在Windows上測試也會有亂碼,所以是mongoexport本身的問題。你可以使用免費的線上工具,把JSON轉換成SVG(請搜尋關鍵字:JSON to SVG converter),或者寫個Node.js的程式來轉換,像這個json-2-csv套件

      thanks,
      jeffrey

  18. 老師您好:

    我參考書上12-44頁,想用NodeMCU當作client端,傳送資料到我的電腦(或者樹莓派)上的Node.js,如下所示

    void IFTTT() {
    http.begin(“192.168.0.111”, 80, “/th?data={\”t\”:22, \”h\”:56}”);
    http.addHeader(“Content-Type”, “application/json”);

    // 設置要傳送的JSON資料
    //int httpCode = http.GET();
    if(httpCode > 0) {
    // 在序列埠監控視窗顯示送出的數據
    Serial.printf(“HTTP code: %d\n”, httpCode);

    if(httpCode == 200) {
    String payload = http.getString();
    // 顯示遠端伺服器的回應
    Serial.println(payload);
    }
    } else {
    Serial.println(“HTTP connection ERROR!”);
    }
    }

    然後我參考https://swf.com.tw/?p=881#comment-954352 老師的作法,在我的電腦(或者樹莓派)上寫Node.js的程式如下:

    var express = require(‘express’);
    var app = express();

    app.get(“/th”, function(req, res) {
    var json=JSON.parse(req.query.data);
    var temp = json.t; // 讀取查詢字串的t值
    var humid = json.h; // 讀取查詢字串的h值

    // 確認有收到溫度和濕度值(兩者都不是undefined)
    if (temp != undefined && humid != undefined) {
      console.log(“溫度: ” + temp + “,濕度: ” + humid);
      res.send(“溫度: ” + temp + “°C,濕度: ” + humid +”%”);
    } else {
    console.log(“沒收到資料!”);
    }
    });
    app.use(“*”,function(req,res){
    res.status(404).send(‘查無此頁!’);
    });

    var server = app.listen(5438, function () {
    console.log(“網站伺服器在5438埠口開工了!”);
    });

    以上程式運作正常,我的電腦(或者樹莓派)可以接收NodeMCU傳回的資料

    然後我改成12-44頁POST的方法,如下所示:

    void IFTTT() {
    http.begin(“192.168.0.111”, 80, “/th”);
    http.addHeader(“Content-Type”, “application/json”);

    // 設置要傳送的JSON資料
    int httpCode = http.POST(“{\”t\”:22, \”h\”:56}”);
    if(httpCode > 0) {
    // 在序列埠監控視窗顯示送出的數據
    Serial.printf(“HTTP code: %d\n”, httpCode);

    if(httpCode == 200) {
    String payload = http.getString();
    // 顯示遠端伺服器的回應
    Serial.println(payload);
    }
    } else {
    Serial.println(“HTTP connection ERROR!”);
    }
    }

    因為上面是採用POST的方法傳回,所以我把app.get改成app.post,修改的Node.js程式如下

    var express = require(‘express’);
    var app = express();

    app.post(“/th”, function(req, res) {
    var json=JSON.parse(req);
    var temp = json.t; // 讀取查詢字串的t值
    var humid = json.h; // 讀取查詢字串的h值

    // 確認有收到溫度和濕度值(兩者都不是undefined)
    if (temp != undefined && humid != undefined) {
      console.log(“溫度: ” + temp + “,濕度: ” + humid);
      res.send(“溫度: ” + temp + “°C,濕度: ” + humid +”%”);
    } else {
    console.log(“沒收到資料!”);
    }
    });
    app.use(“*”,function(req,res){
    res.status(404).send(‘查無此頁!’);
    });

    var server = app.listen(5438, function () {
    console.log(“網站伺服器在5438埠口開工了!”);
    });

    但是我的電腦(或者樹莓派)無法接收NodeMCU傳回的資料,所以想請問老師程式哪裡寫錯了

    1. 請參閱3-38頁「接收與處理POST資料」一節說明,以及3-40頁的程式,你的Node.js程式需要引用”body-parser”模組。

      thanks,
      jeffrey

  19. 老師您好:

    看完老師著作Arduino互動設計入門2的13-28頁~13-32頁,想完成下列兩部份

    第一部份
    用USB線連接實體電子琴和樹莓派(或我的Windows 10 PC),然後想把將實體電子琴的midi訊息(狀態、音高和強弱三個byte)傳送到樹莓派(或我的Windows 10 PC)的Node.js程式。

    第二部份
    用另一條USB線連接Arduino UNO和樹莓派(或我的Windows 10 PC),然後想從樹莓派(或我的Windows 10 PC)的Node.js程式將midi訊息(狀態、音高和強弱三個byte)傳送到Arduino UNO
    去控制連接在Arduino UNO上的伺服馬達或直流馬達

    或者

    不用USB線,然後想從樹莓派(或我的Windows 10 PC)的Node.js程式將midi訊息(狀態、音高和強弱三個byte)透過Wifi傳送到NodeMCU去控制連接在NodeMCU上的伺服馬達或直流馬達

    關於以上兩部份,在第二部份,在超圖解物聯網IOT實作入門的5-22頁將樹莓派(或我的Windows 10 PC)利用johnny-five去控制Arduino UNO的伺服馬達,以及在12-31頁13-9頁去控制NodeMCU的LED(依據上面目標改成控制伺服馬達),不知道是不是比較好的作法或者還有更好的作法?

    這裡另外想問為何12-31頁和13-9頁,同樣是的點亮LED,一個用digitalWrite(LED_PIN, LOW),一個用digitalWrite(LED_PIN, HIGH)呢?

    在第一部份,至於讀取實體電子琴的midi訊息(狀態、音高和強弱三個byte)傳送到樹莓派(或我的Windows 10 PC)的部份,
    在www.npmjs.com網站搜尋midi,找到相當多的模組https://www.npmjs.com/search?q=midi,不知道老師可否在此部落格寫一個教學文,大概教一下該如何使用midi模組讀取實體電子琴的midi訊息呢?

    另外,我使用Arduino互動設計入門2的13-28頁的虛擬MIDI鍵盤軟體,把輸出設定成我的實體電子琴,也可以從虛擬MIDI鍵盤軟體將midi訊息(狀態、音高和強弱三個byte)傳送到我的實體電子琴發出聲音,所以想請問老師上面的https://www.npmjs.com/search?q=midi midi模組是否也能從樹莓派(或我的Windows 10 PC)傳送midi訊息(狀態、音高和強弱三個byte)到實體電子琴發出聲音?

    1. MIDI部份,問題的核心應該在於Arduino板或者樹莓派的USB介面,是否能直接變成USB-MIDI介面,這樣一來,將它們接上電腦或手機、平板,即可立即被辨識成MIDI設備,也能直接和其他MIDI器材相連。相關的說明我會抽空更新。

      樹莓派我不確定,但是採用ATmega32U4處理器的Arduino Leonardo或者Arduino Micro等控制板,因為微處理器內建USB介面,並且可透過程式設定成鍵盤、搖桿、滑鼠以及MIDI等人機介面,所以是可行的。UNO板子,除非改寫ATmega16U2的韌體,或者在電腦端安裝橋接軟體,就只能採用13-25頁所示的MIDI轉USB介面了。

      至於物聯網的12-31頁和13-9頁的問題,是因為LED的接法不同,請對照12-23頁和13-8頁的電路圖。

      thanks,
      jeffrey

    2. 老師您好:

      在第二部份中,我沒有寫清楚,應該是第一部份的樹莓派(或我的Windows 10 PC)的Node.js程式去接收實體電子琴的midi訊息(狀態、音高和強弱三個byte),然後由Node.js程式解讀midi訊息(狀態、音高和強弱三個byte),去命令相對應的Arduino UNO板,以及去控制連接在Arduino UNO上的伺服馬達或直流馬達,譬如音高是DO RE MI FA去控制第一個Arduino UNO上的伺服馬達或直流馬達,SO LA Si 去控制第二個Arduino UNO上的伺服馬達或直流馬達,至於midi的強弱訊息則去控制伺服馬達或直流馬達的轉速。所以並不是樹莓派(或我的Windows 10 PC)的Node.js程式直接傳送midi訊息(狀態、音高和強弱三個byte)給Arduino UNO,只是單純的傳送命令給Arduino UNO,所以並不需要13-25頁所示的MIDI轉USB介面,所以應該可以用6-26頁使用方向鍵操控伺服馬達的方式去改成使用midi鍵盤去操控伺服馬達。

      在第二部份中,如果不用Arduino UNO而改用NodeMCU,並將USB線連接NodeMCU和樹莓派(或我的Windows 10 PC),不知道是否可以用johnny-five模組去控制連接在NodeMCU上的伺服馬達或直流馬達? 5-5頁是把範例檔StandardFirmata燒進Arduino UNO,在這裡則是燒進NodeMCU中

      又如果是樹莓派(或我的Windows 10 PC)不使用USB線連接NodeMCU(也就是不使用johnny-five模組也不用把範例檔StandardFirmata燒進NodeMCU中),而是在樹莓派(或我的Windows 10 PC)上的Node.js程式直接透過Wifi傳送命令給NodeMCU,如果用書中12-31頁去控制NodeMCU的LED(依據上面目標改成控制伺服馬達),應該不可行,因為Node.js是server端,同樣NodeMCU也是server端,無法透過GET和POST的請求傳送資料,請問在這種情況下要如何讓Node.js透過wifi傳送訊號給NodeMCU呢?

      以上所用到的樹莓派、Windows 10 PC、實體電子琴、Arduino UNO以及NodeMCU等設備,目前不確定的是實體電子琴、Arduino UNO以及NodeMCU連接到樹莓派是否能夠被樹莓派偵測到(否則就要在樹莓派的Linux中裝實體電子琴、Arduino UNO以及NodeMCU的驅動程式)

      其實我真正要解決的是第一部份,第二部份書上已經有解了,第一部分的實體電子琴連接Windows 10 PC,Windows 10 PC可以偵測到沒有問題,如果實體電子琴連接到Windows 10 PC的話,目前想知道https://www.npmjs.com/search?q=midi 的模組安裝以後,是否能在Node.js程式中有方便的方法可以讀取實體電子琴的midi訊息(狀態、音高和強弱三個byte),如果可以的話,那麼實體電子琴連接樹莓派的問題只剩下樹莓派是否能偵測到我的實體電子琴(我的實體電子琴是CASIO CTK-3200)(否則就要在樹莓派的Linux中裝實體電子琴的驅動程式)

    3. 抱歉誤解你的問題…在Node.js上,你可以嘗試這個node-midi套件。安裝時,除了必要的C++編譯器和node-gyp,在Linux系統(樹莓派),你還需要事先安裝libasound2-dev:

      $ sudo apt-get install libasound2-dev

      即可在你的midi專案路徑安裝node-midi套件:

      $ npm install midi

      以下的範例修改自node-midi專案網頁的input範例,它將偵聽接在USB埠的MIDI介面訊息,在控制台顯示MIDI訊息。我剛剛在Windows 10和Raspberry Pi 2,連接數位鋼琴測試成功:

      var midi = require('midi');
      
      // 初始化MIDI輸入物件
      var input = new midi.input();
      
      console.log("可用的輸入埠數量:" + input.getPortCount());
      console.log("接在埠0的裝置名稱:" + input.getPortName(0));
      
      // 偵聽並且顯示MIDI訊息
      input.on('message', function(deltaTime, message) {
        console.log('m:' + message + ' d:' + deltaTime);
      });
      
      // 開啟(連線到)埠0
      input.openPort(0);
      

      你可能需要修改連接埠的編號,例如,在我的樹莓派上,MIDI裝置接在埠1。測試成功之後,你可以進一步在”message”事件處理程式裡面解析message字串,分析出音高、強弱等訊息去控制你的週邊。

      不過,其實MIDI訊息不是很複雜,單單Arduino控制板應該足以勝任接收、解析和控制的工作,不必透過電腦或Raspberry Pi轉接,本週我再測試看看。

      thanks,
      jeffrey

    4. 老師您好

      感謝老師幫忙測試,由於目前大部份的電子琴或電鋼琴的介面都是USB的Type-B的孔去接樹莓派(或Windows 10 PC)的
      USB Type-A的孔,如果不用樹莓派(或Windows 10 PC),如何讓電子琴或電鋼琴把midi訊息(狀態、音高和強弱三個byte)傳送
      給Arduino UNO去解析呢? (因為Arduino UNO上沒有USB Type-A的孔)

      在第一部份中,還有一個變化就是原本是將電子琴或電鋼琴把midi訊息(狀態、音高和強弱三個byte)傳送給樹莓派(或Windows 10 PC)
      去解析,那請問如果第一部份不用電子琴或電鋼琴,而是讓Node.js程式直接去讀取midi檔案(譬如在http://sql.jaes.ntpc.edu.tw/javaroom/midi/alas/Ch/chopen.htm 可以抓到很多midi檔案)
      ,想請問node-midi套件可否也讀取出midi訊息(狀態、音高和強弱三個byte),然後就可以進展到我的第二部份(用Node.js程式去解析,並對Arduino UNO上的伺服馬達或直流馬達下命令)

    5. MIDI-USB介面連接Arduino,跟介面的形式沒有關係,反正都是收發TTL序列訊息,我會抽空更新文章說明。

      讀取MIDI檔,你可以搜尋MIDI parser之類的關鍵字,網頁的JavaScript和Node.js都有相關程式庫可用。

      thanks,
      jeffrey

    6. 老師您好

      我在Windows 10 PC,用老師的程式去測試成功,當我按下CASIO CTK-3200 midi keyboard 的中央C(60),發出聲音中央C的聲音並
      送出 144(按下), 60(中央C), 速度(69),放開midi keyboard 的中央C(60),送出 128(放開), 60(中央C), 速度(64),時間花了0.109秒

      如下所示

      C:\Users\alex\Desktop\test>node index.js
      可用的輸入埠數量:1
      接在埠0的裝置名稱:CASIO USB-MIDI 0
      m:144,60,69 d:0
      m:128,60,64 d:0.109

      我試著在嘗試用Node.js程式輸出midi message給可以發出聲音的CASIO CTK-3200,如下所示

      var midi = require(‘midi’);

      // Set up a new output.
      var output = new midi.output();

      // Count the available output ports.
      //output.getPortCount();
      console.log(“可用的輸出埠數量:” + output.getPortCount());

      // Get the name of a specified output port.
      //output.getPortName(0);
      console.log(“接在埠1的裝置名稱:” + output.getPortName(1));

      // Open the first available output port.
      output.openPort(1);

      // Send a MIDI message.
      output.sendMessage([144,60,127]); //按下
      output.sendMessage([128,60,0]); //放開

      // Close the port when done.
      output.closePort();

      我的CASIO CTK-3200可以發出中央C的聲音,但是範例程式並沒有教如何設定送出音符的延遲時間(四分音符為一拍,八分音符為半拍)
      請問該如何設定延遲時間呢?

      以下是我測試畫面

      C:\Users\alex\Desktop\test>node index1.js
      可用的輸出埠數量:2
      接在埠1的裝置名稱:CASIO USB-MIDI 1

      C:\Users\alex\Desktop\test>

    7. 就像彈奏鋼琴一樣,四分音符和八分音符的不同,是依照你按下和放開琴鍵的間隔時間決定,也就是MIDI的ON和OFF訊息的間隔時間,你的程式可以自行掌控。

      thanks,
      jeffrey

  20. 老師您好:

    根據上述老師所寫的程式,繼續作解析的動作,利用serialport模組將midi message傳送給Arduino UNO,Node.js程式修改如下

    var midi = require(‘midi’);
    var com = require(“serialport”);
    var serialPort = new com.SerialPort(“COM4”);

    // 初始化MIDI輸入物件
    var input = new midi.input();
    var output = new midi.output();

    console.log(“可用的輸入埠數量:” + input.getPortCount());
    console.log(“接在埠0的裝置名稱:” + input.getPortName(0));

    // 偵聽並且顯示MIDI訊息
    input.on(‘message’, function(deltaTime, message) {
    console.log(‘m:’ + message + ‘ d:’ + deltaTime);
    serialPort.on(“open”, function(error){
    serialPort.write(message[0]);
    serialPort.write(message[1]);
    serialPort.write(message[2]);
    });
    });

    // 開啟(連線到)埠0
    input.openPort(0);

    接下來想用Arduino UNO去解析midi message,如果按下數位鋼琴中央C就點亮Arduino UNO的LED 13,如果放開中央C就熄滅Arduino UNO的LED 13,Arduino UNO程式如下所示

    byte tempByte[3];
    byte count=0;
    void setup() {
    Serial.begin(115200);
    pinMode(13, OUTPUT);
    }
    void checkMIDI() {
    if (Serial.available()){
    tempByte[count++] = Serial.read();
    if (count==3) {
    if(tempByte[0]==144) {
    if (tempByte[1]==60) {
    digitalWrite(13, HIGH);
    }
    } else if (tempByte[0]==128) {
    if (tempByte[1]==60) {
    digitalWrite(13, LOW);
    }
    }
    count=0;
    }
    }
    }
    void loop() {
    checkMIDI();
    }

    測試畫面如下

    C:\Users\alex\Desktop\test>node test.js
    可用的輸入埠數量:1
    接在埠0的裝置名稱:CASIO USB-MIDI 0
    m:144,60,46 d:0
    m:128,60,64 d:0.331
    m:144,62,57 d:1.985
    m:128,62,64 d:0.3
    m:144,64,66 d:0.8
    m:128,64,64 d:0.3
    m:144,65,63 d:0.484
    m:128,65,64 d:0.3
    m:144,67,74 d:0.548
    m:128,67,64 d:0.299
    m:144,69,77 d:0.4
    (node) warning: possible EventEmitter memory leak detected. 11 open listeners added. Use emitter.setMaxListeners() to increase limit.
    Trace
    at SerialPort.addListener (events.js:239:17)
    at NodeMidiInput. (C:\Users\alex\Desktop\test\test.js:15:14)
    at emitTwo (events.js:87:13)
    at NodeMidiInput.emit (events.js:172:7)
    m:128,69,64 d:0.269
    m:144,71,77 d:0.501
    m:128,71,64 d:0.28
    m:144,72,62 d:1.02
    m:128,72,64 d:0.316
    m:144,60,69 d:1.887
    m:128,60,64 d:0.253

    上述畫面有出現警告,說明要加emitter.setMaxListeners(),但我不知道要加在哪,而且Arduino UNO上的LED燈也沒預期的亮燈及關燈,所以想請問老師上述的警告和沒有亮燈是哪裡出了錯誤?

    1. 出現錯誤訊息的原因是,你把處理開啟序列埠的事件程式寫在接收MIDI訊息的事件程式裡面。開啟偵聽序列埠”open”事件的程式只需要執行一次,我稍微修改你的Node程式,尚未測試過:

      var midi = require('midi');
      var com = require('serialport');
      var serialPort = new com.SerialPort("COM4");
      
      // 確認序列埠已開通,預設為「否」。
      var serialPortOpened = false;
      
      // 初始化MIDI輸入物件
      var input = new midi.input();
      var output = new midi.output();
      
      console.log("可用的輸入埠數量:" + input.getPortCount());
      console.log("接在埠0的裝置名稱:" + input.getPortName(0));
      
      serialPort.on("open", function(error){
          console.log("序列埠已開通");
          
          serialPortOpened = true;
      });
      
      // 偵聽並且顯示MIDI訊息
      input.on('message', function(deltaTime, message) {
          console.log('m:' + message + ' d:' + deltaTime);
          
          if (serialPortOpened ) {
            // 送出序列訊號
            serialPort.write(message[0]);
            serialPort.write(message[1]);
            serialPort.write(message[2]);
         }
      });
      
      // 開啟(連線到)埠0
      input.openPort(0);
      

      thanks,
      jeffrey

  21. 老師您好:

    我試著再嘗試用Node.js程式輸出midi message並延遲音高,給可以發出聲音的CASIO CTK-3200,如下所示

    var midi = require(‘midi’);

    // Set up a new output.
    var output = new midi.output();

    // Count the available output ports.
    console.log(“可用的輸出埠數量:” + output.getPortCount());

    // Get the name of a specified output port.
    console.log(“接在埠1的裝置名稱:” + output.getPortName(1));

    // Open the first available output port.
    output.openPort(1);

    // Send a MIDI message.
    output.sendMessage([144,60,127]); //按下
    setTimeout(function() {
    },3000);
    output.sendMessage([128,60,0]); //放開
    setTimeout(function() {
    },1000);

    // Close the port when done.
    output.closePort();

    測試畫面如下

    C:\Users\alex\Desktop\test>node index1.js
    可用的輸出埠數量:2
    接在埠1的裝置名稱:CASIO USB-MIDI 1

    C:\Users\alex\Desktop\test>

    但是沒辦法延遲中央C的音高,只是延遲程式結束的時間,所以想請問該如何延遲中央C的音高

    1. 請嘗試:

      output.sendMessage([144,60,127]); //按下
      setTimeout(function() {
        output.sendMessage([128,60,0]); //放開
      },1000);
      

      thanks,
      jeffrey

    2. 老師您好:

      還是不行,可能是output.sendMessage([144,60,127]) 這個函式只能發出短促音,根本無法延長

    3. 剛剛仔細看了你的程式碼,最後一行敘述是關閉輸出埠:

      output.closePort();

      導致程式才剛彈奏聲音,通訊埠就被拔斷了。

      請測試:

      var midi = require('midi');
      var output = new midi.output();
      
      console.log("可用的輸出埠數量:" + output.getPortCount());
      console.log("接在埠1的裝置名稱:" + output.getPortName(1));
      
      output.openPort(1);
      
      // 按下中央C,一秒鐘。
      output.sendMessage([144,60,127]); //按下
      setTimeout(function() {
        output.sendMessage([128,60,0]); //放開
      },1000);
      

      thanks,
      jeffrey

    4. 老師您好:

      我試著再嘗試用Node.js程式輸出midi message(小星星前面四個音符 Do(四分音符)、Do(四分音符)、So(四分音符)、So(四分音符)),如下所示

      const one=1000
      const two=one/2.0
      const four=one/4.0
      const eight=one/8.0
      const sixteen=one/16.0
      const thirty_two=one/32.0

      var midi = require(‘midi’);

      // Set up a new output.
      var output = new midi.output();

      // Count the available output ports.
      console.log(“可用的輸出埠數量:” + output.getPortCount());

      // Get the name of a specified output port.
      console.log(“接在埠1的裝置名稱:” + output.getPortName(1));

      // Open the first available output port.
      output.openPort(1);

      var press=function(note) {
      output.sendMessage([144,note,127]);
      };
      var pull=function(note) {
      output.sendMessage([128,note,0]);
      }

      var touch=function(note,lag) {
      output.sendMessage([144,note,127]);
      setTimeout(function() {
      output.sendMessage([128,note,0]);
      },lag);
      }

      var song1=function() {

      output.sendMessage([144,60,127]);
      setTimeout(function() {
      output.sendMessage([128,60,0]);
      setTimeout(function() {
      output.sendMessage([144,60,127]);
      setTimeout(function() {
      output.sendMessage([128,60,0]);
      setTimeout(function() {
      output.sendMessage([144,67,127]);
      setTimeout(function() {
      output.sendMessage([128,67,0]);
      setTimeout(function() {
      output.sendMessage([144,67,127]);
      setTimeout(function() {
      output.sendMessage([128,67,0]);
      },four);
      },four);
      },four);
      },four);
      },four);
      },four);
      },four);

      };

      song1();

      // Close the port when done.
      //output.closePort();

      為了要將四個音符分開彈奏,我目前只試出上面的寫法,但是這種巢狀的寫法並不好,可讀性也不高,所以想請教老師是否有好的寫法,我也寫了press、pull、touch等函式,但還沒派上用場

    5. 那是因為你把MIDI資料和處理邏輯寫在一起,難免會有這種情況。你可以嘗試把MIDI資料全部寫在一個陣列或Object;將處理MIDI ON, OFF的程式寫成函式,讓它處理一個MIDI數據之後,自動抓取下一個數據。

      thanks,
      jeffrey

    6. 老師您好:

      我參考了https://www.npmjs.com/package/midi-file 這個模組的範例程式,想透過這個模組解析midi file,並將
      解析出來的midi message(狀態、音高和強弱三個byte),傳送到數位鋼琴發出聲音,midi-file的範例程式如下:

      var fs = require(‘fs’)
      var parseMidi = require(‘midi-file’).parseMidi
      var writeMidi = require(‘midi-file’).writeMidi

      // Read MIDI file into a buffer
      var input = fs.readFileSync(‘star_wars.mid’)

      // Parse it into an intermediate representation
      // This will take any array-like object. It just needs to support .length, .slice, and the [] indexed element getter.
      // Buffers do that, so do native JS arrays, typed arrays, etc.
      var parsed = parseMidi(input)

      // Turn the intermediate representation back into raw bytes
      var output = writeMidi(parsed)

      // Note that the output is simply an array of byte values. writeFileSync wants a buffer, so this will convert accordingly.
      // Using native Javascript arrays makes the code portable to the browser or non-node environments
      var outputBuffer = new Buffer(output)

      // Write to a new MIDI file. it should match the original
      fs.writeFileSync(‘copy_star_wars.mid’, outputBuffer)

      上述程式將midi file的訊息都存入了parsed這個變數,請問該如何用這個變數透過output.sendMessage()傳送到數位鋼琴呢?

    7. 請查閱它的專案網頁,看看有沒有關於outputBuffer的資料結構的說明,你再從中擷取出你要的數據。

      thanks,
      jeffrey

    8. 老師您好:

      我曾經在Arduino IDE的程式透過序列部去輸出midi訊息,我的作法也是midi訊息和邏輯放在一起,因為有些譜是鋼琴譜有高音譜和低音譜,所以會發生有同時按下三個以上的音符,又因為各個音符長短各不同,所以放開的時間也不同,我是用循序執行的方式,所以執行起來沒有問題,但是Node.js是非同步的方式執行程式,所以我在寫Node.js程式輸出音符時會發生看起來應該是不同時間發出聲音卻變成同一時間發出聲音,所以就不知道如何撰寫,如果像有鋼琴譜(高琴譜和低音譜),就會有同時有多個音符發出聲音,卻在不同時間上消失,這種midi message該如何寫成物件或陣列的格式呢?

      我又參考https://www.npmjs.com/package/jazz-midi,他程式有一段如下

      var jazz = require(‘jazz-midi’);
      var midi = new jazz.MIDI();

      var name = midi.MidiOutOpen(0);
      if(name){
      console.log(‘Default MIDI-Out port:’, name);
      midi.MidiOut(0x90,60,100); midi.MidiOut(0x90,64,100); midi.MidiOut(0x90,67,100);
      setTimeout(function(){
      midi.MidiOut(0x80,60,0); midi.MidiOut(0x80,64,0); midi.MidiOut(0x80,67,0);
      midi.MidiOutClose();
      console.log(‘Thank you for using Jazz-MIDI!’);
      }, 3000);
      } else {
      console.log(‘Cannot open default MIDI-Out port!’);
      }

      他是同時按下Do, Mi, So然後三秒後同時放開,如果這三個音符不同時間放開可能程式就更複雜了

    9. 不會啦~用setTimeOut()之類的時間函式不會產生前後時間錯亂的情況,否則所有採用Node.js開發的MIDI程式都不能用了。

      至於多個琴鍵的情況,也許可以像這樣用陣列和Object安排資料:
      [
      [{“note”:”60″, “time”: 0.5}],
      [{“note”:”61″, “time”: 0.5}, {“note”:”62″, “time”: 0.7}, {“note”:”63″, “time”: 0.5}],
      :
      ]

      其中的第二個元素包含3個音符,解析資料的函式裡面透過for迴圈設定每個音符的ON以及OFF的時間。

      thanks,
      jeffrey

  22. 老師您好:

    真正在演奏鋼琴的時候,每個音符按下跟放開的時間有可能會重疊,所以我是將按下和放開要分開處理,我在
    Arduino IDE有一小節是這樣寫的,如下所示

    1 press(60); press(72); press(76); delay(two);
    2 press(67); delay(four);
    3 pull(60); pull(72); pull(76); delay(four);
    4 press(55); press(71); press(74); delay(four);
    5 pull(67); delay(four);
    6 press(67); delay(four);
    7 pull(55); pull(71); pull(74); delay(four);

    上述樂譜的一個小節(四分音符為一拍,每小節有四拍),像在第2行在第1行尚未放開之前按下的,直到第5行才放開,且第6行按下時,直到下一小節經過一拍後才放開

    如果用老師的資料結構如下

    1 [
    2 [{“note”:”60″, “time”: 0.5}],
    3 [{“note”:”61″, “time”: 0.5}, {“note”:”62″, “time”: 0.7}, {“note”:”63″, “time”: 0.5}],
    4 :
    5 ]

    如果在第3行按下61, 62, 63的時候,經過了0.3秒需要再按下65,這時第3行所按下的音符尚未放開,就是重疊的現象,而且第3行每個音符的長短各有不同,要經過多久才按下第4行並沒有描述,就會發生問題

  23. 老師您好:

    嘗試想把兩個中央C分開彈奏,試了Node.js程式只有第二種寫法才能可以正常運作,但第一種寫法看起來應該會分開彈奏卻會把兩個中央C合起來彈奏,如下所示:

    ————————————————————————————————–
    Node.js 第一種寫法

    const one=1000
    const two=one/2.0
    const four=one/4.0
    const eight=one/8.0
    const sixteen=one/16.0
    const thirty_two=one/32.0

    var midi = require(‘midi’);
    // Set up a new output.
    var output = new midi.output();
    // Count the available output ports.
    console.log(“可用的輸出埠數量:” + output.getPortCount());
    // Get the name of a specified output port.
    console.log(“接在埠1的裝置名稱:” + output.getPortName(1));
    // Open the first available output port.
    output.openPort(1);

    var touch=function(note,lag) {
    output.sendMessage([144,note,127]);
    setTimeout(function() {
    console.log(lag);
    },lag);
    output.sendMessage([128,note,0]);
    setTimeout(function() {
    console.log(lag);
    },lag);
    }

    var song1=function() {
    touch(60,four);
    touch(60,four);
    }

    song1();

    上述程式無法將兩個中央C分開彈奏

    ——————————————————————————————–

    Node.js 第二種寫法

    const one=1000
    const two=one/2.0
    const four=one/4.0
    const eight=one/8.0
    const sixteen=one/16.0
    const thirty_two=one/32.0

    var midi = require(‘midi’);
    // Set up a new output.
    var output = new midi.output();
    // Count the available output ports.
    console.log(“可用的輸出埠數量:” + output.getPortCount());
    // Get the name of a specified output port.
    console.log(“接在埠1的裝置名稱:” + output.getPortName(1));
    // Open the first available output port.
    output.openPort(1);

    var song1=function() {
    output.sendMessage([144,60,127]);
    setTimeout(function() {
    output.sendMessage([128,60,0]);
    setTimeout(function() {
    output.sendMessage([144,60,127]);
    setTimeout(function() {
    output.sendMessage([128,60,0]);
    },four);
    },four);
    },four);
    }

    song1();

    上述程式可以將兩個中央C分開彈奏

    ——————————————————————————————–

    我參考Arduino 互動程式設計書上 13-31頁 改寫Arduino程式

    #define one 1000
    #define two one/2.0
    #define four one/4.0
    #define eight one/8.0
    #define sixteen one/16.0
    #define thirty_two one/32.0

    void midiMsg(byte cmd, byte pitch, byte velocity) {
    Serial.write(cmd);
    Serial.write(pitch);
    Serial.write(velocity);
    }

    void touch(int note, float lag) {
    midiMsg(0x90, note, 0x7F); delay(lag); midiMsg(0x80, note, 0x00); delay(lag);
    }

    void song1() {
    touch(60, four);
    touch(60, four);
    }

    void setup() {
    Serial.begin(115200);
    song1();
    }

    void loop() {

    }

    上述程式可以將兩個中央C分開彈奏
    ——————————————————————————————————–

    第一種寫法的setTimeout 似乎沒有如想像的運作,所以想請教老師第一種寫法是哪裡出錯了?

  24. 老師您好:

    剛試了不能用setTimeout,要用while迴圈才可以

    var touch=function(note,lag) {
    output.sendMessage([144,note,127]);
    var start = new Date;
    while (new Date – start < lag) {};
    output.sendMessage([128,note,0]);
    var start = new Date;
    while (new Date – start < lag) {};
    }

  25. 趙老師您好:

    在書中13-26頁中,使用OLED顯示IP位址與動態溫溼度,而在http://www.instructables.com/id/ESP8266-Weather-Widget/?ALLSTEPS 的網頁中,他使用了NodeMCU和ESP-01透過第三方網頁http://www.wunderground.com/ 以及搭配OLED顯示未來三天的最高溫度和最低溫度,但是第三方網頁有些需要收費或者功能上有所限制,例如利用Arduino Yun搭配Temboo(https://temboo.com/library/)是需要收費或有所限制的(https://temboo.com/account/plan),所以想請問老師如果使用NodeMCU或Arduino Yun要直接擷取Yahoo天氣(https://www.yahoo.com/news/weather/taiwan/taipei-city/taipei-city-2306179)而不透過第三方網頁的方式,該如何作到呢?

    目前看到只有樹莓派連接OLED撰寫python程式,可以直接擷取Yahoo天氣並顯示在OLED上,而使用NodeMCU不知道要如何不透過第三方網頁的方式擷取網路上的天氣資訊顯示在OLED上

    1. 我還沒看Yahoo天氣的訊息格式,但他們一定有提供XML或JSON格式,方便程式解析。

      thanks,
      jeffrey

    1. 老師您好:

      我試著用ArduinoJson library去修改課本12-43頁,去擷取明天的星期幾、最高溫、最低溫,天氣狀況,修改程式如下

      #include
      #include
      #include
      const char* ssid = “你的WiFi網路SSID”;
      const char* pass = “你的WiFi密碼”;
      unsigned long past = 0;
      const unsigned long interval = 5 * 1000L;

      void setup() {
      Serial.begin(115200);
      WiFi.begin(ssid, pass);
      /*
      * 若要指定IP位址,請自行在此加入WiFi.config()敘述。
      WiFi.config(IPAddress(192,168,1,50), // IP位址
      IPAddress(192,168,1,1), // 閘道(gateway)位址
      IPAddress(255,255,255,0)); // 網路遮罩(netmask)
      */
      Serial.println(“”);
      while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(“.”);
      }
      Serial.println(“Wi-Fi ready…”);
      }
      void loop() {
      if (millis() – past > interval) {
      httpSend();
      }
      }
      void httpSend() {
      HTTPClient http;
      String mystring=”/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%3D2306179%20and%20u%3D’c’&format=json&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=”;
      http.begin(“query.yahooapis.com”, 80, mystring);

      int httpCode = http.GET();
      if(httpCode > 0) {
      // 在序列埠監控視窗顯示送出的數據
      Serial.printf(“HTTP code: %d\n”, httpCode);

      if(httpCode == 200) {
      String payload = http.getString();
      // 顯示遠端伺服器的回應
      Serial.println(payload);
      payload.replace(“\””, “\\\””);
      Serial.println(payload);
      parseJson(payload);
      }
      } else {
      Serial.println(“HTTP connection ERROR!”);
      }
      past = millis();
      }
      void parseJson(String payload) {
      StaticJsonBuffer jsonBuffer;
      JsonObject& root = jsonBuffer.parseObject(payload);
      if (!root.success()) {
      Serial.println(“parseObject() failed”);
      return;
      }
      const char* day = root[“query”][“result”][“channel”][“item”][“forecast”][1][“day”];
      int high = root[“query”][“result”][“channel”][“item”][“forecast”][1][“high”];
      int low= root[“query”][“result”][“channel”][“item”][“forecast”][1][“low”];
      const char* text= root[“query”][“result”][“channel”][“item”][“forecast”][1][“text”];

      Serial.println(day);
      Serial.println(high);
      Serial.println(low);
      Serial.println(text);
      }

      發現執行的結果如下

      HTTP code: 200
      {“query”:{“count”:1,”created”:”2016-11-28T02:28:17Z”, ………………………….
      {\”query\”:{\”count\”:1,\”created\”:\”2016-11-28T02:28:17Z\”, ………………..

      3fff2950: 5c7b2c7d 646f6322 3a225c65 3933225c
      3fff2960: 5c2c225c 74616422 3a225c65 3932225c
      3fff2970: 766f4e20 31303220 2c225c36 6164225c
      3fff2980: 3a225c79 7554225c 2c225c65 6968225c
      3fff2990: 225c6867 32225c3a 2c225
      ets Jan 8 2013,rst cause:4, boot mode:(3,6)

      wdt reset
      load 0x4010f000, len 1384, room 16
      tail 8
      chksum 0x2d
      csum 0x2d
      v09f0c112
      ~ld

      結果並沒有印出我要的資料,請問老師這是甚麼錯誤呢?

    2. 我使用openweathermap.org網站擷取氣象資料,取用該網站的開放資料之前,必須先註冊(免費)帳號,取得一個API Key。

      取得API Key之後,輸入底下的網址:

      http://api.openweathermap.org/data/2.5/weather?q=地區名稱&APPID=你的API_KEY

      例如,取得台北地區的氣象:

      http://api.openweathermap.org/data/2.5/weather?q=Taipei,tw&APPID=你的API_KEY

      將能收到如下格式的JSON資料:

      {
      "coord":{"lon":121.53,"lat":25.05},
      "weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],
      "base":"stations",
      "main":{"temp":294.15,"pressure":1023,"humidity":64,"temp_min":294.15,"temp_max":294.15},
      "visibility":10000,
      "wind":{"speed":2.1,"deg":20},
      "clouds":{"all":75},
      "dt":1480951800,
      "sys":{"type":1,"id":7479,"message":0.0064,"country":"TW","sunrise":1480890315,"sunset":1480928657},
      "id":1668341,
      "name":"Taipei",
      "cod":200
      }
      

      我使用ArduinoJson程式庫的JsonHttpClient範例測試;首先修改連線的網址和資源路徑(檢索台中地區氣象資料):

      const char* server = "api.openweathermap.org";
      const char* resource = "/data/2.5/weather?q=Taichung,tw&APPID=你的API_KEY";
      

      調整接收HTTP回應的變數調大成500bytes:

      const size_t MAX_CONTENT_SIZE = 500;

      修改UserData結構資料定義:

      struct UserData {
        char temp[32];     // 溫度
        char humidity[32];  // 濕度
      };
      

      程式庫的範例採用DHCP連線,我使用固定IP,因此需要修改initEthernet()函式:

      void initEthernet() {
        byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
        IPAddress ip(192,168,1, 25);
        Ethernet.begin(mac, ip);
        Serial.println("Ethernet ready");
        delay(1000);
      }
      

      修改解析JSON資料的parseUserData()函式:

      bool parseUserData(char* content, struct UserData* userData) {
        StaticJsonBuffer<MAX_CONTENT_SIZE> jsonBuffer;
      
        JsonObject& root = jsonBuffer.parseObject(content);
      
        if (!root.success()) {
          Serial.println("JSON parsing failed!");
          return false;
        }
      
        // 取出main節點裡的temp和humidity
        strcpy(userData->temp, root["main"]["temp"]);
        strcpy(userData->humidity, root["main"]["humidity"]);
      
        return true;
      }
      

      修改顯示JSON資料的printUserData()函式:

      void printUserData(const struct UserData* userData) {
        Serial.print("Temperature = ");
        Serial.println(userData->temp);
        Serial.print("Humidity = ");
        Serial.println(userData->humidity);
      }
      

      最後編譯與上傳程式到Arduino,開啟序列埠監控視窗的顯示結果:

      溫濕度資料

      傳回的溫度單位是K(參閱維基百科的Kelvin條目說明),減去273.15才是攝氏溫度值。

      Have fun!
      jeffrey

  26. 老師 您好:
    我想請教您的使用GET方法,從Arduino傳送JSON資料給Node.js網站伺服器程式中,我能順利在控制台收到溫濕度的值,但如我想要利用瀏覽器去看溫濕度的值,加入res.send()嗎?括弧內一樣打”溫度:” + json.temp “濕度:” + json.humid嗎?
    希望您能替我解答,謝謝。

    app.get(“/t”, function(req, res) {
    var json=JSON.parse(req.query.data); // 解析JSON字串資料

    console.log(“溫度:” + json.temp); // 顯示23.4
    console.log(“濕度:” + json.humid); // 顯示56.7

    1. 你的需求,是從瀏覽器發起連結,動態向Arduino索取資料…請參閱2-25頁「使用jQuery讀取並解析JSON訊息」一節。

      thanks,
      jeffrey

  27. 老師 您好:
    敝人想用Arduino Wifi shield當作client端,傳送資料到我的電腦上的Node.js,Node.js參考您的範例,Arduino Wifi shield參考官網的程式https://www.arduino.cc/en/Tutorial/WiFiWebClientRepeating,但是連上Wifi後卻一值連不到Server端的電腦,請問可能是甚麼原因,謝謝。

    1. 請問是哪一個範例程式,若直接使用瀏覽器連接Node.js伺服器程式,可以順利運作嗎?

      thanks,
      jeffrey

  28. 老師 您好:
    我可以正常使用Get收直,但發現收直的速度跟Ethernet比起來慢很多(Ethernet:1ms;Wifi:1000ms);再調更快就收不到直,不知道是不是因為程式又做重新連線的關係,希望能像您請教,謝謝,參考的程式如下:
    /*
    Repeating Wifi Web Client

    This sketch connects to a a web server and makes a request
    using an Arduino Wifi shield.

    Circuit:
    * WiFi shield attached to pins SPI pins and pin 7

    created 23 April 2012
    modified 31 May 2012
    by Tom Igoe
    modified 13 Jan 2014
    by Federico Vanzati

    http://www.arduino.cc/en/Tutorial/WifiWebClientRepeating
    This code is in the public domain.
    */

    #include
    #include

    char ssid[] = “yourNetwork”; // your network SSID (name)
    char pass[] = “secretPassword”; // your network password
    int keyIndex = 0; // your network key Index number (needed only for WEP)

    int status = WL_IDLE_STATUS;

    // Initialize the Wifi client library
    WiFiClient client;

    // server address:
    char server[] = “www.arduino.cc”;
    //IPAddress server(64,131,82,241);

    unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds
    const unsigned long postingInterval = 10L * 1000L; // delay between updates, in milliseconds

    void setup() {
    //Initialize serial and wait for port to open:
    Serial.begin(9600);
    while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
    }

    // check for the presence of the shield:
    if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println(“WiFi shield not present”);
    // don’t continue:
    while (true);
    }

    String fv = WiFi.firmwareVersion();
    if (fv != “1.1.0”) {
    Serial.println(“Please upgrade the firmware”);
    }

    // attempt to connect to Wifi network:
    while (status != WL_CONNECTED) {
    Serial.print(“Attempting to connect to SSID: “);
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:
    delay(10000);
    }
    // you’re connected now, so print out the status:
    printWifiStatus();
    }

    void loop() {
    // if there’s incoming data from the net connection.
    // send it out the serial port. This is for debugging
    // purposes only:
    while (client.available()) {
    char c = client.read();
    Serial.write(c);
    }

    // if ten seconds have passed since your last connection,
    // then connect again and send data:
    if (millis() – lastConnectionTime > postingInterval) {
    httpRequest();
    }

    }

    // this method makes a HTTP connection to the server:
    void httpRequest() {
    // close any connection before send a new request.
    // This will free the socket on the WiFi shield
    client.stop();

    // if there’s a successful connection:
    if (client.connect(server, 80)) {
    Serial.println(“connecting…”);
    // send the HTTP PUT request:
    client.println(“GET /latest.txt HTTP/1.1”);
    client.println(“Host: http://www.arduino.cc“);
    client.println(“User-Agent: ArduinoWiFi/1.1”);
    client.println(“Connection: close”);
    client.println();

    // note the time that the connection was made:
    lastConnectionTime = millis();
    } else {
    // if you couldn’t make a connection:
    Serial.println(“connection failed”);
    }
    }

    void printWifiStatus() {
    // print the SSID of the network you’re attached to:
    Serial.print(“SSID: “);
    Serial.println(WiFi.SSID());

    // print your WiFi shield’s IP address:
    IPAddress ip = WiFi.localIP();
    Serial.print(“IP Address: “);
    Serial.println(ip);

    // print the received signal strength:
    long rssi = WiFi.RSSI();
    Serial.print(“signal strength (RSSI):”);
    Serial.print(rssi);
    Serial.println(” dBm”);
    }

    1. 我之前測試Arduio Uno WiFi板子的web前端程式時,它處理回應的間隔時間最低大約7秒,相較之下,你的WiFi shield有1秒已經算很快了 🙂

      話說回來,以物聯網當中的環境監測應用來說,每次回傳監測值的間隔時間可能都在幾十分鐘以上,所以7秒鐘還是可被接受。當然啦~若是要做即時無線遙控之類的裝置就不太合適了。

      thanks,
      jeffrey

  29. 老師 您好:
    謝謝您的經驗分享,因為我Sensor取樣頻率為14kHz,因此才會詢問這些問題;我使用的這塊Arduino wifi shield上附有SD卡,所以我必須先把資料暫存至SD卡後,之後再透過Wifi傳輸資料,以確保資料不會發生溢位或丟失問題嗎?

  30. 老師 您好:
    如果想要從Arduino傳一個陣列資料給node,那Arduino跟node資料該怎麼改?

    if (client.connect(server, 5438)) {
    Serial.write(myFile.read());
    client << "GET /t?data={\"ax\":" << 陣列
    << "} HTTP/1.1\r\n";
    client.println();

    1. 要先把HTTP送出的資料轉成字串,底下的程式片段假設你的Arduino陣列是int形式,透過for迴圈取出每個元素,將它們組合成String(字串)類型:

      int data[] = {123,456,789};
      String str = "";
      // 求取data陣列的元素數量
      byte leng = sizeof(data)/sizeof(int);
      for (byte i=0;i<leng;i++) {
        str = str + data[i] + ',';  // 每個元素後面加上一個','
      }
      

      上面的程式碼執行之後,str的值將是 “123,456,789,”,這樣就能透過HTTP傳送出去了。

      在Node端,用JavaScript字串物件的split()函式,可以把字串「還原」成陣列。例如:

      // 假設這是收到的字串
      var data = "123,456,789,"; 
      // 先把最後一個多餘的','刪除
      data = data.substring(0,data.length-1);
      // 現在,data的內容變成:"123,456,789"
      // 使用','把字串分割成陣列
      var arr = data.split(',');
      

      上面的程式碼執行之後,arr就是三個元素的陣列了,關於JavaScript/Node字串操作指令的相關說明,請參閱4-15頁。

      thanks,
      jeffrey

  31. 老師 您好:
    謝謝您的教學,另外想跟您請教,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] + ','; // 每個元素後面加上一個','
    }
    if (client.connect(server, 5438)) {
    Serial.write(myFile.read());
    client << "GET /t?data={\"ax\":" << str
    << "} HTTP/1.1\r\n";
    client.println();
    Node端的程式如下:
    var json=JSON.parse(req.query.data);
    var data = json.ax;
    data = data.substring(0,data.length-1);
    var arr = data.split(',');
    console.log(arr);

    結果:Error
    感覺是Node端接收的部分或是資料格式導致的。

    1. 因為資料是純文字而非JSON:

      app.get("/data", function(req, res) {
        var data = req.query.str;
        
        if (data != undefined) {
          data = data.substring(0,temp.length-1);
          var arr = data.split(',');
          console.log("資料1:" + arr[1]);
        } else {
        	console.log("沒收到資料!");
        }
        res.status(200).send('ok!');
      });
      

      thanks,
      jeffrey

  32. 老師 您好:

    謝謝您的告知,請問Arduino端程式如下;此格式應該也是JSON,還是傳字串該怎麼表示?

    if (client.connect(server, 5438)) {
    Serial.write(myFile.read());
    client << "GET /t?data={\"temp\":" << str
    << "} HTTP/1.1\r\n";
    client.println();

    1. 你傳送的是data={“temp”:20}之類的查詢字串,伺服器端將收到是data值為字串'{“temp”:20}’,因為此字串符合JSON格式,你可以將它交給Node的JSON.parse()解析。

      thanks,
      jeffrey

  33. 老師 您好:

    不好意思,這部分我還是不太了解,我整合上面提問的程式如下,再麻煩您幫我看看謝謝。

    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] + \',\';
    }
    if (client.connect(server, 5438)) {
    client << \"GET /t?data={\\"ax\\":\" << str (陣列轉換的字串)
    << \"} HTTP/1.1\r\n\";
    client.println();
    Node程式
    app.get(\"/t\", function(req, res) {
    var data = req.query.str; //
    if (data != undefined) {
    data = data.substring(0,data.length-1);
    var arr = data.split(\',\');
    console.log(\"資料1:\" + arr[1]);
    } else {
    console.log(\"沒收到資料!\");
    }
    res.status(200).send(\'ok!\');

    1. 你的目的是透過查詢字串傳送資料給網站伺服器,查詢字串並不需要特意包裝成JSON格式,維持一般字串格式即可:

      client << "GET /t?data=" << str (陣列轉換的字串) << " HTTP/1.1\r\n\";
      

      如此,從前端送出的URL網址將類似:

      http://192.168.1.19/t?data=123,456,789,
      

      thanks,
      jeffrey

  34. 老師 您好:
    依照您的指示將程式修改為如下:
    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] + \',\';
    }
    if (client.connect(server, 5438)) {
    client << "GET /t?data=" << str <<" HTTP/1.1\r\n";
    Node程式
    app.get("/t", function(req, res) {
    var data = req.query.str;
    if (data != undefined) {
    data = data.substring(0,data.length-1);
    var arr = data.split(',');
    console.log("資料1:" + arr[1]);
    } else {
    console.log("沒收到資料!");
    }
    res.status(200).send('ok!');
    });
    結果:data的值為undefined,因此沒收到資料,不知Node端程式有無錯誤

    1. 拍謝,剛剛實機測試,發現問題出在特殊字元沒有經過URL編碼。像’,’在傳送之前,必須先轉換成%2C。

      我從ESP8266 Arduino專案的這個urlencode範例,複製底下的處理URL編碼的函式:

      String urlencode(String str) {
          String encodedString="";
          char c;
          char code0;
          char code1;
          char code2;
          for (int i =0; i < str.length(); i++){
            c=str.charAt(i);
            if (c == ' '){
              encodedString+= '+';
            } else if (isalnum(c)){
              encodedString+=c;
            } else{
              code1=(c & 0xf)+'0';
              if ((c & 0xf) >9){
                  code1=(c & 0xf) - 10 + 'A';
              }
              c=(c>>4)&0xf;
              code0=c+'0';
              if (c > 9){
                  code0=c - 10 + 'A';
              }
              code2='\0';
              encodedString+='&';
              encodedString+=code0;
              encodedString+=code1;
            }
          }
          return encodedString;
      }
      

      如此,傳送字串之前先執行上面的函式,web伺服器程式就能收到正確的資料了:

      str = urlencode(str);
      client << "GET /data?str=" << str << " HTTP/1.1\r\n";
      

      thanks,
      jeffrey

  35. 老師您好
    在課本6-3頁,先執行指令如下
    npm install nodemailer

    再用光碟裡面第六章的程式 mail_1.js來執行,裡面已經改好Gmail的帳號和密碼,但是出現如下訊息

    pi@raspberrypi:~/test2 $ node mail_1.js
    Error: No transport method defined
    at /home/pi/test2/node_modules/nodemailer/lib/nodemailer.js:181:29
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickDomainCallback (internal/process/next_tick.js:122:9)
    at Module.runMain (module.js:607:11)
    at run (bootstrap_node.js:420:7)
    at startup (bootstrap_node.js:139:9)
    at bootstrap_node.js:535:3

    請問是nodemailer模組被修改了嗎? 因為之前是可以寄信的,煩請老師幫忙指導一下,感激不盡

    1. 錯誤訊息顯示的是「沒有定義transport」方法,我明天測試看看。

      [6.21更新] 剛剛測試書本裡的範例程式寄送Gmail沒有問題。但是如果安裝最新的nodemail模組,Node.js必須是6.0版以上,執行書本的程式會發生一個”SMTP”字串的錯誤,將它(和後面的’,’)從createTransport()刪除,也就是把原本的:

      var smtpTransport = nodemailer.createTransport(“SMTP”,{

      改成:

      var smtpTransport = nodemailer.createTransport({

      就沒問題了。你也可以透過書本範例的package.json檔安裝相容NNodejs 4.x的模組版本。

      thanks,
      jeffrey

  36. 老師您好
    有沒有什麼方法可以用google之類的免費服務在網路上發布JSON格式檔案?

    1. 如果你的JSON文件是靜態的(亦即,內容固定不變),可以將它上傳到免費雲端硬碟(如:dropbox),再分享連結即可,因為它的內容是純文字。

      thanks,
      jeffrey

  37. 老師 打擾了,在本篇文章看到您實做JsonHttpClient取天氣資料案例,個人也去把JsonHttpClient這份檔案下載下來,而目前只進行固定ip修改,且想說上傳先了解印出的資料如何,但是序列埠監控視窗會拋出: “Session.connect: java.io.IOException: End of IO Stream Read”,在監控視窗內會顯示: “連接不成功: 正在重試 (0)… ”。使用開發版:Arduino YUN,以下是上傳的草稿碼以及錯誤資訊,謝謝:

    // Sample Arduino Json Web Client
    // Downloads and parse http://jsonplaceholder.typicode.com/users/1
    //
    // Copyright Benoit Blanchon 2014-2016
    // MIT License
    //
    // Arduino JSON library
    // https://github.com/bblanchon/ArduinoJson
    // If you like this project, please add a star!

    #include
    #include
    #include

    EthernetClient client;

    const char* server = “jsonplaceholder.typicode.com”; // server’s address
    const char* resource = “/users/1”; // http resource
    const unsigned long BAUD_RATE = 9600; // serial connection speed
    const unsigned long HTTP_TIMEOUT = 10000; // max respone time from server
    const size_t MAX_CONTENT_SIZE = 512; // max size of the HTTP response

    // The type of data that we want to extract from the page
    struct UserData {
    char name[32];
    char company[32];
    };

    // ARDUINO entry point #1: runs once when you press reset or power the board
    void setup() {
    initSerial();
    initEthernet();
    }

    // ARDUINO entry point #2: runs over and over again forever
    void loop() {
    if (connect(server)) {
    if (sendRequest(server, resource) && skipResponseHeaders()) {
    char response[MAX_CONTENT_SIZE];
    readReponseContent(response, sizeof(response));

    UserData userData;
    if (parseUserData(response, &userData)) {
    printUserData(&userData);
    }
    }
    disconnect();
    }
    wait();
    }

    // Initialize Serial port
    void initSerial() {
    Serial.begin(BAUD_RATE);
    while (!Serial) {
    ; // wait for serial port to initialize
    }
    Serial.println(“Serial ready”);
    }

    // Initialize Ethernet library
    void initEthernet() {
    byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
    IPAddress ip(192,168,1,106);
    Ethernet.begin(mac, ip);
    if (!Ethernet.begin(mac)) {
    Serial.println(“Failed to configure Ethernet”);
    return;
    }
    Serial.println(“Ethernet ready”);
    delay(1000);
    }

    // Open connection to the HTTP server
    bool connect(const char* hostName) {
    Serial.print(“Connect to “);
    Serial.println(hostName);

    bool ok = client.connect(hostName, 80);

    Serial.println(ok ? “Connected” : “Connection Failed!”);
    return ok;
    }

    // Send the HTTP GET request to the server
    bool sendRequest(const char* host, const char* resource) {
    Serial.print(“GET “);
    Serial.println(resource);

    client.print(“GET “);
    client.print(resource);
    client.println(” HTTP/1.1″);
    client.print(“Host: “);
    client.println(server);
    client.println(“Connection: close”);
    client.println();

    return true;
    }

    // Skip HTTP headers so that we are at the beginning of the response’s body
    bool skipResponseHeaders() {
    // HTTP headers end with an empty line
    char endOfHeaders[] = “\r\n\r\n”;

    client.setTimeout(HTTP_TIMEOUT);
    bool ok = client.find(endOfHeaders);

    if (!ok) {
    Serial.println(“No response or invalid response!”);
    }

    return ok;
    }

    // Read the body of the response from the HTTP server
    void readReponseContent(char* content, size_t maxSize) {
    size_t length = client.readBytes(content, maxSize);
    content[length] = 0;
    Serial.println(content);
    }

    // Parse the JSON from the input string and extract the interesting values
    // Here is the JSON we need to parse
    // {
    // “id”: 1,
    // “name”: “Leanne Graham”,
    // “username”: “Bret”,
    // “email”: “Sincere@april.biz”,
    // “address”: {
    // “street”: “Kulas Light”,
    // “suite”: “Apt. 556”,
    // “city”: “Gwenborough”,
    // “zipcode”: “92998-3874”,
    // “geo”: {
    // “lat”: “-37.3159”,
    // “lng”: “81.1496”
    // }
    // },
    // “phone”: “1-770-736-8031 x56442”,
    // “website”: “hildegard.org”,
    // “company”: {
    // “name”: “Romaguera-Crona”,
    // “catchPhrase”: “Multi-layered client-server neural-net”,
    // “bs”: “harness real-time e-markets”
    // }
    // }
    bool parseUserData(char* content, struct UserData* userData) {
    // Compute optimal size of the JSON buffer according to what we need to parse.
    // This is only required if you use StaticJsonBuffer.
    const size_t BUFFER_SIZE =
    JSON_OBJECT_SIZE(8) // the root object has 8 elements
    + JSON_OBJECT_SIZE(5) // the “address” object has 5 elements
    + JSON_OBJECT_SIZE(2) // the “geo” object has 2 elements
    + JSON_OBJECT_SIZE(3); // the “company” object has 3 elements

    // Allocate a temporary memory pool on the stack
    StaticJsonBuffer jsonBuffer;
    // If the memory pool is too big for the stack, use this instead:
    // DynamicJsonBuffer jsonBuffer;

    JsonObject& root = jsonBuffer.parseObject(content);

    if (!root.success()) {
    Serial.println(“JSON parsing failed!”);
    return false;
    }

    // Here were copy the strings we’re interested in
    strcpy(userData->name, root[“name”]);
    strcpy(userData->company, root[“company”][“name”]);
    // It’s not mandatory to make a copy, you could just use the pointers
    // Since, they are pointing inside the “content” buffer, so you need to make
    // sure it’s still in memory when you read the string

    return true;
    }

    // Print the data extracted from the JSON
    void printUserData(const struct UserData* userData) {
    Serial.print(“Name = “);
    Serial.println(userData->name);
    Serial.print(“Company = “);
    Serial.println(userData->company);
    }

    // Close the connection with the HTTP server
    void disconnect() {
    Serial.println(“Disconnect”);
    client.stop();
    }

    // Pause for a 1 minute
    void wait() {
    Serial.println(“Wait 60 seconds”);
    delay(60000);
    }

    Arduino:1.8.5 (Mac OS X), 開發板:”Arduino Yún”

    草稿碼使用了 16992 bytes (59%) 的程式儲存空間。上限為 28672 bytes。
    全域變數使用了 875 bytes (34%) 的動態記憶體,剩餘 1685 bytes 給區域變數。上限為 2560 bytes 。

    avrdude: AVR device initialized and ready to accept instructions
    avrdude: Device signature = 0x1e9587
    avrdude: NOTE: “flash” memory has been specified, an erase cycle will be performed
    To disable this feature, specify the -D option.
    avrdude: erasing chip
    avrdude: reading input file “0xFF”
    avrdude: writing lfuse (1 bytes):
    avrdude: 1 bytes of lfuse written
    avrdude: verifying lfuse memory against 0xFF:
    avrdude: load data lfuse data from input file 0xFF:
    avrdude: input file 0xFF contains 1 bytes
    avrdude: reading on-chip lfuse data:
    avrdude: verifying …
    avrdude: 1 bytes of lfuse verified
    avrdude: reading input file “0xD8”
    avrdude: writing hfuse (1 bytes):
    avrdude: 1 bytes of hfuse written
    avrdude: verifying hfuse memory against 0xD8:
    avrdude: load data hfuse data from input file 0xD8:
    avrdude: input file 0xD8 contains 1 bytes
    avrdude: reading on-chip hfuse data:
    avrdude: verifying …
    avrdude: 1 bytes of hfuse verified
    avrdude: reading input file “0xFB”
    avrdude: writing efuse (1 bytes):
    avrdude: 1 bytes of efuse written
    avrdude: verifying efuse memory against 0xFB:
    avrdude: load data efuse data from input file 0xFB:
    avrdude: input file 0xFB contains 1 bytes
    avrdude: reading on-chip efuse data:
    avrdude: verifying …
    avrdude: 1 bytes of efuse verified
    avrdude: reading input file “/tmp/sketch.hex”
    avrdude: writing flash (32748 bytes):
    avrdude: 32748 bytes of flash written
    avrdude: verifying flash memory against /tmp/sketch.hex:
    avrdude: load data flash data from input file /tmp/sketch.hex:
    avrdude: input file /tmp/sketch.hex contains 32748 bytes
    avrdude: reading on-chip flash data:
    avrdude: verifying …
    avrdude: 32748 bytes of flash verified

    avrdude done. Thank you.

    com.jcraft.jsch.JSchException: Session.connect: java.io.IOException: End of IO Stream Read
    at com.jcraft.jsch.Session.connect(Session.java:557)
    at processing.app.NetworkMonitor.open(NetworkMonitor.java:73)
    at processing.app.AbstractMonitor.resume(AbstractMonitor.java:104)
    at processing.app.Editor.resumeOrCloseSerialMonitor(Editor.java:2223)
    at processing.app.Editor.access$2200(Editor.java:79)
    at processing.app.Editor$DefaultExportHandler.run(Editor.java:2196)
    at java.lang.Thread.run(Thread.java:748)
    Session.connect: java.io.IOException: End of IO Stream Read
    com.jcraft.jsch.JSchException: session is down
    at com.jcraft.jsch.Channel.sendChannelOpen(Channel.java:667)
    at com.jcraft.jsch.Channel.connect(Channel.java:151)
    at com.jcraft.jsch.Channel.connect(Channel.java:145)
    at processing.app.NetworkMonitor.tryConnect(NetworkMonitor.java:98)
    at processing.app.NetworkMonitor.access$100(NetworkMonitor.java:25)
    at processing.app.NetworkMonitor$3.run(NetworkMonitor.java:139)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.run(EventQueue.java:703)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

    This report would have more information with
    “Show verbose output during compilation”
    option enabled in File -> Preferences.

  38. 趙老師你好:
    在《超圖解物聯網IoT實作入門:第4章動手做note.js序列埠通訊4-8~4-12頁,透過USB將Arduino和樹莓派4連線, Arduino透過ttyUSB0可順利上傳在監控視窗看到hello!的字樣, 可是執行node serialTest.js卻一直出現錯誤的訊息,如下
    internal/modules/cjs/loader 208
    throw e ;
    SyntaxError: Error prasing /home/pi/node/node-serialport/package/serialport/package.json: Unexpected string in JSON at position 90
    at JSON.parse()
    at readPackage(internal/modules/cjs/loader.js:204:49);
    at tryPackage(internal/modules/cjs/loader.js:213:13)
    at Function.Module._findPath((internal/modules/cjs/loader.js:339:18)
    at Function.Module_resolveFilename((internal/modules/cjs/loader.js:632:25)
    at Function.Module_load(internal/modules/cjs/loader.js:204:49)
    at Module.require(internal/modules/cjs/loader.js: 692:17)
    at require(internal/modules/cjs/helper.js:25:18)
    at Object.(/home/pi/node/ch4/nodeSerial/serialTest.js:1:12)
    at Module._compile(internal/modules/cjs/loader.js:778:30)
    ….
    可否幫助學生解困惑?

    1. 我在Winddows 10上測試沒問題,但後來我把node.js的serialport模組更新到9.x版,就出現語法不相容的問題。

      這是package.json裡的serialport版本設定:

      "dependencies": {
        "serialport": "^9.0.0"
      }
      

      serialTest.js檔要改成:

      const SerialPort = require("serialport");
      const Readline = SerialPort.parsers.Readline;  // 改用Readline類別
      
      const port = new SerialPort("COM10", {   // 在樹莓派,序列埠請改成"/dev/ttyACM0"
        baudRate: 115200,                      // 我把Arduino的序列埠鮑率改成115200
        parser: new Readline("\n")
      });
      
      port.on("open", function () {
        console.log("已開啟序列埠");
      
        port.on("data", function (d) {
          console.log("資料:" + d);
        });
      });
      

      同樣地,若改用新版的serialport模組,serialOnOff.js檔也要修改:

      var stdin = process.stdin;
      
      const SerialPort = require("serialport");
      const port = new SerialPort("/dev/ttyACM0", {
        baudRate: 115200
      });
      
      port.on("open", function (error) {
        process.stdout.write('請輸入on或off開、關燈。\n');
      
        port.on("data", function (d) {
          console.log("Arduino回應:" + d);
        });
      
        stdin.on('data', function (key) {
          var str = key.toString().toLowerCase().trim();
      
          if (str == 'on') {
            process.stdout.write('開燈\n');
            port.write('1');
          }
      
          if (str == 'off') {
            process.stdout.write('關燈\n');
            port.write('0');
          }
        })
      });
      

      剛剛在樹莓派上測試執行無誤。

      thanks,
      jeffrey

  39. 趙老師你好
    学生仍有连系问题如下:
    pi@raspberrypi:~/node/ch4/nodeSerial $ node serialTest.js
    internal/modules/cjs/loader.js:208
    throw e;
    ^

    SyntaxError: Error parsing /home/pi/node/node-serialport/packages/serialport/package.json: Unexpected string in JSON at position 91
    at JSON.parse ()
    :
    请问会与目录路近有关系吗?serialport 需要与serialTest.js同路近,或在serialTest.js>>
    const SerialPort = require(“/home/pi/node/node-serialport/packages/serialport”);
    Thank you !

    1. package.json檔的語法說明,請參閱3-44頁。

      請先按照4-9頁的說明,在專案路徑中執行npm install,測試現有的範例檔,成功之後,再從範例檔著手修改。

      thanks,
      jeffrey

  40. 趙老師你好
    學生改在windows10測試,
    1.按照建議在node目錄下建立package.json
    {
    “name”:”serialTest”,
    “version”: “0.0.1”,
    “dependencies”: {
    “serialport”: “^9.0.0”
    }
    }
    2.並且在該目錄下安裝npm install :出現以下訊息
    > @serialport/bindings@9.0.2 install C:\node\node_modules\@serialport\bindings
    > prebuild-install –tag-prefix @serialport/bindings@ || node-gyp rebuild

    ‘”node”‘ 不是內部或外部命令、可執行的程式或批次檔。
    ‘node-gyp’ 不是內部或外部命令、可執行的程式或批次檔。
    npm WARN serialTest@0.0.1 No description
    npm WARN serialTest@0.0.1 No repository field.
    npm WARN serialTest@0.0.1 No license field.
    :

    3. serialTest.js改成:
    const SerialPort = require(“serialport”);
    const Readline = SerialPort.parsers.Readline;

    const port = new SerialPort(“COM3”, {
    baudrate: 115200,
    parser: new Readline(“\n”)
    });
    Port.on(“open”, function(){
    console.log(“已開啟序列埠”);

    Port.on(“data”, function(d){
    console.log(“資料:” + d); // 顯示傳入序列埠的資料
    });
    });
    但還是出現如下訊息:
    internal/modules/cjs/loader.js:834
    throw err;
    ^

    Error: Cannot find module ‘serialport’

    謝謝回覆

    1. 1. 這個錯誤訊息代表系統找不到node.js:”node”‘ 不是內部或外部命令、可執行的程式或批次檔。
      請先測試在專案路徑中執行node -v,會不會出現版本訊息?如果沒有,就代表node.js沒有加入系統路徑(參閱3-5頁最後一段說明)。

      2. 最後一個錯誤訊息:Error: Cannot find module ‘serialport’代表找不到serialport模組。
      因為上一個步驟無法順利執行,所以專案資料夾裡面當然沒有serialport模組。

      thanks,
      jeffrey

發佈留言

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

Related Posts

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

Back To Top