在物聯網 (IoT)、互動設計 或 遊戲開發 專案中,將硬體感測資料即時整合到軟體應用中,是實現豐富互動體驗的關鍵。Arduino 作為一款開源硬體平台,搭配各種感測器能提供豐富的物理世界資料。而要將這些資料高效且有結構地傳輸到 Unity 或 TouchDesigner 等軟體平台,JSON (JavaScript Object Notation) 格式是一個極佳的選擇。
JSON 是一種輕量級的資料交換格式,易於人閱讀和撰寫,也易於機器解析和生成。本文將詳細說明如何利用 ArduinoJson 套件,將 Arduino 感測資料序列化為 JSON 字串,透過 Serial (序列埠) 傳輸,並在 Unity 與 TouchDesigner 中成功反序列化,最終應用於你的遊戲或互動專案。
安裝 ArduinoJson 套件
要讓 Arduino 能夠處理 JSON 格式的資料,我們需要安裝 ArduinoJson 套件。
- 開啟 Arduino IDE。點擊左側工具列的「函式庫管理員 (Library Manager)」按鈕(通常是一個書架圖示)。
- 在 Library Manager 搜尋欄中輸入「ArduinoJson」。
- 找到由 Benoit Blanchon 開發的「ArduinoJson」套件。點擊其右側的「安裝 (Install)」按鈕,等待安裝完成。
撰寫 Arduino 程式碼
發送 JSON 字串資料
準備好 ArduinoJson 套件後,我們就可以開始撰寫 Arduino 程式碼,模擬即時感測資料並將其包裝成 JSON 格式字串,然後透過 Serial 埠印出。
首先,在程式碼開頭引入 ArduinoJson.h
函式庫,並建立一個 JsonDocument
變數 doc
。這個 doc
物件將用來儲存我們組織的 JSON 資料結構。
1#include "ArduinoJson.h";
2
3JsonDocument doc;
在 setup()
函式中,啟動 Serial (序列埠) 通訊。Serial.begin()
的 Baud Rate(鮑率)設定為 9600。你可以根據專案需求調整此數值,但請確保 Arduino 與接收端(Unity 或 TouchDesigner)的 Baud Rate 設定一致。
1void setup() {
2 Serial.begin(9600);
3}
在 loop()
函式中,我們要建立一個 String
型別的 dataName
變數和一個 int
型別的 dataValue
變數,用來模擬感測資料。dataName
的內容固定為 "testData"
,而 dataValue
則會產生 0 到 99 之間的隨機整數,模擬即時變化的感測器讀數。
1void loop() {
2 String dataName = "testData";
3 int dataValue = random(0, 100);
4}
接著,我們將 dataName
和 dataValue
分別存入 doc
物件中的 name
和 value
兩個鍵 (Key)。JsonDocument
的操作方式類似於 C# 的字典 (Dictionary),方便地用鍵值對 (Key-Value Pair) 的形式組織資料。
1void loop() {
2 String dataName = "testData";
3 int dataValue = random(0, 100);
4
5 doc["name"] = dataName;
6 doc["value"] = dataValue;
7}
最後,使用 serializeJson()
函式將 doc
物件的內容序列化為 JSON 字串,並透過 Serial
物件印出。為了確保每一條 JSON 訊息都是獨立的一行,我們強制加入新的一行符號 (Serial.println()
)。同時,設定 200 毫秒的延遲 (delay(200)
),控制資料發送頻率。
1void loop() {
2 String dataName = "testData";
3 int dataValue = random(0, 100);
4
5 doc["name"] = dataName;
6 doc["value"] = dataValue;
7
8 serializeJson(doc, Serial);
9 Serial.println();
10 delay(200);
11}
將以上所有程式碼整合,完整的 Arduino 程式碼如下所示 (原始碼下載):
1#include "ArduinoJson.h";
2
3JsonDocument doc;
4
5void setup() {
6 Serial.begin(9600);
7}
8
9void loop() {
10 String dataName = "testData";
11 int dataValue = random(0, 100);
12
13 doc["name"] = dataName;
14 doc["value"] = dataValue;
15
16 serializeJson(doc, Serial);
17 Serial.println();
18 delay(200);
19}
將這段程式碼燒錄至你的 Arduino 板後,開啟 Arduino IDE 的「序列埠監控視窗 (Serial Monitor)」。你會看到程式持續地印出類似 {"name":"testData","value":76}
格式的 JSON 字串。
其中,value
的數字會不斷變化,這代表你的 Arduino 已成功開始發送 JSON 格式的感測資料。
除了從 Arduino 發送 JSON 資料到 Unity 或 TouchDesigner,有時我們也需要反向操作:讓 Arduino 接收外部應用程式(例如 Unity 或 TouchDesigner)傳來的 JSON 字串,並從中解析出所需的控制指令或參數。這對於實現更豐富的雙向互動至關重要。
接收 JSON 字串資料
首先,我們需要在程式碼中準備一個緩衝區 (buffer) 來暫存從序列埠陸續接收到的字元,直到完整的一條 JSON 訊息抵達。
在剛才建立的 JsonDocument
變數 doc
的部分,新增一個型別為 String
的變數 buffer
。這個 buffer
將用來存放外部透過 Serial 埠輸入的 JSON 字串資料。
1#include "ArduinoJson.h";
2
3JsonDocument doc;
4String buffer;
接著,我們要建立一個名為 ParseJSON
的函式。這個函式將負責接收一個字串參數 raw
(即完整的 JSON 字串),並從中提取我們需要的 name
和 value
資料。
在 ParseJSON
函式內部,我們透過 deserializeJson()
函式將 raw
這個 JSON 字串反序列化(解析)為可操作的 JsonDocument
物件 doc
。一旦解析成功,我們就可以像操作字典 (Dictionary) 一樣,透過鍵名(例如 "name"
和 "value"
)來取出對應的值。這裡我們將 name
取為 const char*
型別,value
取為 long
型別,以確保資料型別的兼容性。
為了方便觀察解析結果,我們在提取資料後,立即將 name
和 value
的內容透過 Serial
埠印出來。
1void ParseJSON(String raw){
2 deserializeJson(doc, raw);
3
4 const char* name = doc["name"];
5 long value = doc["value"];
6
7 Serial.print("name: ");
8 Serial.println(name);
9 Serial.print("value: ");
10 Serial.println(value);
11}
最後,我們需要修改 loop()
函式的內容,使其同時具備發送與接收 JSON 資料的能力。loop()
將會持續檢查 Serial 埠是否有資料輸入,如果沒有,則繼續執行原有的發送 JSON 資料的邏輯。
1void loop() {
2 if (Serial.available() > 0) {
3 char msg = Serial.read();
4
5 if (msg == '\n'){
6 ParseJSON(buffer);
7 buffer = "";
8 }
9 else {
10 buffer += msg;
11 }
12 }
13 else {
14 String dataName = "testData";
15 int dataValue = random(0, 100);
16
17 doc["name"] = dataName;
18 doc["value"] = dataValue;
19
20 serializeJson(doc, Serial);
21 Serial.println();
22 }
23}
這個修改後的 loop()
函式實現了一個簡單的雙向通訊機制:當有資料從 Serial 埠傳入時,它會優先處理接收和解析。如果沒有接收到資料,它則會繼續執行既有的隨機資料發送任務。這使得你的 Arduino 裝置能夠更彈性地與外部應用進行互動和資料交換 (原始碼下載)。
另外,因為程式的執行速度很快,我們可另外用 delay()
函式來減慢執行速度,以方便觀察改動結果。
完整的 Arduino 用 Serial 與 JSON 實現的雙向溝通程式碼如下:
1#include "ArduinoJson.h";
2
3JsonDocument doc;
4String buffer;
5
6void setup() {
7 Serial.begin(9600);
8}
9
10void loop() {
11 if (Serial.available() > 0) {
12 char msg = Serial.read();
13
14 if (msg == '\n'){
15 ParseJSON(buffer);
16 buffer = "";
17 }
18 else {
19 buffer += msg;
20 }
21 }
22 else {
23 // 生成與發送模擬資料 (要發送的感測資料寫在這邊)
24 String dataName = "testData";
25 int dataValue = random(0, 100);
26
27 doc["name"] = dataName;
28 doc["value"] = dataValue;
29
30 serializeJson(doc, Serial);
31 Serial.println();
32 }
33}
34
35void ParseJSON(String raw){
36 deserializeJson(doc, raw);
37
38 // 接收與解析模擬資料 (要執行接收命令的功能寫在這邊)
39 const char* name = doc["name"];
40 long value = doc["value"];
41
42 Serial.print("name: ");
43 Serial.println(name);
44 Serial.print("value: ");
45 Serial.println(value);
46}
接下來,我們將展示如何在 Unity 和 TouchDesigner 中接收、解析、編碼、發送 JSON 資料,來和 Arduino 進行雙向溝通。
Unity 整合程式設計
接收 JSON 感測資料
在 Unity 中接收 Arduino 透過 Serial 傳輸的 JSON 資料,我們可以利用第三方套件來簡化開發流程。這裡我們推薦使用改良過的 Ardity 套件,它能方便地處理 Serial 埠通訊。
- 啟動你的 Unity 專案。在頂部選單中,點擊
Edit > Project Settings
。 - 在
Project Settings
視窗中,展開「Player」選項,然後點擊「Other Settings」。在「Configuration」區塊下,將「Api Compatibility Level」設定為.NET Framework
。這是確保 Unity 能夠正確解析 JSON 和使用某些 .NET 函式庫的必要設定。
- 接下來,從頂部選單中點擊
Window > Package Manager
。 - 在 Package Manager 視窗的左上角,點擊
+
按鈕,然後選擇「Install package from git URL…」。
- 在彈出的輸入框中,複製並貼上下方的 Ardity 套件的 GitHub URL,然後點擊「Add」進行安裝。請注意,Unity 從 GitHub 下載套件需要你的電腦預先安裝 Git。如果尚未安裝 Git,請先完成安裝並重新啟動電腦以確保環境變數生效。
1https://github.com/tedliou/Ardity.git?path=Packages/ardity#2.0.1
Ardity 套件提供了一個方便的
SerialController
Prefab,能快速建立與 Arduino 的 Serial 連線。在 Unity 的 Project 面板中,點擊右上角的「選項 (Options)」按鈕(通常是三個點或一個齒輪圖示),選擇「Show Package Folders」來顯示隱藏的套件檔案。展開
Packages
資料夾,找到Ardity > Prefabs
,然後將SerialController
Prefab 拖曳到你的場景 (Hierarchy) 中。選取場景中的
SerialController
物件。在 Inspector 面板中,你需要設定其屬性:- Port Name: 設定為你 Arduino 連接的序列埠名稱。Windows 通常為
COM
加上數字(例如COM3
),macOS 和 Linux 則為/dev/
開頭的字串(例如/dev/tty.usbserial-123
)。 - Baud Rate: 設定為
9600
,與 Arduino 程式碼中的Serial.begin()
設定一致。 - Max Unread Messages: 預設值為
10
。你可以根據資料量和處理速度適當調高此值,讓系統能一次緩衝和接收更多訊息,避免資料遺失。
- Port Name: 設定為你 Arduino 連接的序列埠名稱。Windows 通常為
- 現在,我們要建立一個 C# 腳本來接收
SerialController
傳來的 Arduino 訊息。在 Project 面板中,建立一個空的GameObject
和一個新的 C# 腳本,兩者都命名為ArduinoController
。將ArduinoController
腳本拖曳到你剛建立的ArduinoController
空物件上,將腳本附加到物件。
- 為了讓
SerialController
將接收到的訊息傳遞給ArduinoController
,你需要再次選取場景中的SerialController
物件。在 Inspector 面板中,找到Message Listener
欄位,然後將你場景中的ArduinoController
空物件拖曳到此欄位中。
- 最後,編輯
ArduinoController
腳本。在這個腳本中,我們將定義接收資料的結構,以及處理連線狀態和接收訊息的方法。- 建立一個
[System.Serializable]
的InputData
類別,其成員變數name
(string) 和value
(int) 需與 Arduino 發送的 JSON 鍵值("name"
和"value"
)完全對應。這個類別將用於 JSON 的反序列化。 - 實作
OnConnectionEvent(bool state)
方法,用於處理 Serial 連線的狀態變化。 - 實作
OnMessageArrived(string msg)
方法,這是接收 Arduino 訊息的核心。在此方法中,我們將使用JsonUtility.FromJson<InputData>(msg)
將接收到的 JSON 字串反序列化為InputData
物件,然後透過Debug.Log
印出其內容。
- 建立一個
1using UnityEngine;
2
3[System.Serializable]
4public class InputData {
5 public string name;
6 public int value;
7}
8
9public class ArduinoController : MonoBehaviour
10{
11 private void OnConnectionEvent(bool state)
12 {
13 Debug.Log($"連線狀態:{state}");
14 }
15
16 private void OnMessageArrived(string msg)
17 {
18 var data = JsonUtility.FromJson<InputData>(msg);
19 Debug.Log($"接收資料 {data.name}:{data.value}");
20 }
21}
執行 Unity 程式(點擊 Play 按鈕)。在 Unity 的 Console 視窗中,你將會持續看到 Arduino 傳來的 testData
和其隨機數值被印出 (原始碼下載)。
透過 data.name
和 data.value
,你就可以在 Unity 中獲取 Arduino 感測資料的名稱和數值,進而將其應用於 遊戲開發、互動設計、資料視覺化等用途。
發送 JSON 執行命令
若想要反過來向 Arduino 發送命令呢?我們來設計一個範例吧!
假設 Arduino 會持續發送帶有 name
與 value
的 JSON 資料,但現在 name
的內容將由 Unity 決定,藉此模擬模式切換或遠端指令下達的應用情境。
首先,我們需要修改 Arduino 的程式碼,以便它能夠接收來自 Unity 的 name
值,並將其應用到發送的數據中。主要的更動將集中在第 5、24 與 41 行。
1#include "ArduinoJson.h";
2
3JsonDocument doc;
4String buffer;
5String mode = "testData";
6
7void setup() {
8 Serial.begin(9600);
9}
10
11void loop() {
12 if (Serial.available() > 0) {
13 char msg = Serial.read();
14
15 if (msg == '\n'){
16 ParseJSON(buffer);
17 buffer = "";
18 }
19 else {
20 buffer += msg;
21 }
22 }
23 else {
24 String dataName = mode;
25 int dataValue = random(0, 100);
26
27 doc["name"] = dataName;
28 doc["value"] = dataValue;
29
30 serializeJson(doc, Serial);
31 Serial.println();
32 }
33}
34
35void ParseJSON(String raw){
36 deserializeJson(doc, raw);
37
38 const char* name = doc["name"];
39 long value = doc["value"];
40
41 mode = name;
42}
程式碼解析:
- 第 5 行 (
String mode = "testData";
):新增了一個String
型別的mode
變數,它將儲存當前 Arduino 要發送的name
內容。預設值為"testData"
。 - 第 24 行 (
String dataName = mode;
):在loop()
函式的發送邏輯中,dataName
現在直接取自mode
變數,而不是固定值。 - 第 41 行 (
mode = name;
):在ParseJSON
函式中,從接收到的 JSON 字串中解析出的name
值,會被直接賦值給mode
變數。這表示 Arduino 發送的name
將會根據接收到的命令而改變。
完成 Arduino 程式碼修改後,將其燒錄到你的 Arduino 板 (原始碼下載)。
接著,我們修改 Unity 中的 ArduinoController.cs
腳本,使其能夠向 Arduino 發送 JSON 格式的命令。我們將新增一個公開的 SerialController
變數,並在 Update()
方法中加入鍵盤輸入偵測,以便在按下空白鍵時發送數據。
1using UnityEngine;
2
3[System.Serializable]
4public class InputData {
5 public string name;
6 public int value;
7}
8
9public class ArduinoController : MonoBehaviour
10{
11 public SerialController serialController;
12
13 private void Update()
14 {
15 if (Input.GetKeyDown(KeyCode.Space))
16 {
17 var send = new InputData
18 {
19 name = $"Hello World! {Time.time}",
20 value = 999999999
21 };
22 var json = JsonUtility.ToJson(send);
23
24 serialController.SendSerialMessage(json);
25 }
26 }
27
28 private void OnConnectionEvent(bool state)
29 {
30 Debug.Log($"連線狀態:{state}");
31 }
32
33 private void OnMessageArrived(string msg)
34 {
35 var data = JsonUtility.FromJson<InputData>(msg);
36 Debug.Log($"接收資料 {data.name}:{data.value}");
37 }
38}
C# 程式碼解析:
- 第 11 行 (
public SerialController serialController;
):新增了一個公共變數serialController
,這允許我們從 Unity Inspector 視窗將場景中的SerialController
物件拖曳到此欄位,建立腳本與 Serial 控制器之間的連結。 - 第 13-26 行 (
private void Update() { ... }
):在Update
函式中,我們檢查KeyCode.Space
是否被按下。如果按下,則建立一個InputData
物件,其中name
包含了動態的時間戳記,然後將其序列化為 JSON 字串,並透過serialController.SendSerialMessage()
發送給 Arduino。
完成 C# 腳本修改後,回到 Unity Editor。你需要將場景中的 SerialController
物件拖曳到 ArduinoController
物件上,在 Inspector 視窗中,ArduinoController
腳本的 Serial Controller
欄位中。
最後,執行 Unity 程式。你會觀察到,每當你按下鍵盤上的空白鍵,Arduino 透過 Serial Monitor (或 Unity Console) 發送的 name
內容就會改變,顯示為類似 "Hello World! [時間戳記]"
的格式。這表示 Unity 成功向 Arduino 發送了命令,並且 Arduino 也成功接收並修改了其發送數據的模式 (原始碼下載)。
這個範例展示了如何實現 Unity 向 Arduino 發送 JSON 命令,進一步拓展了 Arduino 與 Unity 之間 雙向互動 的可能性。你可以將 InputData
的 name
或 value
用於控制 Arduino 上的 LED 亮滅、馬達轉速,或是切換不同的感測模式,實現更進階的 物聯網控制 與 實體運算 (Physical Computing) 應用。
TouchDesigner 互動媒體設計
接收 JSON 字串資料
TouchDesigner 作為一款強大的即時互動媒體開發工具,也能夠非常方便地接收和解析來自 Arduino 的 JSON 資料。我們將使用 Serial (DAT)、Select (DAT) 和 JSON (DAT) 等節點來處理資料流。
- 在 TouchDesigner 中,新增一個 Serial (DAT) 節點。這個節點用於與序列埠通訊。
- 設定
Row/Callback Format
為One Per Line
,這表示每讀到一個換行符號就視為一條完整的訊息。 - Port: 設定為你電腦連接 Arduino 的序列埠名稱(例如 Windows 的
COM3
,macOS/Linux 的/dev/tty.usbserial-xxxx
)。 - Baud Rate: 設定為
9600
,與 Arduino 程式碼中的設定保持一致。
- 設定
- 新增一個 Select (DAT) 節點,並將其輸出連接到 Serial (DAT) 的輸入端。這個節點將用來選取我們需要的 JSON 資料行。
- 設定
Select Rows
為by Index
。 - Start Row Index 和 End Row Index 都設定為
1
。由於Serial (DAT)
的One Per Line
模式會將接收到的最新訊息放在第二行(索引為 1),這樣設定可以確保我們總是選取到最新且完整的 JSON 字串。
- 設定
- 新增一個 JSON (DAT) 節點,並將其輸出連接到 Select (DAT) 的輸出端。這個節點負責將 JSON 字串反序列化。
- 設定
Output Format
為Table
。這會將 JSON 資料解析成一個表格形式,方便後續處理。
- 設定
- 最後,新增一個 DAT to (CHOP) 節點。這個節點會將表格資料轉換為 CHOP (Channel Operator) 頻道資料,這是 TouchDesigner 中最常使用的資料類型。
- DAT: 設定為上一步新增的
JSON (DAT)
的名稱。 - Select Cols: 設定為
by Name
。 - Start Col Name: 設定為
value
(對應到 Arduino 發送的{"name":"testData","value":76}
中的"value":76
)。 - First Column is: 設定為
Names
。這會將 JSON 中name
鍵的值(即testData
)作為 CHOP 頻道的名稱。
- DAT: 設定為上一步新增的
完成以上設定後,你將會在 DAT to (CHOP)
節點中看到一個名為 testData
的 CHOP 頻道,其數值會實時變動,顯示來自 Arduino 感測器的隨機資料 (原始碼下載)。
這個 testData
頻道及其數值現在就可以無縫地用於 TouchDesigner 中的各種 遊戲開發、互動設計、視覺化或裝置控制應用,為你的專案帶來實時的物理世界互動性。
發送 JSON 執行命令
如同 Unity 範例,TouchDesigner 也能夠向 Arduino 發送 JSON 格式的命令,實現即時互動控制。這能讓你在 TouchDesigner 的介面中控制 Arduino 的行為,例如切換感測模式或 觸發特定動作。
- 首先,在 TouchDesigner 網路編輯區新增一個 Button (COMP) 節點。
- 設定其
Button Type
為Momentary
。這表示當按鈕按下時觸發一次,放開時回到原始狀態,適合發送單次指令。
- 設定其
- 接著,新增一個 Null (CHOP) 節點,並將其連接到這個 Button (COMP) 的輸出端。Null (CHOP) 通常用於接收上一個節點的數據,並作為下一個節點的輸入,保持數據流的整潔。
- 現在,新增一個 CHOP Execute (DAT) 節點。這個節點允許你監聽 CHOP 數據的變化,並在特定條件下執行 Python 腳本。
- 在 CHOP Execute (DAT) 的參數面板中,設定
CHOPs
為你剛才新增的Null (CHOP)
的名稱。 - 在「Callbacks」頁籤下,啟用
Off to On
(表示當 CHOP 頻道從 0 變為 1 時觸發,對應按鈕按下),並確保禁用其他模式(例如On To Off
或While On
),以避免重複執行。
- 編輯 CHOP Execute (DAT) 的程式碼。雙擊 CHOP Execute (DAT) 節點,打開其文字編輯器,並將以下 Python 程式碼貼入
onOffToOn
函式中:
1import json
2
3def onOffToOn(channel, sampleIndex, val, prev):
4 data = {
5 "name": "Hello World! " + str(absTime.frame),
6 "value": 99999
7 }
8 json_str = json.dumps(data)
9 op("serial1").send(json_str, terminator="\n")
10 return
Python 程式碼解析:
import json
:引入 Python 內建的json
模組,用於處理 JSON 資料的序列化與反序列化。onOffToOn(channel, sampleIndex, val, prev)
:這是CHOP Execute (DAT)
在Off to On
事件發生時會自動呼叫的函式。data = { ... }
:建立一個 Python 字典,其鍵名name
和value
與 Arduino 端的 JSON 結構 (InputData
類別) 保持一致。name
欄位使用了 absTime.frame 來取得當前 TouchDesigner 的幀數,使其每次發送都帶有獨特的識別。json_str = json.dumps(data)
:將 Python 字典data
轉換為 JSON 格式的字串。op("serial1").send(json_str, terminator="\n")
:這行是關鍵!它透過名稱為"serial1"
的 Serial (DAT) 節點(假設你先前的 Serial (DAT) 節點名稱為serial1
)發送 JSON 字串。terminator="\n"
參數確保在發送的字串末尾加上換行符,這樣 Arduino 才能正確識別一條完整的 JSON 訊息。
完成設定後,點擊 TouchDesigner 網路編輯區中的 Button (COMP)。你會發現,這時 Arduino 透過 Serial Monitor(或 Unity Console)發送的 name
內容就會改變,顯示為 "Hello World! [時間戳記]"
的格式。這實現了與 Unity 類似的控制效果,證明 TouchDesigner 也能成功向 Arduino 發送指令並改變其行為模式 (原始碼下載)。
透過這些範例,你已掌握了 Arduino 與 Unity/TouchDesigner 之間高效且有結構的 JSON 數據雙向通訊。這為你的物聯網專案、互動藝術裝置、或是任何需要硬體與軟體協同運作的應用,開啟了無限的可能性。