WOKWI ESP32 4LED DHT22 + ThingBoard
✅ 功能說明:
-
每 5 秒讀取溫溼度資料上傳至 ThingBoard。
-
可從 ThingBoard Dashboard 按鈕遠端控制 4 顆 LED(透過 MQTT RPC)。
🧰 硬體需求:
-
ESP32 開發板
-
DHT22 感測器 (資料腳接 GPIO 4)
-
4 顆 LED 分別接 GPIO 14, 27, 26, 25
-
上拉電阻(DHT22)
-
Wi-Fi 網路
📦 Arduino 函式庫安裝:
請先安裝以下函式庫:
-
PubSubClient(MQTT)
-
DHT sensor library
-
Adafruit Unified Sensor
🧪 ThingBoard 設定簡介:
-
登入 https://demo.thingsboard.io 或你的私有伺服器。
-
建立一個新設備,複製它的 Access Token。
-
在「Dashboard」中建立控制按鈕(Button 控制 RPC):
-
Widget → Cards → Control → Switch
-
設定 method 為
setLED1~setLED4 -
params:true/false
-
📥 匯入方式:
-
登入 ThingBoard
-
到 Dashboard 頁面
-
點右上角「☰」選單 → Import dashboard
-
選擇「From JSON」
-
貼上以下內容即可
✅ 匯入後提醒:
-
請確認你的設備名稱為
ESP32(或依你實際命名改動deviceNameFilter)。 -
此範例使用 Switch 控制器 呼叫 RPC
setLED1~setLED4,需與 ESP32 上一致。 -
溫溼度資料會自動顯示,只要 MQTT 有傳上來。
在 https://demo.thingsboard.io/ 建立「ESP32 設備」和「控制與顯示儀表板」的完整教學,包含:LED 控制、DHT22 溫濕度上傳與顯示。
🧭 一、前置作業
✅ 你需要準備:
-
一個 ThingsBoard 公開測試帳號(免費註冊)
-
ESP32 程式碼(你已有)
-
瀏覽器登入 ThingsBoard 後台介面
🛠 二、建立設備:ESP32
步驟:
-
左側選單點選
Devices(設備) -
點右上角
➕→ 選擇Add new device -
設定如下:
-
Name(名稱):ESP32
-
勾選
Is gateway(非必要)
-
-
點選
Add
🧷 三、取得 Access Token
-
點進剛剛建立的設備
ESP32 -
切到上方分頁「Credentials」
-
將 Access Token 複製起來,填入你的 ESP32 程式中的
const char* token = "xxxxxxxxxx";
🎛 四、建立 Dashboard 儀表板(含溫濕度+LED 控制)
步驟:
-
左側選單點選
Dashboards -
點右上角
☰(選單)→ 選擇+ Create new dashboard -
設定:
-
Name: ESP32 Dashboard
-
點
Add
-
➕ 五、加入 Widget:顯示溫度與濕度
📊 顯示溫度/濕度的方式
-
打開 Dashboard,點選右上角
橙色鉛筆進入編輯模式 -
點左下角
+ Add new widget -
選擇類型:
-
Cards→Digital value -
點選
Add to Dashboard
-
-
設定資料來源:
-
Type:
Device -
Device:
ESP32 -
Data key:
-
temperature(單位 °C,可改名稱為「溫度」) -
humidity(單位 %,名稱「濕度」)
-
-
-
重複步驟再加一次,做出另一個 widget(溫度 / 濕度各一個)
💡 六、加入 Widget:控制 4 顆 LED(RPC)
✅ 建立開關元件(Switch)
-
在 Dashboard 編輯模式中,點
+ Add new widget -
類別選擇:Control → Switch
-
點選
Add to dashboard
🎚 RPC 設定:
在 Switch widget 編輯畫面:
-
Target device: ESP32
-
RPC method:
setLED1(其他三顆是setLED2,setLED3,setLED4) -
Value type:
boolean -
true = 開,false = 關
✅ 再加三個 Switch 控制器 → 改成 setLED2 ~ setLED4
✅ 七、儀表板完成示意
| Widget | 說明 |
|---|---|
| Switch (setLED1~4) | 控制 LED 開關 |
| Digital Value - 溫度 | 顯示 DHT22 溫度 |
| Digital Value - 濕度 | 顯示 DHT22 濕度 |
🔁 八、測試結果
-
當 ESP32 開機後,會自動連上 WiFi 並連接
demo.thingsboard.io -
每 5 秒會上傳:
-
在 Dashboard 上可以觀察即時數據變化
-
點選 Switch 控制 LED → ESP32 會收到如下 RPC:
ESP32 與 ThingsBoard(IoT 平台)的關係是:ESP32 是裝置端(Device),ThingsBoard 是伺服器端(Platform)。兩者透過 MQTT 通訊協定互相溝通、控制與資料交換。
🧩 簡單比喻:
| 角色 | 比喻 | 說明 |
|---|---|---|
| ESP32 | 感測器 + 開關機器人 | 用來收集資料(溫濕度)、控制設備(LED) |
| ThingsBoard | 中控室 + 雲端儀表板 | 收集資料、發送控制命令、畫圖表 |
🔁 互動方式圖解
🔧 技術關係詳解
| 功能項目 | ESP32(Device) | ThingsBoard(Server) |
|---|---|---|
| 資料上傳 | 使用 MQTT 傳送 telemetry 資料 | 接收資料、畫圖表、儲存資料 |
| 設備控制 | 接收 RPC(遠端呼叫)控制 LED | 發送 RPC 命令給 ESP32 控制 GPIO |
| 身份驗證 | 使用 Access Token 驗證設備身份 | 管理所有裝置、發行 token |
| 儀表板顯示 | 無螢幕顯示(只傳資料) | 顯示圖表、數字、按鈕、Switch,供使用者操作 |
🔗 他們怎麼連在一起?
-
ESP32 使用
WiFi + MQTT連線到ThingsBoard -
ESP32 提供:
-
感測器資料(如溫度、濕度)上傳到主題
v1/devices/me/telemetry -
控制回應(LED 開/關)接收主題
v1/devices/me/rpc/request/+
-
-
ThingsBoard:
-
顯示來自 ESP32 的資料
-
用 Dashboard RPC 控制元件(例如開關)控制 ESP32
-
🧠 ESP32 程式扮演的角色
| 程式區段 | 功能說明 |
|---|---|
client.publish(...) | 上傳資料到 ThingsBoard |
client.subscribe(...) | 接收平台下達的控制命令(如控制 LED) |
callback() | 當收到 RPC 指令,執行對應控制,例如點亮 LED |
✅ 總結:ESP32 和 ThingsBoard 的關係是…
ESP32 就像是前線的感測控制士兵,ThingsBoard 就是後台的指揮官與總部資料中心。
-
ESP32 把環境資料回報總部(ThingsBoard)
-
總部也能從後台 Dashboard 控制 ESP32 的行為(如開燈)
https://demo.thingsboard.io/ 儀錶板json檔案
"title": "ESP32 LED + DHT22 Dashboard",
"image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": {
"description": "ESP32 LED + DHT22",
"widgets": {
"a8b3ca27-9246-2824-390f-7b21effdb1b7": {
"typeFullFqn": "system.analogue_gauges.temperature_radial_gauge_canvas_gauges",
"type": "latest",
"sizeX": 6,
"sizeY": 5,
"config": {
"datasources": [
{
"type": "device",
"name": "",
"deviceId": "63dafa10-54e3-11f0-905b-715188ad2cd8",
"dataKeys": [
{
"name": "temperature",
"type": "timeseries",
"label": "Temperature",
"color": "#2196f3",
"settings": {},
"_hash": 0.5312546541520234
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
}
],
"timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY",
"hideInterval": false,
"hideLastInterval": false,
"hideQuickInterval": false
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1751270835153,
"endTimeMs": 1751357235153
},
"quickInterval": "CURRENT_DAY",
"hideInterval": false,
"hideLastInterval": false,
"hideFixedInterval": false,
"hideQuickInterval": false
},
"aggregation": {
"type": "AVG",
"limit": 2500
}
},
"showTitle": false,
"backgroundColor": "rgb(255, 255, 255)",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "8px",
"settings": {
"startAngle": 67.5,
"ticksAngle": 225,
"needleCircleSize": 7,
"defaultColor": "#e65100",
"minValue": -40,
"maxValue": 80,
"majorTicksCount": 12,
"colorMajorTicks": "#444",
"minorTicks": 12,
"colorMinorTicks": "#666",
"numbersFont": {
"family": "Roboto",
"size": 20,
"style": "normal",
"weight": "normal",
"color": "#263238"
},
"numbersColor": "#263238",
"showUnitTitle": true,
"unitTitle": "溫度",
"titleFont": {
"family": "Roboto",
"size": 24,
"style": "normal",
"weight": "normal",
"color": "#263238"
},
"titleColor": "#263238",
"unitsFont": {
"family": "Roboto",
"size": 28,
"style": "normal",
"weight": "500",
"color": "#616161"
},
"unitsColor": "#616161",
"valueBox": true,
"valueInt": 3,
"valueFont": {
"family": "Segment7Standard",
"size": 30,
"style": "normal",
"weight": "normal",
"shadowColor": "rgba(0, 0, 0, 0.49)",
"color": "#444"
},
"valueColor": "#444",
"valueColorShadow": "rgba(0, 0, 0, 0.49)",
"colorValueBoxRect": "#888",
"colorValueBoxRectEnd": "#666",
"colorValueBoxBackground": "#babab2",
"colorValueBoxShadow": "rgba(0,0,0,1)",
"showBorder": true,
"colorPlate": "#cfd8dc",
"colorNeedle": null,
"colorNeedleEnd": null,
"colorNeedleShadowUp": "rgba(2, 255, 255, 0)",
"colorNeedleShadowDown": "rgba(188, 143, 143, 0.78)",
"highlightsWidth": 15,
"highlights": [
{
"from": -60,
"to": -50,
"color": "#42a5f5"
},
{
"from": -50,
"to": -40,
"color": "rgba(66, 165, 245, 0.83)"
},
{
"from": -40,
"to": -30,
"color": "rgba(66, 165, 245, 0.66)"
},
{
"from": -30,
"to": -20,
"color": "rgba(66, 165, 245, 0.5)"
},
{
"from": -20,
"to": -10,
"color": "rgba(66, 165, 245, 0.33)"
},
{
"from": -10,
"to": 0,
"color": "rgba(66, 165, 245, 0.16)"
},
{
"from": 0,
"to": 10,
"color": "rgba(229, 115, 115, 0.16)"
},
{
"from": 10,
"to": 20,
"color": "rgba(229, 115, 115, 0.33)"
},
{
"from": 20,
"to": 30,
"color": "rgba(229, 115, 115, 0.5)"
},
{
"from": 30,
"to": 40,
"color": "rgba(229, 115, 115, 0.66)"
},
{
"from": 40,
"to": 50,
"color": "rgba(229, 115, 115, 0.83)"
},
{
"from": 50,
"to": 60,
"color": "#e57373"
}
],
"animation": true,
"animationDuration": 1000,
"animationRule": "bounce"
},
"title": "Temperature radial gauge",
"dropShadow": true,
"enableFullscreen": true,
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"widgetStyle": {},
"widgetCss": "",
"pageSize": 1024,
"decimals": 0,
"noDataDisplayMessage": "",
"configMode": "basic",
"units": "°C",
"useDashboardTimewindow": true,
"displayTimewindow": true,
"actions": {},
"borderRadius": null
},
"row": 0,
"col": 0,
"id": "a8b3ca27-9246-2824-390f-7b21effdb1b7"
},
"bf08f6d1-e9f8-f822-2f63-8d85f01f6e0e": {
"typeFullFqn": "system.analogue_gauges.temperature_gauge_canvas_gauges",
"type": "latest",
"sizeX": 7,
"sizeY": 3,
"config": {
"datasources": [
{
"type": "device",
"name": "",
"deviceId": "63dafa10-54e3-11f0-905b-715188ad2cd8",
"dataKeys": [
{
"name": "humidity",
"type": "timeseries",
"label": "Temperature",
"color": "#2196f3",
"settings": {},
"_hash": 0.6795682583119522,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
}
],
"timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY",
"hideInterval": false,
"hideLastInterval": false,
"hideQuickInterval": false
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1751270894262,
"endTimeMs": 1751357294262
},
"quickInterval": "CURRENT_DAY",
"hideInterval": false,
"hideLastInterval": false,
"hideFixedInterval": false,
"hideQuickInterval": false
},
"aggregation": {
"type": "AVG",
"limit": 2500
}
},
"showTitle": false,
"backgroundColor": "rgb(255, 255, 255)",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "8px",
"settings": {
"startAngle": 45,
"ticksAngle": 270,
"needleCircleSize": 10,
"defaultColor": "#e64a19",
"minValue": 0,
"maxValue": 100,
"majorTicksCount": 8,
"colorMajorTicks": "#444",
"minorTicks": 8,
"colorMinorTicks": "#666",
"numbersFont": {
"family": "Arial",
"size": 18,
"style": "normal",
"weight": "normal",
"color": "#263238"
},
"numbersColor": "#263238",
"showUnitTitle": true,
"unitTitle": "濕度",
"titleFont": {
"family": "Roboto",
"size": 24,
"style": "normal",
"weight": "normal",
"color": "#78909c"
},
"titleColor": "#78909c",
"unitsFont": {
"family": "Roboto",
"size": 26,
"style": "normal",
"weight": "500",
"color": "#37474f"
},
"unitsColor": "#37474f",
"valueBox": true,
"valueInt": 3,
"valueFont": {
"family": "Roboto",
"size": 40,
"style": "normal",
"weight": "500",
"color": "#444",
"shadowColor": "rgba(0,0,0,0.3)"
},
"valueColor": "#444",
"valueColorShadow": "rgba(0,0,0,0.3)",
"colorValueBoxRect": "#888",
"colorValueBoxRectEnd": "#666",
"colorValueBoxBackground": "#babab2",
"colorValueBoxShadow": "rgba(0,0,0,1)",
"showBorder": false,
"colorPlate": "#fff",
"colorNeedle": null,
"colorNeedleEnd": null,
"colorNeedleShadowUp": "rgba(2,255,255,0.2)",
"colorNeedleShadowDown": "rgba(188,143,143,0.45)",
"highlightsWidth": 10,
"highlights": [
{
"from": -60,
"to": -40,
"color": "#90caf9"
},
{
"from": -40,
"to": -20,
"color": "rgba(144, 202, 249, 0.66)"
},
{
"from": -20,
"to": 0,
"color": "rgba(144, 202, 249, 0.33)"
},
{
"from": 0,
"to": 20,
"color": "rgba(244, 67, 54, 0.2)"
},
{
"from": 20,
"to": 40,
"color": "rgba(244, 67, 54, 0.4)"
},
{
"from": 40,
"to": 60,
"color": "rgba(244, 67, 54, 0.6)"
},
{
"from": 60,
"to": 80,
"color": "rgba(244, 67, 54, 0.8)"
},
{
"from": 80,
"to": 100,
"color": "#f44336"
}
],
"animation": true,
"animationDuration": 1500,
"animationRule": "linear",
"barStrokeWidth": 2.5,
"colorBarStroke": "#b0bec5",
"colorBar": "rgba(255, 255, 255, 0.4)",
"colorBarEnd": "rgba(221, 221, 221, 0.38)",
"colorBarProgress": "#90caf9",
"colorBarProgressEnd": "#f44336"
},
"title": "Thermometer scale",
"dropShadow": true,
"enableFullscreen": true,
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"widgetStyle": {},
"widgetCss": "",
"pageSize": 1024,
"decimals": 0,
"noDataDisplayMessage": "",
"configMode": "basic",
"units": "%",
"useDashboardTimewindow": true,
"displayTimewindow": true,
"actions": {},
"borderRadius": null
},
"row": 0,
"col": 0,
"id": "bf08f6d1-e9f8-f822-2f63-8d85f01f6e0e"
},
"85e60cf3-1c4b-e918-2301-fc20c95418a7": {
"typeFullFqn": "system.single_switch",
"type": "rpc",
"sizeX": 3.5,
"sizeY": 1,
"config": {
"showTitle": false,
"backgroundColor": "#ffffff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"initialState": {
"action": "EXECUTE_RPC",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"onUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED1",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"offUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED1",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;"
}
},
"disabledState": {
"action": "DO_NOTHING",
"defaultValue": false,
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"layout": "right",
"autoScale": true,
"showLabel": true,
"label": "LED 1 控制",
"labelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "500",
"lineHeight": "24px"
},
"labelColor": "rgba(0, 0, 0, 0.76)",
"showIcon": false,
"iconSize": 24,
"iconSizeUnit": "px",
"icon": "mdi:lightbulb-outline",
"iconColor": "rgba(0, 0, 0, 0.76)",
"switchColorOn": "#5469FF",
"switchColorOff": "rgba(84, 105, 255, 0.30)",
"switchColorDisabled": "#D5D7E5",
"tumblerColorOn": "#fff",
"tumblerColorOff": "#fff",
"tumblerColorDisabled": "#fff",
"showOnLabel": false,
"onLabel": "On",
"onLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"onLabelColor": "rgba(0, 0, 0, 0.38)",
"showOffLabel": false,
"offLabel": "Off",
"offLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"offLabelColor": "rgba(0, 0, 0, 0.38)",
"background": {
"type": "color",
"color": "#fff",
"overlay": {
"enabled": false,
"color": "rgba(255,255,255,0.72)",
"blur": 3
}
},
"padding": ""
},
"title": "Single Switch",
"dropShadow": true,
"enableFullscreen": false,
"widgetStyle": {},
"actions": {},
"widgetCss": "",
"noDataDisplayMessage": "",
"titleFont": {
"size": 12,
"sizeUnit": "px",
"family": null,
"weight": null,
"style": null,
"lineHeight": "1.6"
},
"showTitleIcon": false,
"titleTooltip": "",
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"pageSize": 1024,
"titleIcon": "",
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "14px",
"configMode": "basic",
"datasources": [],
"targetDevice": {
"type": "device",
"deviceId": "63dafa10-54e3-11f0-905b-715188ad2cd8"
},
"borderRadius": null
},
"row": 0,
"col": 0,
"id": "85e60cf3-1c4b-e918-2301-fc20c95418a7"
},
"97e0414e-acc7-5615-4b02-14d47b8ca114": {
"typeFullFqn": "system.single_switch",
"type": "rpc",
"sizeX": 3.5,
"sizeY": 1,
"config": {
"showTitle": false,
"backgroundColor": "#ffffff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"initialState": {
"action": "EXECUTE_RPC",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"onUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED3",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"offUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED3",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;"
}
},
"disabledState": {
"action": "DO_NOTHING",
"defaultValue": false,
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"layout": "right",
"autoScale": true,
"showLabel": true,
"label": "LED 3 控制",
"labelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "500",
"lineHeight": "24px"
},
"labelColor": "rgba(0, 0, 0, 0.76)",
"showIcon": false,
"iconSize": 24,
"iconSizeUnit": "px",
"icon": "mdi:lightbulb-outline",
"iconColor": "rgba(0, 0, 0, 0.76)",
"switchColorOn": "#5469FF",
"switchColorOff": "rgba(84, 105, 255, 0.30)",
"switchColorDisabled": "#D5D7E5",
"tumblerColorOn": "#fff",
"tumblerColorOff": "#fff",
"tumblerColorDisabled": "#fff",
"showOnLabel": false,
"onLabel": "On",
"onLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"onLabelColor": "rgba(0, 0, 0, 0.38)",
"showOffLabel": false,
"offLabel": "Off",
"offLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"offLabelColor": "rgba(0, 0, 0, 0.38)",
"background": {
"type": "color",
"color": "#fff",
"overlay": {
"enabled": false,
"color": "rgba(255,255,255,0.72)",
"blur": 3
}
},
"padding": ""
},
"title": "Single Switch",
"dropShadow": true,
"enableFullscreen": false,
"widgetStyle": {},
"actions": {},
"widgetCss": "",
"noDataDisplayMessage": "",
"titleFont": {
"size": 12,
"sizeUnit": "px",
"family": null,
"weight": null,
"style": null,
"lineHeight": "1.6"
},
"showTitleIcon": false,
"titleTooltip": "",
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"pageSize": 1024,
"titleIcon": "",
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "14px",
"configMode": "basic",
"datasources": [],
"targetDevice": {
"type": "device",
"deviceId": "63dafa10-54e3-11f0-905b-715188ad2cd8"
},
"borderRadius": null
},
"row": 0,
"col": 0,
"id": "97e0414e-acc7-5615-4b02-14d47b8ca114"
},
"db7122f6-4963-d859-8f7e-487ba7d205ab": {
"typeFullFqn": "system.single_switch",
"type": "rpc",
"sizeX": 3.5,
"sizeY": 1,
"config": {
"showTitle": false,
"backgroundColor": "#ffffff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"initialState": {
"action": "EXECUTE_RPC",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"onUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED2",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"offUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED2",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;"
}
},
"disabledState": {
"action": "DO_NOTHING",
"defaultValue": false,
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"layout": "right",
"autoScale": true,
"showLabel": true,
"label": "LED 2 控制",
"labelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "500",
"lineHeight": "24px"
},
"labelColor": "rgba(0, 0, 0, 0.76)",
"showIcon": false,
"iconSize": 24,
"iconSizeUnit": "px",
"icon": "mdi:lightbulb-outline",
"iconColor": "rgba(0, 0, 0, 0.76)",
"switchColorOn": "#5469FF",
"switchColorOff": "rgba(84, 105, 255, 0.30)",
"switchColorDisabled": "#D5D7E5",
"tumblerColorOn": "#fff",
"tumblerColorOff": "#fff",
"tumblerColorDisabled": "#fff",
"showOnLabel": false,
"onLabel": "On",
"onLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"onLabelColor": "rgba(0, 0, 0, 0.38)",
"showOffLabel": false,
"offLabel": "Off",
"offLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"offLabelColor": "rgba(0, 0, 0, 0.38)",
"background": {
"type": "color",
"color": "#fff",
"overlay": {
"enabled": false,
"color": "rgba(255,255,255,0.72)",
"blur": 3
}
},
"padding": ""
},
"title": "Single Switch",
"dropShadow": true,
"enableFullscreen": false,
"widgetStyle": {},
"actions": {},
"widgetCss": "",
"noDataDisplayMessage": "",
"titleFont": {
"size": 12,
"sizeUnit": "px",
"family": null,
"weight": null,
"style": null,
"lineHeight": "1.6"
},
"showTitleIcon": false,
"titleTooltip": "",
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"pageSize": 1024,
"titleIcon": "",
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "14px",
"configMode": "basic",
"datasources": [],
"targetDevice": {
"type": "device",
"deviceId": "63dafa10-54e3-11f0-905b-715188ad2cd8"
},
"borderRadius": null
},
"row": 0,
"col": 0,
"id": "db7122f6-4963-d859-8f7e-487ba7d205ab"
},
"a2a74f67-a940-e174-6b03-044d46b3f063": {
"typeFullFqn": "system.single_switch",
"type": "rpc",
"sizeX": 3.5,
"sizeY": 1,
"config": {
"showTitle": false,
"backgroundColor": "#ffffff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"initialState": {
"action": "EXECUTE_RPC",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"onUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED4",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"offUpdateState": {
"action": "EXECUTE_RPC",
"executeRpc": {
"method": "setLED4",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SERVER_SCOPE",
"key": "state"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;"
}
},
"disabledState": {
"action": "DO_NOTHING",
"defaultValue": false,
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"layout": "right",
"autoScale": true,
"showLabel": true,
"label": "LED 4 控制",
"labelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "500",
"lineHeight": "24px"
},
"labelColor": "rgba(0, 0, 0, 0.76)",
"showIcon": false,
"iconSize": 24,
"iconSizeUnit": "px",
"icon": "mdi:lightbulb-outline",
"iconColor": "rgba(0, 0, 0, 0.76)",
"switchColorOn": "#5469FF",
"switchColorOff": "rgba(84, 105, 255, 0.30)",
"switchColorDisabled": "#D5D7E5",
"tumblerColorOn": "#fff",
"tumblerColorOff": "#fff",
"tumblerColorDisabled": "#fff",
"showOnLabel": false,
"onLabel": "On",
"onLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"onLabelColor": "rgba(0, 0, 0, 0.38)",
"showOffLabel": false,
"offLabel": "Off",
"offLabelFont": {
"family": "Roboto",
"size": 16,
"sizeUnit": "px",
"style": "normal",
"weight": "400",
"lineHeight": "24px"
},
"offLabelColor": "rgba(0, 0, 0, 0.38)",
"background": {
"type": "color",
"color": "#fff",
"overlay": {
"enabled": false,
"color": "rgba(255,255,255,0.72)",
"blur": 3
}
},
"padding": ""
},
"title": "Single Switch",
"dropShadow": true,
"enableFullscreen": false,
"widgetStyle": {},
"actions": {},
"widgetCss": "",
"noDataDisplayMessage": "",
"titleFont": {
"size": 12,
"sizeUnit": "px",
"family": null,
"weight": null,
"style": null,
"lineHeight": "1.6"
},
"showTitleIcon": false,
"titleTooltip": "",
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"pageSize": 1024,
"titleIcon": "",
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "14px",
"configMode": "basic",
"datasources": [],
"targetDevice": {
"type": "device",
"deviceId": "63dafa10-54e3-11f0-905b-715188ad2cd8"
},
"borderRadius": null
},
"row": 0,
"col": 0,
"id": "a2a74f67-a940-e174-6b03-044d46b3f063"
}
},
"states": {
"default": {
"name": "ESP32 LED + DHT22 Dashboard",
"root": true,
"layouts": {
"main": {
"widgets": {
"a8b3ca27-9246-2824-390f-7b21effdb1b7": {
"sizeX": 6,
"sizeY": 5,
"row": 0,
"col": 0
},
"bf08f6d1-e9f8-f822-2f63-8d85f01f6e0e": {
"sizeX": 7,
"sizeY": 3,
"row": 0,
"col": 6
},
"85e60cf3-1c4b-e918-2301-fc20c95418a7": {
"sizeX": 3,
"sizeY": 1,
"row": 3,
"col": 6
},
"97e0414e-acc7-5615-4b02-14d47b8ca114": {
"sizeX": 3,
"sizeY": 1,
"row": 3,
"col": 10
},
"db7122f6-4963-d859-8f7e-487ba7d205ab": {
"sizeX": 3,
"sizeY": 1,
"row": 4,
"col": 6
},
"a2a74f67-a940-e174-6b03-044d46b3f063": {
"sizeX": 3,
"sizeY": 1,
"row": 4,
"col": 10
}
},
"gridSettings": {
"layoutType": "default",
"backgroundColor": "#eeeeee",
"columns": 24,
"margin": 10,
"outerMargin": true,
"backgroundSizeMode": "100%"
}
}
}
}
},
"entityAliases": {},
"filters": {},
"timewindow": {
"displayValue": "",
"hideAggregation": false,
"hideAggInterval": false,
"hideTimezone": false,
"selectedTab": 0,
"realtime": {
"realtimeType": 0,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY",
"hideInterval": false,
"hideLastInterval": false,
"hideQuickInterval": false
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1751270820746,
"endTimeMs": 1751357220746
},
"quickInterval": "CURRENT_DAY",
"hideInterval": false,
"hideLastInterval": false,
"hideFixedInterval": false,
"hideQuickInterval": false
},
"aggregation": {
"type": "AVG",
"limit": 2500
}
},
"settings": {
"stateControllerId": "entity",
"showTitle": false,
"showDashboardsSelect": true,
"showEntitiesSelect": true,
"showDashboardTimewindow": true,
"showDashboardExport": true,
"toolbarAlwaysOpen": true
}
},
"name": "ESP32 LED + DHT22 Dashboard",
"resources": []
}
WOKWI程式
🧩 一、程式引用函式庫
| 函式庫 | 用途 |
|---|---|
WiFi.h | 讓 ESP32 連上 WiFi |
PubSubClient.h | 支援 MQTT 通訊協定(連接 ThingBoard) |
DHT.h + Adafruit_Sensor.h | 讀取 DHT22 溫濕度感測器 |
ArduinoJson.h | 解析從 ThingBoard 收到的 JSON RPC 指令 |
🌐 二、WiFi 與 MQTT 設定
-
這是連線到 Wokwi 模擬器提供的 WiFi 熱點
-
使用公開的 ThingBoard 伺服器
demo.thingsboard.io -
token是你在 ThingBoard 建立設備後所取得的 Access Token(類似密碼)
🌡️ 三、感測器與 LED 腳位設定
-
DHT22 接在 GPIO 19
-
感測器會讀取「溫度」與「濕度」
-
控制四顆 LED 分別接在 GPIO 18、5、17、16
🧠 四、MQTT RPC Callback 處理函式
-
當 ESP32 接收到來自 ThingBoard 的 RPC 訊息時會觸發這個函式
-
它解析 payload,轉成 JSON 格式
-
method是要執行的指令名稱(例如setLED3) -
value是要設定的值(true 開燈,false 關燈)
-
根據指令名稱,對應控制哪一顆 LED 開/關
🔁 五、MQTT 重連機制
-
若 MQTT 斷線,會持續嘗試重連
-
重連成功後,重新訂閱 RPC 指令路徑(+ 表示接受任何編號的請求)
⚙️ 六、初始化 setup()
-
啟動序列埠(方便除錯)
-
啟動 DHT22 感測器
-
設定 LED 腳位為輸出,並預設關閉
-
連接 WiFi
-
設定 MQTT 伺服器與 callback 處理函式
🔁 七、主程式 loop()
-
確保 MQTT 持續連線並接收訊息
-
每 5 秒從 DHT22 讀取一次溫濕度,並送到 ThingBoard
-
發送格式為 JSON,例如:
🎯 總結
| 功能模組 | 說明 |
|---|---|
| DHT22 感測器 | 每 5 秒上傳溫濕度資料到 ThingBoard |
| 4 顆 LED | 接收 ThingBoard 控制指令 setLED1~4 開/關 |
| MQTT 重連機制 | 保證與平台持續連線 |
| JSON RPC 解析 | 可根據 method + params 控制硬體 |




沒有留言:
張貼留言