2026年1月11日 星期日

WOKWI ESP32-IDF DHT22 + MQTT 

 WOKWI ESP32-IDF DHT22 + MQTT











WOKWI ESP32-IDF 程式

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "mqtt_client.h"

static const char *TAG = "DHT_MQTT_APP";

// --- 配置區 ---
#define MQTT_BROKER_URL "mqtt://mqttgo.io"
#define TOPIC_TEMP      "alex9ufo/wokwi/dht/temperature"
#define TOPIC_HUMID     "alex9ufo/wokwi/dht/humidity"
#define WIFI_SSID       "Wokwi-GUEST"
#define WIFI_PASS       ""

// WiFi 事件標誌
#define WIFI_CONNECTED_BIT BIT0
static EventGroupHandle_t s_wifi_event_group;

// --- DHT 資料結構 ---
typedef enum {
    DHT_OK,
    DHT_TIMEOUT,
    DHT_CHECKSUM_FAIL,
    DHT_NOT_READY,
} dht_state_t;

typedef struct {
    gpio_num_t gpio;
    uint64_t time;
    uint64_t buffer;
} dht_handle_t;

// --- 函數原型 ---
dht_state_t dht_get_raw(dht_handle_t * dht);
float dht_get_temperature(dht_handle_t * dht);
float dht_get_humidity(dht_handle_t * dht);
static void mqtt_app_start(void);

static esp_mqtt_client_handle_t mqtt_client;

// --- DHT 邏輯 ---
dht_state_t dht_get_raw(dht_handle_t * dht) {
    uint8_t count = 0;
    uint64_t pulse_start;
    uint8_t checksum;

    if (dht->time > esp_timer_get_time()) dht->time = esp_timer_get_time();

    // DHT22 兩次讀取需間隔 2 秒
    if (esp_timer_get_time() - dht->time > 2000000) {
        dht->buffer = 0;
        gpio_set_direction(dht->gpio, GPIO_MODE_OUTPUT);
        gpio_set_level(dht->gpio, 0);
        vTaskDelay(pdMS_TO_TICKS(20));
        gpio_set_level(dht->gpio, 1);
        gpio_set_direction(dht->gpio, GPIO_MODE_INPUT);
        gpio_pullup_en(dht->gpio);

        dht->time = esp_timer_get_time();

        while (count < 41) {
            while (!gpio_get_level(dht->gpio)) {
                if (esp_timer_get_time() - dht->time > 12000) return DHT_TIMEOUT;
            }
            pulse_start = esp_timer_get_time();
            while (gpio_get_level(dht->gpio)) {
                if (esp_timer_get_time() - dht->time > 12000) return DHT_TIMEOUT;
            }
            if (count && (esp_timer_get_time() - pulse_start > 60)) {
                dht->buffer |= (1LL) << (40 - count);
            }
            count++;
        }

        checksum = 0;
        for (uint8_t i = 8; i < 40; i += 8) {
            checksum += (dht->buffer >> i) & 0xFF;
        }

        if (checksum == (dht->buffer & 0xFF)) return DHT_OK;
        else return DHT_CHECKSUM_FAIL;
    }
    return DHT_NOT_READY;
}

float dht_get_temperature(dht_handle_t * dht) {
    if ((dht->buffer >> 16) & 0x80) {
        return ((dht->buffer >> 8) & 0x7FFF) / -10.0;
    } else {
        return ((dht->buffer >> 8) & 0x7FFF) / 10.0;
    }
}

float dht_get_humidity(dht_handle_t * dht) {
    return ((dht->buffer >> 24) & 0xFFFF) / 10.0;
}

// --- 事件處理器 (WiFi & IP) ---
static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        ESP_LOGI(TAG, "Retrying connection to the AP");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

// --- MQTT 事件處理 ---
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
    switch ((esp_mqtt_event_id_t)event_id) {
        case MQTT_EVENT_CONNECTED:
            ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
            break;
        case MQTT_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
            break;
        case MQTT_EVENT_ERROR:
            ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
            break;
        default:
            break;
    }
}

// --- 初始化 WiFi ---
void wifi_init_sta(void) {
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASS,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "wifi_init_sta finished.");
   
    // 等待連線成功的位元設定 (最多等待 30 秒)
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
    ESP_LOGI(TAG, "WiFi Connected to AP");
}

// --- 初始化 MQTT ---
static void mqtt_app_start(void) {
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = MQTT_BROKER_URL,
    };
    mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    esp_mqtt_client_start(mqtt_client);
}

// --- 主程式 ---
void app_main() {
    // 1. 初始化 NVS (WiFi 必需)
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    esp_timer_early_init();

    // 2. 連接 WiFi (會卡住直到取得 IP)
    wifi_init_sta();

    // 3. 啟動 MQTT
    mqtt_app_start();

    dht_handle_t dht22 = {
        .gpio = GPIO_NUM_21,
        .time = 0,
    };

    char payload[16];

    while (1) {
        if (dht_get_raw(&dht22) == DHT_OK) {
            float temp = dht_get_temperature(&dht22);
            float humid = dht_get_humidity(&dht22);

            printf("Sending -> Temp: %.1f, Hum: %.1f\n", temp, humid);

            snprintf(payload, sizeof(payload), "%.1f", temp);
            esp_mqtt_client_publish(mqtt_client, TOPIC_TEMP, payload, 0, 1, 0);

            snprintf(payload, sizeof(payload), "%.1f", humid);
            esp_mqtt_client_publish(mqtt_client, TOPIC_HUMID, payload, 0, 1, 0);
        }
        vTaskDelay(pdMS_TO_TICKS(3000)); // 每 3 秒讀取並發送一次
    }
}



您可以打開一個網頁瀏覽器,搜尋 MQTTGO.io 的網頁版客戶端,或者是使用電腦終端機輸入: mosquitto_sub -h mqttgo.io -t "alex9ufo/wokwi/dht/temperature" 來查看是否有數據進入。

要在電腦上安裝 mosquitto_sub(這屬於 Mosquitto 客戶端工具的一部分),根據您的作業系統,安裝方式如下:

1. Windows 系統

在 Windows 上,最簡單的方法是安裝完整的 Mosquitto 套件。

  • 下載安裝檔:前往 Mosquitto 官方下載頁面,下載 Windows 64 位元版本(如 mosquitto-2.x.x-install-windows-x64.exe)。

  • 執行安裝:照著預設步驟安裝。

  • 設定環境變數(重要)

    1. 搜尋「編輯系統環境變數」。

    2. 點擊「環境變數」-> 在「系統變數」下找到 Path -> 點擊「編輯」。

    3. 新增 Mosquitto 的安裝路徑(預設通常是 C:\Program Files\mosquitto)。

  • 驗證:打開命令提示字元 (CMD),輸入 mosquitto_sub --help

如何訂閱您 ESP32 發出的數據?

安裝完成後,您可以使用以下指令來測試連線到 mqttgo.io 並接收溫度數據:

訂閱溫度:

Bash
mosquitto_sub -h mqttgo.io -t "alex9ufo/wokwi/dht/temperature"

訂閱濕度:

Bash
mosquitto_sub -h mqttgo.io -t "alex9ufo/wokwi/dht/humidity"

同時訂閱該路徑下的所有訊息(使用萬用字元 #):

Bash
mosquitto_sub -h mqttgo.io -t "alex9ufo/wokwi/dht/#" -v

-v 參數會顯示 Topic 名稱,方便區分溫度和濕度。


Python +TKinter  程式

import tkinter as tk

from tkinter import messagebox

import paho.mqtt.client as mqtt


# --- 配置區 ---

BROKER = "mqttgo.io"

PORT = 1883

TOPIC_TEMP = "alex9ufo/wokwi/dht/temperature"

TOPIC_HUMID = "alex9ufo/wokwi/dht/humidity"


class MqttApp:

    def __init__(self, root):

        self.root = root

        self.root.title("ESP32 DHT22 遠端監控儀表板")

        self.root.geometry("400x350")

        self.root.configure(bg="#f0f0f0")


        # UI 變數

        self.temp_var = tk.StringVar(value="--.- °C")

        self.humid_var = tk.StringVar(value="--.- %")

        self.status_var = tk.StringVar(value="等待連線...")

        self.status_color = "gray"


        self.setup_ui()


        # MQTT 初始化

        self.client = mqtt.Client()

        self.client.on_connect = self.on_connect

        self.client.on_message = self.on_message

        

        try:

            self.client.connect(BROKER, PORT, 60)

            self.client.loop_start()

        except Exception as e:

            self.status_var.set("連線失敗")

            messagebox.showerror("錯誤", f"無法連線至 Broker: {e}")


    def setup_ui(self):

        # 標題

        tk.Label(self.root, text="MQTT 數據監測站", font=("Arial", 18, "bold"), bg="#f0f0f0").pack(pady=10)


        # 狀態燈與文字

        status_frame = tk.Frame(self.root, bg="#f0f0f0")

        status_frame.pack(pady=5)

        self.canvas_status = tk.Canvas(status_frame, width=20, height=20, bg="#f0f0f0", highlightthickness=0)

        self.status_dot = self.canvas_status.create_oval(5, 5, 15, 15, fill="gray")

        self.canvas_status.pack(side=tk.LEFT, padx=5)

        tk.Label(status_frame, textvariable=self.status_var, font=("Arial", 10), bg="#f0f0f0").pack(side=tk.LEFT)


        # 數據顯示區

        data_frame = tk.Frame(self.root, bg="white", bd=2, relief="groove")

        data_frame.pack(pady=20, padx=20, fill=tk.BOTH, expand=True)


        # 溫度

        tk.Label(data_frame, text="當前溫度", font=("Arial", 12), bg="white").grid(row=0, column=0, padx=40, pady=10)

        tk.Label(data_frame, textvariable=self.temp_var, font=("Arial", 24, "bold"), bg="white", fg="#e74c3c").grid(row=1, column=0, padx=40)


        # 濕度

        tk.Label(data_frame, text="當前濕度", font=("Arial", 12), bg="white").grid(row=0, column=1, padx=40, pady=10)

        tk.Label(data_frame, textvariable=self.humid_var, font=("Arial", 24, "bold"), bg="white", fg="#3498db").grid(row=1, column=1, padx=40)


        # 退出按鈕

        tk.Button(self.root, text="關閉程式", command=self.on_close).pack(pady=10)


    def on_connect(self, client, userdata, flags, rc):

        if rc == 0:

            self.status_var.set(f"已連線至 {BROKER}")

            self.canvas_status.itemconfig(self.status_dot, fill="green")

            # 訂閱主題

            client.subscribe(TOPIC_TEMP)

            client.subscribe(TOPIC_HUMID)

        else:

            self.status_var.set("連線錯誤")

            self.canvas_status.itemconfig(self.status_dot, fill="red")


    def on_message(self, client, userdata, msg):

        payload = msg.payload.decode()

        if msg.topic == TOPIC_TEMP:

            self.temp_var.set(f"{payload} °C")

        elif msg.topic == TOPIC_HUMID:

            self.humid_var.set(f"{payload} %")


    def on_close(self):

        self.client.loop_stop()

        self.client.disconnect()

        self.root.destroy()


if __name__ == "__main__":

    root = tk.Tk()

    app = MqttApp(root)

    root.protocol("WM_DELETE_WINDOW", app.on_close)

    root.mainloop()


Node-Red 程式

 [{"id":"node_temp","type":"mqtt in","z":"a6dc6d40023a8282","name":"接收溫度","topic":"alex9ufo/wokwi/dht/temperature","qos":"2","datatype":"auto-detect","broker":"mqtt_broker_io","nl":false,"rap":false,"inputs":0,"x":160,"y":120,"wires":[["ui_temp_gauge"]]},{"id":"node_humid","type":"mqtt in","z":"a6dc6d40023a8282","name":"接收濕度","topic":"alex9ufo/wokwi/dht/humidity","broker":"mqtt_broker_io","inputs":0,"x":160,"y":200,"wires":[["ui_humid_gauge"]]},{"id":"ui_temp_gauge","type":"ui_gauge","z":"a6dc6d40023a8282","name":"","group":"ui_group_dht","order":1,"width":0,"height":0,"gtype":"gage","title":"溫度","label":"°C","format":"{{value}}","min":0,"max":"50","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":400,"y":120,"wires":[]},{"id":"ui_humid_gauge","type":"ui_gauge","z":"a6dc6d40023a8282","name":"","group":"ui_group_dht","order":2,"width":0,"height":0,"gtype":"gage","title":"濕度","label":"%","format":"{{value}}","min":0,"max":100,"colors":["#0099cc","#007799","#005577"],"x":400,"y":200,"wires":[]},{"id":"mqtt_broker_io","type":"mqtt-broker","name":"MQTTGO.IO","broker":"mqttgo.io","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"15","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"ui_group_dht","type":"ui_group","name":"ESP32 DHT22 監測","tab":"ui_tab_main","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"ui_tab_main","type":"ui_tab","name":"環境監控","icon":"dashboard"}]

沒有留言:

張貼留言

2026 作業1 MQTT基本觀念

 2026 作業1MQTT基本觀念 作業  參考下面網址 https://alex9ufoexploer.blogspot.com/2025/02/1-mqtt-relay-dht22-mqtt-box-pc-mymqtt.html https://alex9ufoexploer...