2025年7月1日 星期二

WOKWI ESP32 4LED DHT22 + ThingBoard

 WOKWI  ESP32 4LED DHT22 + ThingBoard

✅ 功能說明:

  1. 每 5 秒讀取溫溼度資料上傳至 ThingBoard。

  2. 可從 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 設定簡介:

  1. 登入 https://demo.thingsboard.io 或你的私有伺服器。

  2. 建立一個新設備,複製它的 Access Token

  3. 在「Dashboard」中建立控制按鈕(Button 控制 RPC):

    • Widget → Cards → Control → Switch

    • 設定 method 為 setLED1 ~ setLED4

    • params: true / false

📥 匯入方式:

  1. 登入 ThingBoard

  2. 到 Dashboard 頁面

  3. 點右上角「☰」選單 → Import dashboard

  4. 選擇「From JSON」

  5. 貼上以下內容即可

✅ 匯入後提醒:

  • 請確認你的設備名稱為 ESP32(或依你實際命名改動 deviceNameFilter)。

  • 此範例使用 Switch 控制器 呼叫 RPC setLED1 ~ setLED4,需與 ESP32 上一致。

  • 溫溼度資料會自動顯示,只要 MQTT 有傳上來。



https://demo.thingsboard.io/ 建立「ESP32 設備」和「控制與顯示儀表板」的完整教學,包含:LED 控制、DHT22 溫濕度上傳與顯示。


🧭 一、前置作業

✅ 你需要準備:

  1. 一個 ThingsBoard 公開測試帳號(免費註冊)

  2. ESP32 程式碼(你已有)

  3. 瀏覽器登入 ThingsBoard 後台介面


🛠 二、建立設備:ESP32

步驟:

  1. 左側選單點選 Devices(設備)

  2. 點右上角 → 選擇 Add new device

  3. 設定如下:

    • Name(名稱):ESP32

    • 勾選 Is gateway(非必要)

  4. 點選 Add


🧷 三、取得 Access Token

  1. 點進剛剛建立的設備 ESP32

  2. 切到上方分頁「Credentials」

  3. Access Token 複製起來,填入你的 ESP32 程式中的 const char* token = "xxxxxxxxxx";


🎛 四、建立 Dashboard 儀表板(含溫濕度+LED 控制)

步驟:

  1. 左側選單點選 Dashboards

  2. 點右上角 ☰(選單) → 選擇 + Create new dashboard

  3. 設定:

    • Name: ESP32 Dashboard

    • Add


➕ 五、加入 Widget:顯示溫度與濕度

📊 顯示溫度/濕度的方式

  1. 打開 Dashboard,點選右上角 橙色鉛筆 進入編輯模式

  2. 點左下角 + Add new widget

  3. 選擇類型:

    • CardsDigital value

    • 點選 Add to Dashboard

  4. 設定資料來源

    • Type: Device

    • Device: ESP32

    • Data key:

      • temperature(單位 °C,可改名稱為「溫度」)

      • humidity(單位 %,名稱「濕度」)

  5. 重複步驟再加一次,做出另一個 widget(溫度 / 濕度各一個)


💡 六、加入 Widget:控制 4 顆 LED(RPC)

✅ 建立開關元件(Switch)

  1. 在 Dashboard 編輯模式中,點 + Add new widget

  2. 類別選擇:Control → Switch

  3. 點選 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 濕度

🔁 八、測試結果

  1. 當 ESP32 開機後,會自動連上 WiFi 並連接 demo.thingsboard.io

  2. 每 5 秒會上傳:

json
{"temperature": 26.5, "humidity": 52.3}
  1. 在 Dashboard 上可以觀察即時數據變化

  2. 點選 Switch 控制 LED → ESP32 會收到如下 RPC:

json
{"method":"setLED1","params":true}


ESP32 與 ThingsBoard(IoT 平台)的關係是:ESP32 是裝置端(Device),ThingsBoard 是伺服器端(Platform)。兩者透過 MQTT 通訊協定互相溝通、控制與資料交換。


🧩 簡單比喻:

角色比喻說明
ESP32感測器 + 開關機器人用來收集資料(溫濕度)、控制設備(LED)
ThingsBoard中控室 + 雲端儀表板收集資料、發送控制命令、畫圖表

🔁 互動方式圖解

lua
+------------------------+ | ThingsBoard 平台 | | demo.thingsboard.io | +----------+------------+ ▲ RPC 控制 │ MQTT 傳送資料 │ ▼ +------------------------+ | ESP32 開發板 | | DHT22, LED, WiFi... | +------------------------+

🔧 技術關係詳解

功能項目ESP32(Device)ThingsBoard(Server)
資料上傳使用 MQTT 傳送 telemetry 資料接收資料、畫圖表、儲存資料
設備控制接收 RPC(遠端呼叫)控制 LED發送 RPC 命令給 ESP32 控制 GPIO
身份驗證使用 Access Token 驗證設備身份管理所有裝置、發行 token
儀表板顯示無螢幕顯示(只傳資料)顯示圖表、數字、按鈕、Switch,供使用者操作

🔗 他們怎麼連在一起?

  1. ESP32 使用 WiFi + MQTT 連線到 ThingsBoard

  2. ESP32 提供:

    • 感測器資料(如溫度、濕度)上傳到主題 v1/devices/me/telemetry

    • 控制回應(LED 開/關)接收主題 v1/devices/me/rpc/request/+

  3. 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檔案


Thingboard dashboard儀表板的檔案


{

  "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程式



#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Adafruit_Sensor.h>
#include <ArduinoJson.h> // 新增 ArduinoJson 函式庫

// --- Wi-Fi 配置 ---
//const char* ssid = "您的Wi-Fi名稱";      // 替換成您的 Wi-Fi 名稱
//const char* password = "您的Wi-Fi密碼";  // 替換成您的 Wi-Fi 密碼
const char* ssid = "Wokwi-GUEST";    
const char* password = "";    

// ==== ThingBoard 設定 ====
const char* mqtt_server = "demo.thingsboard.io"; // 或你自己的伺服器 IP
const int mqtt_port = 1883;
const char* token = "1fj3t03rcg5es1mr1cnwab"; // 來自設備的 Access Token

WiFiClient espClient;
PubSubClient client(espClient);

// ==== DHT22 設定 ====
#define DHTPIN 19
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// ==== LED 腳位設定 ====
const int ledPins[4] = {18, 5, 17, 16};

// ==== 狀態暫存 ====
unsigned long lastSend = 0;
const unsigned long sendInterval = 5000; // 每5秒上傳一次

// MQTT RPC 指令處理
void callback(char* topic, byte* payload, unsigned int length) {
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, payload, length);
  if (error) {
    Serial.print("deserializeJson() failed: ");
    Serial.println(error.c_str());
    return;
  }

  String method = doc["method"];
  bool value = doc["params"];

  if (method == "setLED1") digitalWrite(ledPins[0], value ? HIGH : LOW);
  if (method == "setLED2") digitalWrite(ledPins[1], value ? HIGH : LOW);
  if (method == "setLED3") digitalWrite(ledPins[2], value ? HIGH : LOW);
  if (method == "setLED4") digitalWrite(ledPins[3], value ? HIGH : LOW);
}


// 重新連線
void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    if (client.connect("ESP32Client", token, NULL)) {
      Serial.println("connected");
      client.subscribe("v1/devices/me/rpc/request/+"); // RPC 控制
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  dht.begin();

  // 初始化 LED 腳位
  for (int i = 0; i < 4; i++) {
    pinMode(ledPins[i], OUTPUT);
    digitalWrite(ledPins[i], LOW);
  }

  // Wi-Fi 連線
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected");

  // MQTT 設定
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastSend > sendInterval) {
    lastSend = now;
    float h = dht.readHumidity();
    float t = dht.readTemperature();

    if (isnan(h) || isnan(t)) {
      Serial.println("Failed to read from DHT sensor!");
      return;
    }

    // 傳送至 ThingBoard 的 MQTT payload 格式
    String payload = "{\"temperature\":";
    payload += t;
    payload += ",\"humidity\":";
    payload += h;
    payload += "}";

    Serial.print("Publishing telemetry: ");
    Serial.println(payload);

    client.publish("v1/devices/me/telemetry", payload.c_str());
  }
}


🧩 一、程式引用函式庫

cpp
#include <WiFi.h> #include <PubSubClient.h> #include <DHT.h> #include <Adafruit_Sensor.h> #include <ArduinoJson.h>
函式庫用途
WiFi.h讓 ESP32 連上 WiFi
PubSubClient.h支援 MQTT 通訊協定(連接 ThingBoard)
DHT.h + Adafruit_Sensor.h讀取 DHT22 溫濕度感測器
ArduinoJson.h解析從 ThingBoard 收到的 JSON RPC 指令

🌐 二、WiFi 與 MQTT 設定

cpp
const char* ssid = "Wokwi-GUEST"; const char* password = ""; const char* mqtt_server = "demo.thingsboard.io"; const int mqtt_port = 1883; const char* token = "1fj3t0cg5es1mr1cnwab"; // ThingBoard 裝置的 Access Token
  • 這是連線到 Wokwi 模擬器提供的 WiFi 熱點

  • 使用公開的 ThingBoard 伺服器 demo.thingsboard.io

  • token 是你在 ThingBoard 建立設備後所取得的 Access Token(類似密碼)


🌡️ 三、感測器與 LED 腳位設定

cpp
#define DHTPIN 19 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE);
  • DHT22 接在 GPIO 19

  • 感測器會讀取「溫度」與「濕度」

cpp
const int ledPins[4] = {18, 5, 17, 16};
  • 控制四顆 LED 分別接在 GPIO 18、5、17、16


🧠 四、MQTT RPC Callback 處理函式

cpp
void callback(char* topic, byte* payload, unsigned int length) { StaticJsonDocument<200> doc; DeserializationError error = deserializeJson(doc, payload, length);
  • 當 ESP32 接收到來自 ThingBoard 的 RPC 訊息時會觸發這個函式

  • 它解析 payload,轉成 JSON 格式

cpp
String method = doc["method"]; bool value = doc["params"];
  • method 是要執行的指令名稱(例如 setLED3

  • value 是要設定的值(true 開燈,false 關燈)

cpp
if (method == "setLED1") digitalWrite(ledPins[0], value ? HIGH : LOW);
  • 根據指令名稱,對應控制哪一顆 LED 開/關


🔁 五、MQTT 重連機制

cpp
void reconnect() { while (!client.connected()) { client.connect("ESP32Client", token, NULL); client.subscribe("v1/devices/me/rpc/request/+"); } }
  • 若 MQTT 斷線,會持續嘗試重連

  • 重連成功後,重新訂閱 RPC 指令路徑(+ 表示接受任何編號的請求)


⚙️ 六、初始化 setup()

cpp
void setup() { Serial.begin(115200); dht.begin();
  • 啟動序列埠(方便除錯)

  • 啟動 DHT22 感測器

cpp
for (int i = 0; i < 4; i++) { pinMode(ledPins[i], OUTPUT); digitalWrite(ledPins[i], LOW); }
  • 設定 LED 腳位為輸出,並預設關閉

cpp
WiFi.begin(ssid, password); ... client.setServer(mqtt_server, mqtt_port); client.setCallback(callback);
  • 連接 WiFi

  • 設定 MQTT 伺服器與 callback 處理函式


🔁 七、主程式 loop()

cpp
void loop() { if (!client.connected()) { reconnect(); } client.loop();
  • 確保 MQTT 持續連線並接收訊息

cpp
unsigned long now = millis(); if (now - lastSend > sendInterval) { lastSend = now; ... client.publish("v1/devices/me/telemetry", payload.c_str());
  • 每 5 秒從 DHT22 讀取一次溫濕度,並送到 ThingBoard

  • 發送格式為 JSON,例如:

json
{"temperature": 25.3, "humidity": 63.0}

🎯 總結

功能模組說明
DHT22 感測器                每 5 秒上傳溫濕度資料到 ThingBoard
4 顆 LED接收 ThingBoard 控制指令 setLED1~4 開/關
MQTT 重連機制保證與平台持續連線
JSON RPC 解析可根據 method + params 控制硬體

沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...