建立Arduino的Socket即時通訊程式(二)

延續上一篇貼文,本單元的程式修改自Socket.io-v1.x-Library程式庫的“Hello_time”範例,Arduino和Node.js的socket.io建立連線之後,將每隔5秒發送一個事件訊息詢問Node.js目前的時間,Node.js將在收到訊息之後回覆一個事件訊息給Arduino。

Arduino與Node.js的socket訊息交換

實驗材料:

  • Arduino Uno板 × 1
  • 採用W5100晶片的乙太網路擴展板 × 1
  • 執行Node.js的電腦或控制板(如:樹梅派) × 1

Arduino Uno + W5100乙太網路卡 + Raspberry Pi

撰寫Arduino的Socket即時通訊程式

Socket.io-v1.x-Library程式庫支援三種網路晶片,每個晶片都需要引用不同的程式庫,因此,SocketIOClient.h檔案透過#ifdef(代表“if defined”,「若有定義」)和#endif(代表“end if”,「結束if區塊」)前置處理指令,來決定要引用哪些程式庫:

#ifdef和$endif處理指令

所以,Arduino程式必須依照網路晶片類型,在開頭使用#define指令定義W5100, ESP8266或者ENC28J60。本文採用W5100晶片的乙太網路擴展板,程式開頭需要先定義W5100(後面不需要設定任何值),再引用SocketIOClient.h程式庫:

使用#define指令定義W5100

接著宣告SocketIO的程式物件,以及一些變數:

使用extern關鍵字宣告的變數,代表該變數存在於外部程式檔,在程式編譯過程中,編譯器會自行找尋定義該變數的程式檔。

client物件的connect()方法用於連結遠端伺服器,在底下的setup()函式中,程式將在連結成功之後,發送一則socket訊息:

setup()函式

使用F()函式將字串存入Flash記憶體

上面程式裡的序列輸出字串敘述,採用F()函式包裝字串。F()函式能把字串存入Flash(程式記憶體)區域,底下兩個敘述都會在「序列埠監控視窗」輸出“Hello!”:

用F()函式包裝字串

差別在於左邊的寫法,字串會佔用主記憶體,右邊的寫法不會。

在loop()函式中監聽是否有新的Socket訊息

底下的程式將每隔5秒,透過socket送出“Time please?”(請問時間)訊息給Node.js伺服器程式,並且在每一次loop()迴圈中,執行client.monitor()看看是否有新的訊息:

loop()函式

如果有新的訊息,即可從RID, Rname和Rcontent這三個外在(extern)變數,取得socket的事件名稱、訊息名稱和訊息內容

完整的Arduino程式碼如下:

Node.js伺服器端的socket.io程式

本單元的Node.js伺服器端程式改自書本第5章的chat.js程式(位於ch5的socket資料夾),因為該專案路徑已經包含socket.io套件。完整的Node.js程式碼如下:

需要補充說明的是new Date().toJSON()這個敘述,toJSON()方法會把日期物件資料轉換成ISO標準日期格式的簡短字串:

new Date().toJSON()

筆者將此Node程式命名成arduinoSocket.js存入ch5的socket資料夾,先執行此Node程式,再執行Arduino的socket程式,Node伺服器端的命令列(終端機)視窗將出現如下的訊息:

Node伺服器端的命令列(終端機)視窗

Arduino的「序列埠監控視窗」將呈現如下的訊息:

序列埠監控視窗

延伸閱讀

12 thoughts on “建立Arduino的Socket即時通訊程式(二)

  1. 趙老師您好:

    書中5-30頁,Node.js是作網頁伺服器,瀏覽器為用戶端,兩者用socket.io建立即時連線。

    而在這個範例中,老師用Node.js是作網頁伺服器,Arduino UNO+W5100及ESP8266當作用戶端,兩者用Socket.io-v1.x-Library程式庫及socket.io建立即時連線。

    如果Arduino UNO+W5100及ESP8266當作網頁伺服器,瀏覽器為用戶端,兩者用Socket.io-v1.x-Library程式庫及socket.io建立即時連線,程式該如何撰寫呢?

    1. hi alex:

      Socket.io-v1.x-Library程式庫是前端,後端你可以用ESPSocket程式庫,我看了一下它的範例程式,有包含網頁檔案,也具備上傳檔案到快閃記憶體的功能,提供你參考,謝謝!

      thanks,
      jeffrey

  2. 趙老師你好:我不知道我哪裡做錯了,一直無法編譯過,以下是錯誤的訊息,是否能幫助我一下呢?

    In file included from /Users/eagle/Desktop/test_socket/test_socket.ino:2:0:
    /Users/eagle/Documents/Arduino/libraries/Socket.io-v1.x-Library-master/SocketIOClient.h:40:42: fatal error: ESP8266WiFi.h: No such file or directory
    #include //For ESP8266
    ^
    compilation terminated.
    exit status 1
    編譯時發生錯誤

    1. 本文的範例程式適用於W5100晶片的乙太網路卡,請問你有依照上文說明,修改SocketIOClient.h檔嗎?

      thanks,
      jeffrey

  3. Hello CUBIE!
    謝謝您精彩的補充教材!請問採用GPRS實現socket通訊合適嗎?Socket.io-v1.x-Library只支援三種晶片,如果想用Arduino+SIM900模組實現socket請問有推薦的library嗎?

    1. 拍謝,我沒用過GPRS模組。不過,GSM的通訊協定與Arduino程式庫都和乙太網路不同,即便是普通的Web程式也無法直接相互移植。

      thanks,
      jeffrey

  4. 老師您好 前篇的更改.h已經更改過了,但我編譯上面的ARDUINO程式,一直編譯失敗,會出現下面問題
    In file included from sketch_mar30a.ino:4:0:
    C:\Users\User1\Documents\Arduino\libraries\SocketIOClient/SocketIOClient.h:30:22: fatal error: Ethernet.h: No such file or directory
    #include
    ^
    compilation terminated.

    1. 請在你的程式碼開頭加入底下的敘述測試看看,我在1.8.1版編譯Arduino Uno程式沒問題。

      thanks,
      jeffrey

  5. 老師您好:
    文中未提到Ethernet模組連接電腦的方式,
    請問老師是利用什麼樣的環境連結的?node js伺服器的IP是利用固定IP來接收的?

    1. 通常用來當做伺服器端的設備, 都會設定成固定IP. 因為當你需要它的時候, 如果它在固定的位置, 你很容易的就能在同一個地方找到它.
      固定IP還可以分成二種類型, 一種是虛擬IP(常見的為:192.168.X.X, 用於區域網路), 另一種為實體IP(例如:61.219.146.X, 用於外部網路).
      區域網路的概念你可以把它想像成一棟大樓, 對大樓管理員而言, 假設你跟管理員說東西要交給B棟 9F(這個相當於虛擬IP)的潘先生, 他可以很輕易的知道東西要交到本大樓第二棟9F的住戶手裡; 但如果是跟外面的路人說要把東西交給B棟 9F的潘先生, 路人會一頭霧水, 是哪一棟大樓的B棟 9F?
      如果要讓路人也能知道正確的位置, 你必需要跟他講一個實際的地址(例如:XX市XX路378號9F, 這個就是實體IP), 這樣才能找到實際的位置.
      至於node.js伺服器要使用虛擬IP還是實體IP, 就要看你的需求而定了~~~

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *