{"id":244,"date":"2026-03-22T13:48:47","date_gmt":"2026-03-22T05:48:47","guid":{"rendered":"https:\/\/www.isunding.com\/?p=244"},"modified":"2026-03-22T17:31:38","modified_gmt":"2026-03-22T09:31:38","slug":"esp32-c3-dht11-%e6%b8%a9%e6%b9%bf%e5%ba%a6%e7%9b%91%e6%8e%a7%e7%b3%bb%e7%bb%9f%e5%ae%9e%e7%8e%b0","status":"publish","type":"post","link":"https:\/\/www.isunding.com\/?p=244","title":{"rendered":"ESP32-C3 DHT11 \u6e29\u6e7f\u5ea6\u76d1\u63a7\u7cfb\u7edf\u5b9e\u73b0"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">\u7cfb\u7edf\u6982\u8ff0<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u672c\u7cfb\u7edf\u57fa\u4e8eESP32-C3\u5f00\u53d1\u677f\u548cDHT11\u6e29\u6e7f\u5ea6\u4f20\u611f\u5668\uff0c\u5b9e\u73b0\u4e86\u4e00\u4e2a\u5b8c\u6574\u7684\u6e29\u6e7f\u5ea6\u76d1\u63a7\u89e3\u51b3\u65b9\u6848\u3002\u7cfb\u7edf\u901a\u8fc7Web\u754c\u9762\u5b9e\u65f6\u663e\u793a\u6e29\u6e7f\u5ea6\u6570\u636e\u3001\u5386\u53f2\u8d8b\u52bf\u56fe\u8868\uff0c\u5e76\u63d0\u4f9b\u6570\u636e\u5bfc\u51fa\u3001\u8fdc\u7a0b\u63a7\u5236\u7b49\u529f\u80fd\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u786c\u4ef6\u8981\u6c42<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ESP32-C3\u5f00\u53d1\u677f<\/li>\n\n\n\n<li>DHT11\u6e29\u6e7f\u5ea6\u4f20\u611f\u5668<\/li>\n\n\n\n<li>4.7K\u03a9\u4e0a\u62c9\u7535\u963b\uff08DHT11\u6570\u636e\u5f15\u811a\uff09<\/li>\n\n\n\n<li>WiFi\u7f51\u7edc\u8fde\u63a5<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u8f6f\u4ef6\u529f\u80fd<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \u6838\u5fc3\u529f\u80fd<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u6e29\u6e7f\u5ea6\u6570\u636e\u91c7\u96c6 \uff1a\u4f7f\u7528DHT11\u4f20\u611f\u5668\u91c7\u96c6\u73af\u5883\u6e29\u6e7f\u5ea6\u6570\u636e\uff0c\u652f\u6301\u5c0f\u6570\u663e\u793a<\/li>\n\n\n\n<li>\u6570\u636e\u5b58\u50a8 \uff1a\u5c06\u6570\u636e\u5b58\u50a8\u5230SPIFFS\u6587\u4ef6\u7cfb\u7edf\uff0c\u652f\u6301\u5386\u53f2\u6570\u636e\u67e5\u8be2<\/li>\n\n\n\n<li>\u5206\u7ea7\u91c7\u6837 \uff1a24\u5c0f\u65f6\u5185\u6bcf10\u5206\u949f\u91c7\u6837\u4e00\u6b21\uff0c\u8d85\u8fc724\u5c0f\u65f6\u6bcf1\u5c0f\u65f6\u91c7\u6837\u4e00\u6b21<\/li>\n\n\n\n<li>Web\u754c\u9762 \uff1a\u63d0\u4f9b\u54cd\u5e94\u5f0fWeb\u754c\u9762\uff0c\u663e\u793a\u5b9e\u65f6\u6570\u636e\u548c\u5386\u53f2\u8d8b\u52bf\u56fe\u8868<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u9ad8\u7ea7\u529f\u80fd<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u9608\u503c\u62a5\u8b66 \uff1a\u53ef\u8bbe\u7f6e\u6e29\u5ea6\u548c\u6e7f\u5ea6\u7684\u4e0a\u4e0b\u9650\uff0c\u8d85\u51fa\u8303\u56f4\u65f6\u53d1\u51fa\u62a5\u8b66<\/li>\n\n\n\n<li>\u6570\u636e\u5bfc\u51fa \uff1a\u652f\u6301\u5bfc\u51faCSV\u683c\u5f0f\u7684\u5386\u53f2\u6570\u636e<\/li>\n\n\n\n<li>\u8fdc\u7a0b\u63a7\u5236 \uff1a\u652f\u6301\u8fdc\u7a0b\u91cd\u542f\u8bbe\u5907\u548c\u6e05\u9664\u5386\u53f2\u8bb0\u5f55<\/li>\n\n\n\n<li>NTP\u65f6\u95f4\u540c\u6b65 \uff1a\u81ea\u52a8\u4eceNTP\u670d\u52a1\u5668\u540c\u6b65\u65f6\u95f4\uff0c\u652f\u6301\u591a\u4e2aNTP\u670d\u52a1\u5668\u914d\u7f6e<\/li>\n\n\n\n<li>\u65f6\u95f4\u663e\u793a \uff1a\u5927\u5b57\u4f53\u663e\u793a\u5373\u65f6\u65f6\u95f4\uff0c\u6bcf\u79d2\u81ea\u52a8\u66f4\u65b0<\/li>\n\n\n\n<li>\u7248\u6743\u4fe1\u606f \uff1a\u5e95\u90e8\u663e\u793a\u7248\u6743\u4fe1\u606f\uff0c\u5305\u542b\u53ef\u70b9\u51fb\u7684\u8d85\u94fe\u63a5<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u6280\u672f\u7279\u70b9<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u591aNTP\u670d\u52a1\u5668 \uff1a\u914d\u7f6e\u4e86\u591a\u4e2aNTP\u670d\u52a1\u5668\uff0c\u63d0\u9ad8\u65f6\u95f4\u540c\u6b65\u7684\u53ef\u9760\u6027<\/li>\n\n\n\n<li>\u7f13\u5b58\u63a7\u5236 \uff1a\u6dfb\u52a0\u4e86\u7f13\u5b58\u63a7\u5236\u5934\uff0c\u786e\u4fdd\u65f6\u95f4\u5b9e\u65f6\u66f4\u65b0<\/li>\n\n\n\n<li>\u9519\u8bef\u5904\u7406 \uff1a\u5b8c\u5584\u7684\u9519\u8bef\u5904\u7406\u673a\u5236\uff0c\u786e\u4fdd\u7cfb\u7edf\u7a33\u5b9a\u8fd0\u884c<\/li>\n\n\n\n<li>\u5185\u5b58\u7ba1\u7406 \uff1a\u5408\u7406\u7684\u5185\u5b58\u5206\u914d\u548c\u91ca\u653e\uff0c\u907f\u514d\u5185\u5b58\u6cc4\u6f0f<\/li>\n\n\n\n<li>\u54cd\u5e94\u5f0f\u8bbe\u8ba1 \uff1aWeb\u754c\u9762\u9002\u914d\u4e0d\u540c\u5c4f\u5e55\u5c3a\u5bf8<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u4f7f\u7528\u8bf4\u660e<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \u786c\u4ef6\u8fde\u63a5<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>DHT11 VCC \u63a5 5V \u7535\u6e90<\/li>\n\n\n\n<li>DHT11 GND \u63a5\u5730<\/li>\n\n\n\n<li>DHT11 DATA \u63a5 GPIO14\uff0c\u5e76\u4e32\u8054 4.7K\u03a9 \u4e0a\u62c9\u7535\u963b<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u914d\u7f6e\u4fee\u6539<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u4fee\u6539 WIFI_SSID \u548c WIFI_PASS \u4e3a\u5b9e\u9645\u7684WiFi\u540d\u79f0\u548c\u5bc6\u7801<\/li>\n\n\n\n<li>\u53ef\u6839\u636e\u9700\u8981\u8c03\u6574 DHT11_GPIO_PIN \u4e3a\u5176\u4ed6GPIO\u5f15\u811a<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u7cfb\u7edf\u542f\u52a8<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7f16\u8bd1\u5e76\u70e7\u5f55\u7a0b\u5e8f\u5230ESP32-C3<\/li>\n\n\n\n<li>\u8bbe\u5907\u4f1a\u81ea\u52a8\u8fde\u63a5WiFi\u5e76\u542f\u52a8Web\u670d\u52a1\u5668<\/li>\n\n\n\n<li>\u901a\u8fc7\u4e32\u53e3\u76d1\u89c6\u5668\u67e5\u770b\u8bbe\u5907IP\u5730\u5740<\/li>\n\n\n\n<li>\u5728\u6d4f\u89c8\u5668\u4e2d\u8bbf\u95ee\u8bbe\u5907IP\u5730\u5740\uff0c\u5373\u53ef\u770b\u5230Web\u754c\u9762<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4. Web\u754c\u9762\u64cd\u4f5c<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u9608\u503c\u8bbe\u7f6e \uff1a\u8bbe\u7f6e\u6e29\u5ea6\u548c\u6e7f\u5ea6\u7684\u4e0a\u4e0b\u9650<\/li>\n\n\n\n<li>NTP\u8bbe\u7f6e \uff1a\u8bbe\u7f6eNTP\u540c\u6b65\u95f4\u9694\u548c\u670d\u52a1\u5668\u5730\u5740<\/li>\n\n\n\n<li>\u6570\u636e\u5bfc\u51fa \uff1a\u70b9\u51fb&#8221;\u5bfc\u51faCSV\u6570\u636e&#8221;\u4e0b\u8f7d\u5386\u53f2\u6570\u636e<\/li>\n\n\n\n<li>\u8fdc\u7a0b\u63a7\u5236 \uff1a\u70b9\u51fb&#8221;\u91cd\u542f\u8bbe\u5907&#8221;\u6216&#8221;\u6e05\u9664\u5386\u53f2\u8bb0\u5f55&#8221;\u6267\u884c\u76f8\u5e94\u64cd\u4f5c<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u6280\u672f\u5b9e\u73b0<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \u6838\u5fc3\u6a21\u5757<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>DHT11\u9a71\u52a8 \uff1a\u5b9e\u73b0DHT11\u4f20\u611f\u5668\u7684\u521d\u59cb\u5316\u548c\u6570\u636e\u8bfb\u53d6<\/li>\n\n\n\n<li>\u6570\u636e\u5b58\u50a8 \uff1a\u4f7f\u7528SPIFFS\u6587\u4ef6\u7cfb\u7edf\u5b58\u50a8\u5386\u53f2\u6570\u636e<\/li>\n\n\n\n<li>Web\u670d\u52a1\u5668 \uff1a\u57fa\u4e8eESP-IDF\u7684HTTP\u670d\u52a1\u5668\u5b9e\u73b0<\/li>\n\n\n\n<li>NTP\u540c\u6b65 \uff1a\u4f7f\u7528ESP-IDF\u7684SNTP\u5e93\u5b9e\u73b0\u65f6\u95f4\u540c\u6b65<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u4efb\u52a1\u7ba1\u7406<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>dht11_task \uff1a\u8d1f\u8d23DHT11\u6570\u636e\u91c7\u96c6\u548c\u5b58\u50a8<\/li>\n\n\n\n<li>HTTP\u670d\u52a1\u5668\u4efb\u52a1 \uff1a\u5904\u7406Web\u8bf7\u6c42<\/li>\n\n\n\n<li>FreeRTOS\u4efb\u52a1 \uff1a\u7cfb\u7edf\u81ea\u52a8\u7ba1\u7406\u7684\u5176\u4ed6\u4efb\u52a1<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u6570\u636e\u6d41\u7a0b<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>DHT11\u4f20\u611f\u5668\u91c7\u96c6\u6e29\u6e7f\u5ea6\u6570\u636e<\/li>\n\n\n\n<li>\u6570\u636e\u5b58\u50a8\u5230\u5185\u5b58\u548cSPIFFS\u6587\u4ef6\u7cfb\u7edf<\/li>\n\n\n\n<li>Web\u670d\u52a1\u5668\u63d0\u4f9b\u6570\u636e\u67e5\u8be2\u548c\u63a7\u5236\u63a5\u53e3<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>\u6e90\u4ee3\u7801                               <\/summary>\n<pre class=\"wp-block-code\"><code>#include &lt;stdio.h>\n#include &lt;string.h>\n#include &lt;time.h>\n#include &lt;sys\/time.h>\n#include \"esp_wifi.h\"\n#include \"esp_event.h\"\n#include \"esp_log.h\"\n#include \"esp_system.h\"\n#include \"nvs_flash.h\"\n#include \"esp_netif.h\"\n#include \"esp_http_server.h\"\n#include \"driver\/gpio.h\"\n#include \"esp_spiffs.h\"\n#include \"esp_sntp.h\"\n#include \"freertos\/FreeRTOS.h\"\n#include \"freertos\/task.h\"\n#include \"freertos\/event_groups.h\"\n\n\/\/ \u914d\u7f6e\u53c2\u6570\n#define TAG \"DHT11_FINAL\"\n#define WIFI_SSID \"****\"       \/\/ \u66ff\u6362\u4e3a\u5b9e\u9645WiFi\u540d\u79f0\n#define WIFI_PASS \"****\"       \/\/ \u66ff\u6362\u4e3a\u5b9e\u9645WiFi\u5bc6\u7801\n#define WIFI_MAX_RETRY 5\n#define DHT11_GPIO_PIN GPIO_NUM_14     \/\/ DHT11\u6570\u636e\u5f15\u811a\n#define RECORD_INTERVAL 60             \/\/ \u57fa\u7840\u91c7\u96c6\u95f4\u9694\uff081\u5206\u949f=60\u79d2\uff09\n#define SAMPLING_24H_INTERVAL 60       \/\/ 24\u5c0f\u65f6\u5185\u91c7\u6837\u95f4\u9694\uff081\u5206\u949f=60\u79d2\uff09\n#define SAMPLING_HISTORY_INTERVAL 3600 \/\/ \u5386\u53f2\u91c7\u6837\u95f4\u9694\uff081\u5c0f\u65f6=3600\u79d2\uff09\n#define MAX_TOTAL_RECORDS 200          \/\/ \u6700\u5927\u8bb0\u5f55\u6570\n#define CHART_24H_POINTS 144           \/\/ 24\u5c0f\u65f6\u56fe\u8868\u6700\u5927\u70b9\u6570\uff0810\u5206\u949f\/\u70b9\uff09\n#define CHART_HISTORY_POINTS 100       \/\/ \u5386\u53f2\u56fe\u8868\u6700\u5927\u70b9\u6570\n#define SPIFFS_MAX_USAGE 80.0f         \/\/ SPIFFS\u6700\u5927\u4f7f\u7528\u7387\uff08%\uff09\n#define RECORD_FILE_PATH \"\/spiffs\/dht11_records.csv\"\n#define LOCAL_TIME_FORMAT \"%Y-%m-%d %H:%M:%S\"\n\n\/\/ NTP\u670d\u52a1\u5668\u914d\u7f6e\n#define NTP_SERVER1 \"ntp.aliyun.com\"\n#define NTP_SERVER2 \"cn.ntp.org.cn\"\n#define NTP_SERVER3 \"pool.ntp.org\"\n\n\/\/ \u6e29\u6e7f\u5ea6\u8bb0\u5f55\u7ed3\u6784\u4f53\ntypedef struct {\n    time_t timestamp;\n    float temperature;\n    float humidity;\n    char time_str&#91;32];\n} dht11_record_t;\n\n\/\/ \u4e8b\u4ef6\u6807\u5fd7\u4f4d\n#define WIFI_CONNECTED_BIT BIT0\n#define WIFI_FAIL_BIT      BIT1\n\n\/\/ \u5168\u5c40\u53d8\u91cf\nstatic EventGroupHandle_t s_wifi_event_group;\nstatic int s_retry_num = 0;\nstatic httpd_handle_t server = NULL;\nstatic float g_current_temp = -1.0f;\nstatic float g_current_hum = -1.0f;\nstatic int g_record_count = 0;\nstatic dht11_record_t g_all_records&#91;MAX_TOTAL_RECORDS];\n\n\/\/ \u9608\u503c\u8bbe\u7f6e\nstatic float g_temp_max = 30.0f;\nstatic float g_temp_min = 10.0f;\nstatic float g_hum_max = 80.0f;\nstatic float g_hum_min = 30.0f;\n\n\/\/ NTP\u8bbe\u7f6e\nstatic int g_ntp_interval = 3600;  \/\/ NTP\u540c\u6b65\u95f4\u9694\uff08\u79d2\uff09\nstatic char g_ntp_server1&#91;64] = NTP_SERVER1;\nstatic char g_ntp_server2&#91;64] = NTP_SERVER2;\nstatic char g_ntp_server3&#91;64] = NTP_SERVER3;\n\n\/\/ \u51fd\u6570\u58f0\u660e\nstatic esp_err_t thresholds_handler(httpd_req_t *req);\nstatic esp_err_t export_handler(httpd_req_t *req);\nstatic esp_err_t restart_handler(httpd_req_t *req);\nstatic esp_err_t clear_handler(httpd_req_t *req);\nstatic esp_err_t favicon_handler(httpd_req_t *req);\nstatic esp_err_t time_handler(httpd_req_t *req);\nstatic esp_err_t ntp_handler(httpd_req_t *req);\n\n\/**\n * @brief NTP\u65f6\u95f4\u521d\u59cb\u5316\n *\/\nstatic void ntp_time_init(void)\n{\n    setenv(\"TZ\", \"CST-8\", 1);  \/\/ \u8bbe\u7f6e\u65f6\u533a\u4e3a\u4e1c\u516b\u533a\n    tzset();\n\n    esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);\n\n    \/\/ \u6dfb\u52a0\u591a\u4e2aNTP\u670d\u52a1\u5668\uff0c\u63d0\u9ad8\u540c\u6b65\u53ef\u9760\u6027\n    esp_sntp_setservername(0, NTP_SERVER1);\n    esp_sntp_setservername(1, NTP_SERVER2);\n    esp_sntp_setservername(2, NTP_SERVER3);\n    esp_sntp_init();\n\n    \/\/ \u7b49\u5f85NTP\u540c\u6b65\n    int retry = 0;\n    time_t now = 0;\n    struct tm timeinfo = {0};\n    while (timeinfo.tm_year &lt; (2020 - 1900) &amp;&amp; retry &lt; 10) {\n        vTaskDelay(1000 \/ portTICK_PERIOD_MS);\n        time(&amp;now);\n        localtime_r(&amp;now, &amp;timeinfo);\n        retry++;\n    }\n\n    if (timeinfo.tm_year >= (2020 - 1900)) {\n        char time_buf&#91;32];\n        strftime(time_buf, sizeof(time_buf), LOCAL_TIME_FORMAT, &amp;timeinfo);\n        ESP_LOGI(TAG, \"NTP\u540c\u6b65\u6210\u529f \u2192 \u5f53\u524d\u65f6\u95f4: %s\", time_buf);\n    } else {\n        ESP_LOGE(TAG, \"NTP\u540c\u6b65\u5931\u8d25\");\n    }\n}\n\n\/**\n * @brief \u83b7\u53d6\u683c\u5f0f\u5316\u7684\u5f53\u524d\u65f6\u95f4\u5b57\u7b26\u4e32\n *\/\nstatic void get_current_time_str(char *time_str, size_t len)\n{\n    time_t now;\n    struct tm timeinfo;\n    time(&amp;now);\n    localtime_r(&amp;now, &amp;timeinfo);\n    strftime(time_str, len, LOCAL_TIME_FORMAT, &amp;timeinfo);\n}\n\n\/**\n * @brief \u521d\u59cb\u5316SPIFFS\u6587\u4ef6\u7cfb\u7edf\n *\/\nstatic esp_err_t spiffs_init(void)\n{\n    esp_vfs_spiffs_conf_t conf = {\n        .base_path = \"\/spiffs\",\n        .partition_label = NULL,\n        .max_files = 5,\n        .format_if_mount_failed = true  \/\/ \u6302\u8f7d\u5931\u8d25\u5219\u683c\u5f0f\u5316\n    };\n\n    esp_err_t ret = esp_vfs_spiffs_register(&amp;conf);\n    if (ret != ESP_OK) {\n        if (ret == ESP_FAIL) {\n            ESP_LOGE(TAG, \"SPIFFS\u6302\u8f7d\u5931\u8d25\uff0c\u65e0\u6cd5\u683c\u5f0f\u5316\");\n        } else if (ret == ESP_ERR_NOT_FOUND) {\n            ESP_LOGE(TAG, \"SPIFFS\u5206\u533a\u672a\u627e\u5230\uff08\u68c0\u67e5\u5206\u533a\u8868\uff09\");\n        } else {\n            ESP_LOGE(TAG, \"SPIFFS\u521d\u59cb\u5316\u5931\u8d25: %s\", esp_err_to_name(ret));\n        }\n        return ret;\n    }\n\n    \/\/ \u6253\u5370SPIFFS\u4f7f\u7528\u60c5\u51b5\n    size_t total = 0, used = 0;\n    ret = esp_spiffs_info(NULL, &amp;total, &amp;used);\n    if (ret != ESP_OK) {\n        ESP_LOGE(TAG, \"\u83b7\u53d6SPIFFS\u4fe1\u606f\u5931\u8d25: %s\", esp_err_to_name(ret));\n    } else {\n        ESP_LOGI(TAG, \"SPIFFS\u521d\u59cb\u5316\u6210\u529f \u2192 \u603b\u5927\u5c0f: %dKB, \u5df2\u7528: %dKB (%.1f%%)\",\n                 total\/1024, used\/1024, (float)used\/total*100);\n    }\n    return ESP_OK;\n}\n\n\/**\n * @brief \u6e05\u7406\u8fc7\u671f\u8bb0\u5f55\uff08\u6309\u81ea\u5b9a\u4e49\u6761\u6570+\u7a7a\u95f4\u53cc\u91cd\u9650\u5236\uff09\n *\/\nstatic void clean_expired_records(void)\n{\n    \/\/ 1. \u6309\u6761\u6570\u9650\u5236\u6e05\u7406\n    if (g_record_count > MAX_TOTAL_RECORDS) {\n        int delete_count = g_record_count - MAX_TOTAL_RECORDS;\n        memmove(g_all_records, g_all_records + delete_count,\n                (g_record_count - delete_count) * sizeof(dht11_record_t));\n        g_record_count -= delete_count;\n        ESP_LOGI(TAG, \"\u8bb0\u5f55\u6570\u8d85\u9650\uff08%d>%d\uff09\uff0c\u5220\u9664%d\u6761\u65e7\u8bb0\u5f55\",\n                 g_record_count + delete_count, MAX_TOTAL_RECORDS, delete_count);\n    }\n\n    \/\/ 2. \u6309SPIFFS\u7a7a\u95f4\u6e05\u7406\n    size_t total, used;\n    esp_spiffs_info(NULL, &amp;total, &amp;used);\n    if ((float)used\/total*100 > SPIFFS_MAX_USAGE) {\n        \/\/ \u5220\u9664\u6700\u65e9\u768420%\u8bb0\u5f55\n        int delete_count = g_record_count * 0.2;\n        if (delete_count &lt; 1) delete_count = 1;\n        \n        memmove(g_all_records, g_all_records + delete_count,\n                (g_record_count - delete_count) * sizeof(dht11_record_t));\n        g_record_count -= delete_count;\n        \n        ESP_LOGI(TAG, \"SPIFFS\u7a7a\u95f4\u4e0d\u8db3\uff08%.1f%%\uff09\uff0c\u5220\u9664%d\u6761\u65e7\u8bb0\u5f55\",\n                 (float)used\/total*100, delete_count);\n    }\n\n    \/\/ 3. \u4fdd\u5b58\u6e05\u7406\u540e\u7684\u8bb0\u5f55\u5230\u6587\u4ef6\n    FILE *f = fopen(RECORD_FILE_PATH, \"w\");\n    if (f) {\n        for (int i = 0; i &lt; g_record_count; i++) {\n            \/\/ \u4fee\u590d\uff1a%lu \u2192 %llu\uff08\u9002\u914d64\u4f4dtime_t\uff09\n            fprintf(f, \"%llu,%.1f,%.1f,%s\\n\",\n                    (unsigned long long)g_all_records&#91;i].timestamp,\n                    g_all_records&#91;i].temperature,\n                    g_all_records&#91;i].humidity,\n                    g_all_records&#91;i].time_str);\n        }\n        fclose(f);\n    }\n}\n\n\/**\n * @brief \u52a0\u8f7d\u5386\u53f2\u8bb0\u5f55\u4eceSPIFFS\n *\/\nstatic void load_records_from_spiffs(void)\n{\n    FILE *f = fopen(RECORD_FILE_PATH, \"r\");\n    if (!f) {\n        ESP_LOGW(TAG, \"\u8bb0\u5f55\u6587\u4ef6\u4e0d\u5b58\u5728\uff0c\u521b\u5efa\u65b0\u6587\u4ef6\");\n        f = fopen(RECORD_FILE_PATH, \"w\");\n        if (f) fclose(f);\n        return;\n    }\n\n    char line&#91;128];\n    g_record_count = 0;\n    while (fgets(line, sizeof(line), f) &amp;&amp; g_record_count &lt; MAX_TOTAL_RECORDS) {\n        dht11_record_t rec;\n        \/\/ \u4fee\u590d\uff1a%lu \u2192 %llu\uff08\u9002\u914d64\u4f4dtime_t\uff09\n        if (sscanf(line, \"%llu,%f,%f,%&#91;^\\n]\",\n                   (unsigned long long*)&amp;rec.timestamp, &amp;rec.temperature, &amp;rec.humidity, rec.time_str) == 4) {\n            g_all_records&#91;g_record_count++] = rec;\n        }\n    }\n    fclose(f);\n    ESP_LOGI(TAG, \"\u4eceSPIFFS\u52a0\u8f7d%d\u6761\u5386\u53f2\u8bb0\u5f55\", g_record_count);\n}\n\n\/**\n * @brief \u4fdd\u5b58\u5355\u6761\u8bb0\u5f55\u5230\u5185\u5b58+SPIFFS\n *\/\nstatic void save_single_record(float temp, float hum)\n{\n    if (temp &lt; 0 || hum &lt; 0) return;\n\n    \/\/ \u6821\u9a8c\u65f6\u95f4\u6709\u6548\u6027\uff08\u9632\u6b62\u65e0\u6548\u65f6\u95f4\u6233\uff09\n    time_t now = time(NULL);\n    if (now &lt; 1609459200) { \/\/ \u62d2\u7edd2021\u5e74\u4e4b\u524d\u7684\u65e0\u6548\u65f6\u95f4\n        ESP_LOGW(TAG, \"\u65e0\u6548\u65f6\u95f4\u6233\uff0c\u8df3\u8fc7\u4fdd\u5b58\u8bb0\u5f55\");\n        return;\n    }\n\n    \/\/ \u83b7\u53d6\u5f53\u524d\u65f6\u95f4\n    dht11_record_t rec;\n    rec.timestamp = now;\n    get_current_time_str(rec.time_str, sizeof(rec.time_str));\n    rec.temperature = temp;\n    rec.humidity = hum;\n\n    \/\/ \u6dfb\u52a0\u5230\u5185\u5b58\n    if (g_record_count &lt; MAX_TOTAL_RECORDS) {\n        g_all_records&#91;g_record_count++] = rec;\n    } else {\n        \/\/ \u6ee1\u4e86\u5219\u66ff\u6362\u6700\u65e7\u7684\n        memmove(g_all_records, g_all_records + 1, (MAX_TOTAL_RECORDS - 1) * sizeof(dht11_record_t));\n        g_all_records&#91;MAX_TOTAL_RECORDS - 1] = rec;\n    }\n\n    \/\/ \u8ffd\u52a0\u5230\u6587\u4ef6\n    FILE *f = fopen(RECORD_FILE_PATH, \"a\");\n    if (f) {\n        \/\/ \u4fee\u590d\uff1a%lu \u2192 %llu\uff08\u9002\u914d64\u4f4dtime_t\uff09\n        fprintf(f, \"%llu,%.1f,%.1f,%s\\n\",\n                (unsigned long long)rec.timestamp,\n                rec.temperature,\n                rec.humidity,\n                rec.time_str);\n        fclose(f);\n    }\n\n    \/\/ \u68c0\u67e5\u5e76\u6e05\u7406\u8fc7\u671f\u8bb0\u5f55\n    clean_expired_records();\n}\n\n\/**\n * @brief DHT11\u8bfb\u53d6\u51fd\u6570\n *\/\nstatic bool dht11_read_data(float *temp, float *hum)\n{\n    uint8_t data&#91;5] = {0};\n    gpio_set_direction(DHT11_GPIO_PIN, GPIO_MODE_OUTPUT);\n\n    gpio_set_level(DHT11_GPIO_PIN, 0);\n    esp_rom_delay_us(20000);\n    gpio_set_level(DHT11_GPIO_PIN, 1);\n    esp_rom_delay_us(30);\n\n    gpio_set_direction(DHT11_GPIO_PIN, GPIO_MODE_INPUT);\n    int retry = 0;\n    while (gpio_get_level(DHT11_GPIO_PIN) == 1 &amp;&amp; retry &lt; 100) {\n        esp_rom_delay_us(1);\n        retry++;\n    }\n    if (retry >= 100) return false;\n\n    esp_rom_delay_us(80);\n    if (gpio_get_level(DHT11_GPIO_PIN) == 0) return false;\n    esp_rom_delay_us(80);\n\n    for (int i = 0; i &lt; 40; i++) {\n        retry = 0;\n        while (gpio_get_level(DHT11_GPIO_PIN) == 0 &amp;&amp; retry &lt; 100) {\n            esp_rom_delay_us(1);\n            retry++;\n        }\n        if (retry >= 100) return false;\n        \n        esp_rom_delay_us(40);\n        data&#91;i\/8] &lt;&lt;= 1;\n        if (gpio_get_level(DHT11_GPIO_PIN) == 1) data&#91;i\/8] |= 1;\n        \n        \/\/ \u6dfb\u52a0\u91cd\u8bd5\u6b21\u6570\u9650\u5236\uff0c\u907f\u514d\u65e0\u9650\u5faa\u73af\n        retry = 0;\n        while (gpio_get_level(DHT11_GPIO_PIN) == 1 &amp;&amp; retry &lt; 100) {\n            esp_rom_delay_us(1);\n            retry++;\n        }\n        if (retry >= 100) return false;\n    }\n\n    if ((data&#91;0] + data&#91;1] + data&#91;2] + data&#91;3]) != data&#91;4]) return false;\n\n    \/\/ \u8ba1\u7b97\u6e29\u6e7f\u5ea6\u503c\uff0c\u5305\u62ec\u5c0f\u6570\u90e8\u5206\n    *hum = (float)data&#91;0] + (float)data&#91;1] \/ 10.0f;\n    *temp = (float)data&#91;2] + (float)data&#91;3] \/ 10.0f;\n    return true;\n}\n\n\/**\n * @brief DHT11\u91c7\u96c6\u4efb\u52a1\uff08\u65b0\u589e\u5206\u7ea7\u91c7\u6837\uff09\n *\/\nstatic void dht11_task(void *arg)\n{\n    if (spiffs_init() != ESP_OK) {\n        ESP_LOGE(TAG, \"SPIFFS\u521d\u59cb\u5316\u5931\u8d25\uff0c\u91c7\u96c6\u4efb\u52a1\u9000\u51fa\");\n        vTaskDelete(NULL);\n        return;\n    }\n\n    \/\/ \u52a0\u8f7d\u5386\u53f2\u8bb0\u5f55\n    load_records_from_spiffs();\n\n    \/\/ \u7b49\u5f85WiFi\u8fde\u63a5\u540e\u540c\u6b65NTP\n    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,\n            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);\n    if (bits &amp; WIFI_CONNECTED_BIT) {\n        ntp_time_init();\n    }\n\n    \/\/ \u91c7\u6837\u65f6\u95f4\u6233\u8bb0\u5f55\n    time_t last_24h_sample = 0;\n    time_t last_history_sample = 0;\n\n    while (1) {\n        float temp, hum;\n        if (dht11_read_data(&amp;temp, &amp;hum)) {\n            g_current_temp = temp;\n            g_current_hum = hum;\n            time_t now = time(NULL);\n\n            \/\/ \u9608\u503c\u62a5\u8b66\u68c0\u67e5\n            if (temp > g_temp_max) {\n                ESP_LOGE(TAG, \"\u26a0\ufe0f \u6e29\u5ea6\u62a5\u8b66\uff1a\u5f53\u524d\u6e29\u5ea6 %.1f\u2103 \u8d85\u8fc7\u4e0a\u9650 %.1f\u2103\", temp, g_temp_max);\n            } else if (temp &lt; g_temp_min) {\n                ESP_LOGE(TAG, \"\u26a0\ufe0f \u6e29\u5ea6\u62a5\u8b66\uff1a\u5f53\u524d\u6e29\u5ea6 %.1f\u2103 \u4f4e\u4e8e\u4e0b\u9650 %.1f\u2103\", temp, g_temp_min);\n            }\n            \n            if (hum > g_hum_max) {\n                ESP_LOGE(TAG, \"\u26a0\ufe0f \u6e7f\u5ea6\u62a5\u8b66\uff1a\u5f53\u524d\u6e7f\u5ea6 %.1f%% \u8d85\u8fc7\u4e0a\u9650 %.1f%%\", hum, g_hum_max);\n            } else if (hum &lt; g_hum_min) {\n                ESP_LOGE(TAG, \"\u26a0\ufe0f \u6e7f\u5ea6\u62a5\u8b66\uff1a\u5f53\u524d\u6e7f\u5ea6 %.1f%% \u4f4e\u4e8e\u4e0b\u9650 %.1f%%\", hum, g_hum_min);\n            }\n\n            \/\/ \u5206\u7ea7\u91c7\u6837\u903b\u8f91\n            bool need_save = false;\n            \n            \/\/ 1. \u8fd124\u5c0f\u65f6\u5185\uff1a\u6bcf10\u5206\u949f\u91c7\u68371\u6761\n            time_t twenty_four_hours_ago = now - 24 * 3600;\n            if (g_record_count == 0 || now - last_24h_sample >= SAMPLING_24H_INTERVAL) {\n                need_save = true;\n                last_24h_sample = now;\n            }\n            \n            \/\/ 2. \u8d85\u8fc724\u5c0f\u65f6\uff1a\u6bcf1\u5c0f\u65f6\u91c7\u68371\u6761\n            else if (g_record_count > 0 &amp;&amp; g_all_records&#91;0].timestamp &lt; twenty_four_hours_ago &amp;&amp;\n                     now - last_history_sample >= SAMPLING_HISTORY_INTERVAL) {\n                need_save = true;\n                last_history_sample = now;\n            }\n\n            \/\/ \u4fdd\u5b58\u91c7\u6837\u8bb0\u5f55\n            if (need_save) {\n                save_single_record(temp, hum);\n                ESP_LOGI(TAG, \"\u4fdd\u5b58\u91c7\u6837\u8bb0\u5f55 \u2192 %s | \u6e29\u5ea6:%.1f\u2103 \u6e7f\u5ea6:%.1f%%RH\",\n                         g_all_records&#91;g_record_count-1].time_str, temp, hum);\n            }\n        } else {\n            g_current_temp = -1.0f;\n            g_current_hum = -1.0f;\n        }\n\n        \/\/ \u6bcf30\u79d2\u5f3a\u5236\u5199\u5165\u4e00\u6b21\uff08\u907f\u514d\u7f13\u5b58\u672a\u5199\uff09\n        static int write_counter = 0;\n        write_counter++;\n        if (write_counter >= 15 &amp;&amp; g_current_temp >= 0) {  \/\/ 15*2\u79d2=30\u79d2\uff0c\u4e14\u6570\u636e\u6709\u6548\n            save_single_record(g_current_temp, g_current_hum);\n            write_counter = 0;\n        }\n\n        vTaskDelay(RECORD_INTERVAL * 1000 \/ portTICK_PERIOD_MS);\n    }\n}\n\n\/**\n * @brief \u6784\u5efa\u5206\u7ea7\u56fe\u8868\u6570\u636e\uff08\u589e\u52a0\u7f13\u51b2\u533a\u8d8a\u754c\u68c0\u67e5\uff09\n *\/\nstatic void build_chart_data(char *chart_24h, char *chart_history, size_t len)\n{\n    time_t now = time(NULL);\n    time_t twenty_four_hours_ago = now - 24 * 3600;\n\n    \/\/ \u521d\u59cb\u5316\u6570\u636e\u5b57\u7b26\u4e32\uff08\u5b89\u5168\u957f\u5ea6\u68c0\u67e5\uff09\n    snprintf(chart_24h, len, \"var chart24h = {time:&#91;], temp:&#91;], hum:&#91;]};\");\n    snprintf(chart_history, len, \"var chartHistory = {time:&#91;], temp:&#91;], hum:&#91;]};\");\n\n    \/\/ \u4e34\u65f6\u5b58\u50a8\u56fe\u8868\u6570\u636e\n    char temp_buf&#91;16], hum_buf&#91;16];\n    int count_24h = 0, count_history = 0;\n    \n    \/\/ \u4e34\u65f6\u5b58\u50a824\u5c0f\u65f6\u6570\u636e\u7684\u6570\u7ec4\uff0c\u7528\u4e8e\u4fdd\u6301\u65f6\u95f4\u987a\u5e8f\n    typedef struct {\n        char time_str&#91;32];\n        float temperature;\n        float humidity;\n    } chart_point_t;\n    chart_point_t chart_24h_points&#91;CHART_24H_POINTS];\n    chart_point_t chart_history_points&#91;CHART_HISTORY_POINTS];\n\n    \/\/ \u4ece\u6700\u65b0\u7684\u8bb0\u5f55\u5f00\u59cb\u904d\u5386\uff0c\u5ffd\u7565\u65e7\u6570\u636e\n    for (int i = g_record_count - 1; i >= 0; i--) {\n        dht11_record_t *rec = &amp;g_all_records&#91;i];\n\n        \/\/ 1. \u8fd124\u5c0f\u65f6\u6570\u636e\uff08\u6700\u591a1440\u4e2a\u70b9\uff09\n        if (rec->timestamp >= twenty_four_hours_ago &amp;&amp; count_24h &lt; CHART_24H_POINTS) {\n            \/\/ \u4fdd\u5b58\u5230\u4e34\u65f6\u6570\u7ec4\uff0c\u7a0d\u540e\u6309\u65f6\u95f4\u987a\u5e8f\u6dfb\u52a0\n            strncpy(chart_24h_points&#91;count_24h].time_str, rec->time_str, sizeof(chart_24h_points&#91;count_24h].time_str) - 1);\n            chart_24h_points&#91;count_24h].temperature = rec->temperature;\n            chart_24h_points&#91;count_24h].humidity = rec->humidity;\n            count_24h++;\n        }\n        \/\/ 2. \u5386\u53f2\u6570\u636e\uff08\u8d85\u8fc724\u5c0f\u65f6\uff0c\u6700\u591a100\u4e2a\u70b9\uff09\n        else if (rec->timestamp &lt; twenty_four_hours_ago &amp;&amp; count_history &lt; CHART_HISTORY_POINTS) {\n            \/\/ \u4fdd\u5b58\u5230\u4e34\u65f6\u6570\u7ec4\uff0c\u7a0d\u540e\u6309\u65f6\u95f4\u987a\u5e8f\u6dfb\u52a0\n            strncpy(chart_history_points&#91;count_history].time_str, rec->time_str, sizeof(chart_history_points&#91;count_history].time_str) - 1);\n            chart_history_points&#91;count_history].temperature = rec->temperature;\n            chart_history_points&#91;count_history].humidity = rec->humidity;\n            count_history++;\n        }\n    }\n\n    \/\/ \u6309\u65f6\u95f4\u987a\u5e8f\uff08\u4ece\u8fdc\u5230\u8fd1\uff09\u6dfb\u52a024\u5c0f\u65f6\u6570\u636e\u5230\u56fe\u8868\n    for (int i = count_24h - 1; i >= 0; i--) {\n        size_t current_len = strlen(chart_24h);\n        if (current_len + 128 &lt; len) {\n            strncat(chart_24h, \"chart24h.time.push('\", len - current_len - 1);\n            strncat(chart_24h, chart_24h_points&#91;i].time_str, len - strlen(chart_24h) - 1);\n            strncat(chart_24h, \"');\", len - strlen(chart_24h) - 1);\n            \n            snprintf(temp_buf, sizeof(temp_buf), \"%.1f\", chart_24h_points&#91;i].temperature);\n            strncat(chart_24h, \"chart24h.temp.push(\", len - strlen(chart_24h) - 1);\n            strncat(chart_24h, temp_buf, len - strlen(chart_24h) - 1);\n            strncat(chart_24h, \");\", len - strlen(chart_24h) - 1);\n            \n            snprintf(hum_buf, sizeof(hum_buf), \"%.1f\", chart_24h_points&#91;i].humidity);\n            strncat(chart_24h, \"chart24h.hum.push(\", len - strlen(chart_24h) - 1);\n            strncat(chart_24h, hum_buf, len - strlen(chart_24h) - 1);\n            strncat(chart_24h, \");\", len - strlen(chart_24h) - 1);\n        }\n    }\n\n    \/\/ \u6309\u65f6\u95f4\u987a\u5e8f\uff08\u4ece\u8fdc\u5230\u8fd1\uff09\u6dfb\u52a0\u5386\u53f2\u6570\u636e\u5230\u56fe\u8868\n    for (int i = count_history - 1; i >= 0; i--) {\n        size_t current_len = strlen(chart_history);\n        if (current_len + 128 &lt; len) {\n            strncat(chart_history, \"chartHistory.time.push('\", len - current_len - 1);\n            strncat(chart_history, chart_history_points&#91;i].time_str, len - strlen(chart_history) - 1);\n            strncat(chart_history, \"');\", len - strlen(chart_history) - 1);\n            \n            snprintf(temp_buf, sizeof(temp_buf), \"%.1f\", chart_history_points&#91;i].temperature);\n            strncat(chart_history, \"chartHistory.temp.push(\", len - strlen(chart_history) - 1);\n            strncat(chart_history, temp_buf, len - strlen(chart_history) - 1);\n            strncat(chart_history, \");\", len - strlen(chart_history) - 1);\n            \n            snprintf(hum_buf, sizeof(hum_buf), \"%.1f\", chart_history_points&#91;i].humidity);\n            strncat(chart_history, \"chartHistory.hum.push(\", len - strlen(chart_history) - 1);\n            strncat(chart_history, hum_buf, len - strlen(chart_history) - 1);\n            strncat(chart_history, \");\", len - strlen(chart_history) - 1);\n        }\n    }\n\n    \/\/ \u6dfb\u52a0\u6700\u65b0\u7684\u5b9e\u65f6\u6570\u636e\uff08\u5982\u679c\u6709\uff09\n    if (g_current_temp >= 0) {\n        char current_time_str&#91;32];\n        get_current_time_str(current_time_str, sizeof(current_time_str));\n        \n        size_t current_len = strlen(chart_24h);\n        if (current_len + 128 &lt; len) {\n            strncat(chart_24h, \"chart24h.time.push('\", len - current_len - 1);\n            strncat(chart_24h, current_time_str, len - strlen(chart_24h) - 1);\n            strncat(chart_24h, \"');\", len - strlen(chart_24h) - 1);\n            \n            snprintf(temp_buf, sizeof(temp_buf), \"%.1f\", g_current_temp);\n            strncat(chart_24h, \"chart24h.temp.push(\", len - strlen(chart_24h) - 1);\n            strncat(chart_24h, temp_buf, len - strlen(chart_24h) - 1);\n            strncat(chart_24h, \");\", len - strlen(chart_24h) - 1);\n            \n            snprintf(hum_buf, sizeof(hum_buf), \"%.1f\", g_current_hum);\n            strncat(chart_24h, \"chart24h.hum.push(\", len - strlen(chart_24h) - 1);\n            strncat(chart_24h, hum_buf, len - strlen(chart_24h) - 1);\n            strncat(chart_24h, \");\", len - strlen(chart_24h) - 1);\n            \n            count_24h++;\n        }\n    }\n\n    ESP_LOGI(TAG, \"\u56fe\u8868\u6570\u636e\u6784\u5efa\u5b8c\u6210 \u2192 24\u5c0f\u65f6:%d\u70b9, \u5386\u53f2:%d\u70b9\", count_24h, count_history);\n}\n\n\/**\n * @brief Web\u6839\u9875\u9762\u5904\u7406\uff08\u53cc\u56fe\u8868+\u5728\u7ebfECharts\uff0c\u5806\u5185\u5b58\u5206\u914d\uff09\n *\/\nstatic esp_err_t root_handler(httpd_req_t *req)\n{\n    \/\/ WiFi\u72b6\u6001\n    wifi_ap_record_t ap_info;\n    char rssi_str&#91;64] = \"\u672a\u77e5\";\n    char ssid_str&#91;64] = \"\u672a\u8fde\u63a5\";\n    if (esp_wifi_sta_get_ap_info(&amp;ap_info) == ESP_OK) {\n        snprintf(ssid_str, sizeof(ssid_str), \"%s\", ap_info.ssid);\n        snprintf(rssi_str, sizeof(rssi_str), \"%d dBm\", ap_info.rssi);\n    }\n\n    \/\/ \u5f53\u524d\u65f6\u95f4\n    char current_time&#91;32];\n    get_current_time_str(current_time, sizeof(current_time));\n\n    \/\/ \u5806\u5185\u5b58\u5206\u914d\u56fe\u8868\u7f13\u51b2\u533a\uff08\u907f\u514d\u6808\u6ea2\u51fa\uff09\n    char *chart_24h = (char *)malloc(16384);\n    char *chart_history = (char *)malloc(16384);\n    char *html = (char *)malloc(65536);\n    \n    \/\/ \u5185\u5b58\u5206\u914d\u5931\u8d25\u5904\u7406\n    if (!chart_24h || !chart_history || !html) {\n        ESP_LOGE(TAG, \"\u5806\u5185\u5b58\u5206\u914d\u5931\u8d25\");\n        free(chart_24h);\n        free(chart_history);\n        free(html);\n        httpd_resp_send_500(req);\n        return ESP_FAIL;\n    }\n\n    \/\/ \u521d\u59cb\u5316\u7f13\u51b2\u533a\n    memset(chart_24h, 0, 16384);\n    memset(chart_history, 0, 16384);\n    memset(html, 0, 65536);\n\n    \/\/ \u6784\u5efa\u56fe\u8868\u6570\u636e\n    build_chart_data(chart_24h, chart_history, 16384);\n\n    \/\/ \u6784\u5efa\u5f53\u524d\u6e29\u6e7f\u5ea6\u5b57\u7b26\u4e32\n    char temp_hum_str&#91;128] = {0};\n    if (g_current_temp >= 0) {\n        snprintf(temp_hum_str, sizeof(temp_hum_str),\n            \"&lt;b>\u5f53\u524d\u6e29\u5ea6\uff1a&lt;\/b>%.1f \u2103 &amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;b>\u5f53\u524d\u6e7f\u5ea6\uff1a&lt;\/b>%.1f %%RH\",\n            g_current_temp, g_current_hum);\n    } else {\n        snprintf(temp_hum_str, sizeof(temp_hum_str),\n            \"&lt;div class='error'>DHT11\u8bfb\u53d6\u5931\u8d25\uff08\u68c0\u67e5\u63a5\u7ebf\uff09&lt;\/div>\");\n    }\n\n    \/\/ \u6784\u5efaHTML\uff08\u4e00\u6b21\u6027\u62fc\u63a5\uff0c\u907f\u514dstrcat\u6ea2\u51fa\uff09\n    snprintf(html, 65536,\n        \"&lt;!DOCTYPE html>\"\n        \"&lt;html lang='zh-CN'>\"\n        \"&lt;head>\"\n        \"&lt;meta charset='UTF-8'>\"\n        \"&lt;meta http-equiv='refresh' content='60'>\"  \/\/ 1\u5206\u949f\u5237\u65b0\u4e00\u6b21\n        \"&lt;title>ESP32-C3 \u6e29\u6e7f\u5ea6\u76d1\u63a7&lt;\/title>\"\n        \/\/ \u6539\u7528\u5728\u7ebfECharts CDN\uff08\u65e0\u9700\u672c\u5730\u6587\u4ef6\uff09\n        \"&lt;script src='https:\/\/cdn.jsdelivr.net\/npm\/echarts\/dist\/echarts.min.js'>&lt;\/script>\"\n        \"&lt;style>\"\n        \"body {margin:20px; font-family:Arial; max-width:1200px; margin:0 auto;}\"\n        \".header {text-align:center; margin:20px 0;}\"\n        \".current-data {font-size:22px; margin:20px 0; color:#2c3e50;}\"\n        \".error {color:#e74c3c;}\"\n        \".chart-container {width:100%%; height:400px; margin:30px 0;}\"\n        \".info {font-size:14px; color:#7f8c8d; margin:10px 0;}\"\n        \"&lt;\/style>\"\n        \"&lt;\/head>\"\n        \"&lt;body>\"\n        \"&lt;div class='header'>\"\n        \"&lt;h1>ESP32-C3 DHT11 \u6e29\u6e7f\u5ea6\u76d1\u63a7&lt;\/h1>\"\n        \"&lt;div id='currentTime' style='font-size:24px; font-weight:bold; text-align:center; margin:20px 0; color:#2c3e50;'>%s&lt;\/div>\"\n        \"&lt;p class='info'>\u66f4\u65b0\u65f6\u95f4: %s | WiFi: %s (\u4fe1\u53f7: %s) | \u603b\u8bb0\u5f55\u6570: %d&lt;\/p>\"\n        \"&lt;\/div>\"\n        \"&lt;div class='current-data'>%s&lt;\/div>\"\n        \"&lt;h2>\u9608\u503c\u8bbe\u7f6e&lt;\/h2>\"\n        \"&lt;div class='settings'>\"\n        \"&lt;form id='thresholdForm'>\"\n        \"&lt;label>\u6e29\u5ea6\u4e0a\u9650: &lt;input type='number' id='tempMax' value='30' step='0.1'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;label>\u6e29\u5ea6\u4e0b\u9650: &lt;input type='number' id='tempMin' value='10' step='0.1'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;label>\u6e7f\u5ea6\u4e0a\u9650: &lt;input type='number' id='humMax' value='80' step='0.1'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;label>\u6e7f\u5ea6\u4e0b\u9650: &lt;input type='number' id='humMin' value='30' step='0.1'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;button type='button' onclick='saveThresholds()'>\u4fdd\u5b58\u9608\u503c&lt;\/button>\"\n        \"&lt;\/form>\"\n        \"&lt;div id='thresholdStatus' style='margin-top:10px; color:green;'>&lt;\/div>\"\n        \"&lt;\/div>\"\n        \"&lt;h2>NTP\u8bbe\u7f6e&lt;\/h2>\"\n        \"&lt;div class='settings'>\"\n        \"&lt;form id='ntpForm'>\"\n        \"&lt;label>\u540c\u6b65\u95f4\u9694(\u79d2): &lt;input type='number' id='ntpInterval' value='3600' min='60' max='86400'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;label>\u670d\u52a1\u56681: &lt;input type='text' id='ntpServer1' value='pool.ntp.org' style='width:150px;'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;label>\u670d\u52a1\u56682: &lt;input type='text' id='ntpServer2' value='cn.ntp.org.cn' style='width:150px;'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;label>\u670d\u52a1\u56683: &lt;input type='text' id='ntpServer3' value='ntp.aliyun.com' style='width:150px;'>&lt;\/label> &amp;nbsp;\"\n        \"&lt;button type='button' onclick='saveNtpSettings()'>\u4fdd\u5b58NTP\u8bbe\u7f6e&lt;\/button>\"\n        \"&lt;\/form>\"\n        \"&lt;div id='ntpStatus' style='margin-top:10px; color:green;'>&lt;\/div>\"\n        \"&lt;\/div>\"\n        \"&lt;h2>\u6570\u636e\u5bfc\u51fa&lt;\/h2>\"\n        \"&lt;div class='export'>\"\n        \"&lt;a href='\/export' target='_blank'>\u5bfc\u51faCSV\u6570\u636e&lt;\/a>\"\n        \"&lt;\/div>\"\n        \"&lt;h2>\u8fdc\u7a0b\u63a7\u5236&lt;\/h2>\"\n        \"&lt;div class='control'>\"\n        \"&lt;button onclick='restartDevice()'>\u91cd\u542f\u8bbe\u5907&lt;\/button>\"\n        \"&lt;button onclick='clearRecords()'>\u6e05\u9664\u5386\u53f2\u8bb0\u5f55&lt;\/button>\"\n        \"&lt;div id='controlStatus' style='margin-top:10px; color:green;'>&lt;\/div>\"\n        \"&lt;\/div>\"\n        \"&lt;h2>\u8fd124\u5c0f\u65f6\u6e29\u6e7f\u5ea6\u66f2\u7ebf\uff08\u6bcf10\u5206\u949f1\u6761\uff09&lt;\/h2>\"\n        \"&lt;div id='chart24h' class='chart-container'>&lt;\/div>\"\n        \"&lt;h2>\u5386\u53f2\u6e29\u6e7f\u5ea6\u66f2\u7ebf\uff08\u8d85\u8fc71\u5929\uff0c\u6bcf1\u5c0f\u65f61\u6761\uff09&lt;\/h2>\"\n        \"&lt;div id='chartHistory' class='chart-container'>&lt;\/div>\"\n        \"&lt;script>\"\n        \"%s\"\n        \"%s\"\n        \/\/ \u521d\u59cb\u531624\u5c0f\u65f6\u56fe\u8868\n        \"var chart24hObj = echarts.init(document.getElementById('chart24h'));\"\n        \"chart24hObj.setOption({\"\n        \"    title: {text: '\u8fd124\u5c0f\u65f6\u6e29\u6e7f\u5ea6\u53d8\u5316', left: 'center'},\"\n        \"    tooltip: {trigger: 'axis'},\"\n        \"    legend: {data: &#91;'\u6e29\u5ea6(\u2103)', '\u6e7f\u5ea6(%%RH)'], bottom: 0},\"\n        \"    xAxis: {type: 'category', data: chart24h.time, axisLabel: {rotate: 45}},\"\n        \"    yAxis: {type: 'value'},\"\n        \"    series: &#91;\"\n        \"        {name: '\u6e29\u5ea6(\u2103)', type: 'line', data: chart24h.temp, smooth: true},\"\n        \"        {name: '\u6e7f\u5ea6(%%RH)', type: 'line', data: chart24h.hum, smooth: true}\"\n        \"    ]\"\n        \"});\"\n        \/\/ \u521d\u59cb\u5316\u5386\u53f2\u56fe\u8868\n        \"var chartHistoryObj = echarts.init(document.getElementById('chartHistory'));\"\n        \"chartHistoryObj.setOption({\"\n        \"    title: {text: '\u5386\u53f2\u6e29\u6e7f\u5ea6\u53d8\u5316', left: 'center'},\"\n        \"    tooltip: {trigger: 'axis'},\"\n        \"    legend: {data: &#91;'\u6e29\u5ea6(\u2103)', '\u6e7f\u5ea6(%%RH)'], bottom: 0},\"\n        \"    xAxis: {type: 'category', data: chartHistory.time, axisLabel: {rotate: 45}},\"\n        \"    yAxis: {type: 'value'},\"\n        \"    series: &#91;\"\n        \"        {name: '\u6e29\u5ea6(\u2103)', type: 'line', data: chartHistory.temp, smooth: true},\"\n        \"        {name: '\u6e7f\u5ea6(%%RH)', type: 'line', data: chartHistory.hum, smooth: true}\"\n        \"    ]\"\n        \"});\"\n        \/\/ \u81ea\u9002\u5e94\u7a97\u53e3\u5927\u5c0f\n        \"window.addEventListener('resize', function() {\"\n        \"    chart24hObj.resize();\"\n        \"    chartHistoryObj.resize();\"\n        \"});\"\n        \"\"\n        \"\/\/ \u4fdd\u5b58\u9608\u503c\u8bbe\u7f6e\"\n        \"function saveThresholds() {\"\n        \"    const tempMax = document.getElementById('tempMax').value;\"\n        \"    const tempMin = document.getElementById('tempMin').value;\"\n        \"    const humMax = document.getElementById('humMax').value;\"\n        \"    const humMin = document.getElementById('humMin').value;\"\n        \"    \"\n        \"    fetch('\/thresholds?tempMax=' + tempMax + '&amp;tempMin=' + tempMin + '&amp;humMax=' + humMax + '&amp;humMin=' + humMin)\"\n        \"        .then(response => response.text())\"\n        \"        .then(data => {\"\n        \"            document.getElementById('thresholdStatus').textContent = '\u9608\u503c\u4fdd\u5b58\u6210\u529f\uff01';\"\n        \"            setTimeout(() => {\"\n        \"                document.getElementById('thresholdStatus').textContent = '';\"\n        \"            }, 3000);\"\n        \"        })\"\n        \"        .catch(error => {\"\n        \"            document.getElementById('thresholdStatus').textContent = '\u4fdd\u5b58\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5';\"\n        \"        });\"\n        \"}\"\n        \"\"\n        \"\/\/ \u91cd\u542f\u8bbe\u5907\"\n        \"function restartDevice() {\"\n        \"    if (confirm('\u786e\u5b9a\u8981\u91cd\u542f\u8bbe\u5907\u5417\uff1f')) {\"\n        \"        fetch('\/restart')\"\n        \"            .then(response => response.text())\"\n        \"            .then(data => {\"\n        \"                document.getElementById('controlStatus').textContent = '\u8bbe\u5907\u6b63\u5728\u91cd\u542f...';\"\n        \"            });\"\n        \"    }\"\n        \"}\"\n        \"\"\n        \"\/\/ \u6e05\u9664\u5386\u53f2\u8bb0\u5f55\"\n        \"function clearRecords() {\"\n        \"    if (confirm('\u786e\u5b9a\u8981\u6e05\u9664\u6240\u6709\u5386\u53f2\u8bb0\u5f55\u5417\uff1f')) {\"\n        \"        fetch('\/clear')\"\n        \"            .then(response => response.text())\"\n        \"            .then(data => {\"\n        \"                document.getElementById('controlStatus').textContent = '\u5386\u53f2\u8bb0\u5f55\u5df2\u6e05\u9664\uff01';\"\n        \"                setTimeout(() => {\"\n        \"                    location.reload();\"\n        \"                }, 1000);\"\n        \"            });\"\n        \"    }\"\n        \"}\"\n        \"\"\n        \"\/\/ \u4fdd\u5b58NTP\u8bbe\u7f6e\"\n        \"function saveNtpSettings() {\"\n        \"    const interval = document.getElementById('ntpInterval').value;\"\n        \"    const server1 = document.getElementById('ntpServer1').value;\"\n        \"    const server2 = document.getElementById('ntpServer2').value;\"\n        \"    const server3 = document.getElementById('ntpServer3').value;\"\n        \"    \"\n        \"    fetch('\/ntp?interval=' + interval + '&amp;server1=' + encodeURIComponent(server1) + '&amp;server2=' + encodeURIComponent(server2) + '&amp;server3=' + encodeURIComponent(server3))\"\n        \"        .then(response => response.text())\"\n        \"        .then(data => {\"\n        \"            document.getElementById('ntpStatus').textContent = 'NTP\u8bbe\u7f6e\u4fdd\u5b58\u6210\u529f\uff01';\"\n        \"            setTimeout(() => {\"\n        \"                document.getElementById('ntpStatus').textContent = '';\"\n        \"            }, 3000);\"\n        \"        })\"\n        \"        .catch(error => {\"\n        \"            document.getElementById('ntpStatus').textContent = '\u4fdd\u5b58\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5';\"\n        \"        });\"\n        \"}\"\n        \"\"\n        \"\/\/ \u5b9e\u65f6\u66f4\u65b0\u65f6\u95f4\"\n        \"function updateTime() {\"\n        \"    fetch('\/time')\"\n        \"        .then(response => response.text())\"\n        \"        .then(data => {\"\n        \"            \/\/ \u66f4\u65b0\u5927\u5b57\u4f53\u65f6\u95f4\u663e\u793a\"\n        \"            const currentTimeElement = document.getElementById('currentTime');\"\n        \"            if (currentTimeElement) {\"\n        \"                currentTimeElement.textContent = data;\"\n        \"            }\"\n        \"            \/\/ \u66f4\u65b0\u4fe1\u606f\u680f\u65f6\u95f4\"\n        \"            const timeElement = document.querySelector('.info');\"\n        \"            if (timeElement) {\"\n        \"                const parts = timeElement.textContent.split('|');\"\n        \"                if (parts.length > 0) {\"\n        \"                    timeElement.textContent = '\u66f4\u65b0\u65f6\u95f4: ' + data + ' |' + parts.slice(1).join('|');\"\n        \"                }\"\n        \"            }\"\n        \"        });\"\n        \"}\"\n        \"\"\n        \"\/\/ \u6bcf\u79d2\u949f\u66f4\u65b0\u4e00\u6b21\u65f6\u95f4\"\n        \"setInterval(updateTime, 1000);\"\n        \"\/\/ \u9875\u9762\u52a0\u8f7d\u65f6\u7acb\u5373\u66f4\u65b0\u4e00\u6b21\"\n        \"updateTime();\"\n        \"&lt;\/script>\"\n        \"&lt;div style='text-align:center; margin-top:50px; padding:20px; font-size:14px; color:#7f8c8d; border-top:1px solid #eee;'>\"\n        \"Copyright 2026 &lt;a href='https:\/\/www.isunding.com' style='color:#3498db; text-decoration:none;' target='_blank'>iRabbit&lt;\/a> All Rights Reserved.\"\n        \"&lt;\/div>\"\n        \"&lt;\/body>\"\n        \"&lt;\/html>\",\n        current_time, current_time, ssid_str, rssi_str, g_record_count,\n        temp_hum_str, chart_24h, chart_history);\n\n    \/\/ \u6dfb\u52a0\u7f13\u5b58\u63a7\u5236\u5934\uff0c\u786e\u4fdd\u6d4f\u89c8\u5668\u4e0d\u4f1a\u7f13\u5b58\u9875\u9762\u5185\u5bb9\n    httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache, no-store, must-revalidate\");\n    httpd_resp_set_hdr(req, \"Pragma\", \"no-cache\");\n    httpd_resp_set_hdr(req, \"Expires\", \"0\");\n    \n    \/\/ \u53d1\u9001HTTP\u54cd\u5e94\n    httpd_resp_send(req, html, HTTPD_RESP_USE_STRLEN);\n\n    \/\/ \u91ca\u653e\u5806\u5185\u5b58\uff08\u9632\u6b62\u5185\u5b58\u6cc4\u6f0f\uff09\n    free(chart_24h);\n    free(chart_history);\n    free(html);\n\n    return ESP_OK;\n}\n\n\/**\n * @brief WiFi\u4e8b\u4ef6\u5904\u7406+Web\u670d\u52a1\u5668\u542f\u52a8\uff08\u589e\u5927\u670d\u52a1\u5668\u6808\u7a7a\u95f4\uff09\n *\/\nstatic void event_handler(void* arg, esp_event_base_t event_base,\n                          int32_t event_id, void* event_data)\n{\n    if (event_base == WIFI_EVENT &amp;&amp; event_id == WIFI_EVENT_STA_START) {\n        esp_wifi_connect();\n    } else if (event_base == WIFI_EVENT &amp;&amp; event_id == WIFI_EVENT_STA_DISCONNECTED) {\n        if (s_retry_num &lt; WIFI_MAX_RETRY) {\n            esp_wifi_connect();\n            s_retry_num++;\n            ESP_LOGI(TAG, \"WiFi\u65ad\u5f00\uff0c\u6b63\u5728\u91cd\u8fde\uff08%d\/%d\uff09\", s_retry_num, WIFI_MAX_RETRY);\n        } else {\n            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);\n        }\n    } else if (event_base == IP_EVENT &amp;&amp; event_id == IP_EVENT_STA_GOT_IP) {\n        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;\n        ESP_LOGI(TAG, \"WiFi\u8fde\u63a5\u6210\u529f \u2192 IP: \" IPSTR, IP2STR(&amp;event->ip_info.ip));\n        s_retry_num = 0;\n        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);\n        \n        if (server == NULL) {\n            httpd_config_t config = HTTPD_DEFAULT_CONFIG();\n            config.server_port = 80;\n            config.stack_size = 16384;  \/\/ \u589e\u5927HTTP\u670d\u52a1\u5668\u6808\u7a7a\u95f4\u523016KB\n            if (httpd_start(&amp;server, &amp;config) == ESP_OK) {\n                \/\/ \u6ce8\u518c\u8def\u7531\n                httpd_uri_t root_uri = {\n                    .uri = \"\/\",\n                    .method = HTTP_GET,\n                    .handler = root_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;root_uri);\n                \n                \/\/ \u9608\u503c\u8bbe\u7f6e\u8def\u7531\n                httpd_uri_t thresholds_uri = {\n                    .uri = \"\/thresholds\",\n                    .method = HTTP_GET,\n                    .handler = thresholds_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;thresholds_uri);\n                \n                \/\/ \u6570\u636e\u5bfc\u51fa\u8def\u7531\n                httpd_uri_t export_uri = {\n                    .uri = \"\/export\",\n                    .method = HTTP_GET,\n                    .handler = export_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;export_uri);\n                \n                \/\/ \u8bbe\u5907\u91cd\u542f\u8def\u7531\n                httpd_uri_t restart_uri = {\n                    .uri = \"\/restart\",\n                    .method = HTTP_GET,\n                    .handler = restart_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;restart_uri);\n                \n                \/\/ \u6e05\u9664\u8bb0\u5f55\u8def\u7531\n                httpd_uri_t clear_uri = {\n                    .uri = \"\/clear\",\n                    .method = HTTP_GET,\n                    .handler = clear_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;clear_uri);\n                \n                \/\/ \u5904\u7406favicon.ico\u8bf7\u6c42\uff0c\u907f\u514d404\u8b66\u544a\n                httpd_uri_t favicon_uri = {\n                    .uri = \"\/favicon.ico\",\n                    .method = HTTP_GET,\n                    .handler = favicon_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;favicon_uri);\n                \n                \/\/ \u5904\u7406\u65f6\u95f4\u8bf7\u6c42\uff0c\u7528\u4e8e\u7f51\u9875\u5b9e\u65f6\u66f4\u65b0\u65f6\u95f4\n                httpd_uri_t time_uri = {\n                    .uri = \"\/time\",\n                    .method = HTTP_GET,\n                    .handler = time_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;time_uri);\n                \n                \/\/ \u5904\u7406NTP\u8bbe\u7f6e\u8bf7\u6c42\n                httpd_uri_t ntp_uri = {\n                    .uri = \"\/ntp\",\n                    .method = HTTP_GET,\n                    .handler = ntp_handler,\n                    .user_ctx = NULL\n                };\n                httpd_register_uri_handler(server, &amp;ntp_uri);\n                \n                ESP_LOGI(TAG, \"Web\u670d\u52a1\u5668\u542f\u52a8 \u2192 \u8bbf\u95ee\u5730\u5740: http:\/\/\" IPSTR, IP2STR(&amp;event->ip_info.ip));\n            }\n        }\n    }\n}\n\n\/**\n * @brief WiFi\u521d\u59cb\u5316\n *\/\nvoid wifi_init_sta(void)\n{\n    s_wifi_event_group = xEventGroupCreate();\n\n    esp_err_t ret = nvs_flash_init();\n    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {\n        ESP_ERROR_CHECK(nvs_flash_erase());\n        ret = nvs_flash_init();\n    }\n    ESP_ERROR_CHECK(ret);\n\n    ESP_ERROR_CHECK(esp_netif_init());\n    \/\/ \u5c1d\u8bd5\u521b\u5efa\u9ed8\u8ba4\u4e8b\u4ef6\u5faa\u73af\uff0c\u5982\u679c\u5df2\u7ecf\u5b58\u5728\u5219\u5ffd\u7565\u9519\u8bef\n    esp_err_t err = esp_event_loop_create_default();\n    if (err != ESP_OK &amp;&amp; err != ESP_ERR_INVALID_STATE) {\n        ESP_ERROR_CHECK(err);\n    }\n    esp_netif_create_default_wifi_sta();\n\n    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();\n    ESP_ERROR_CHECK(esp_wifi_init(&amp;cfg));\n\n    esp_event_handler_instance_t instance_any_id, instance_got_ip;\n    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,\n                    ESP_EVENT_ANY_ID, &amp;event_handler, NULL, &amp;instance_any_id));\n    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,\n                    IP_EVENT_STA_GOT_IP, &amp;event_handler, NULL, &amp;instance_got_ip));\n\n    wifi_config_t wifi_config = {\n    .sta = {\n        .ssid = WIFI_SSID,                  \/\/ WiFi\u540d\u79f0\n        .password = WIFI_PASS,              \/\/ WiFi\u5bc6\u7801\n    },\n};\n    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));\n    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &amp;wifi_config));\n    ESP_ERROR_CHECK(esp_wifi_start());\n\n    ESP_LOGI(TAG, \"WiFi\u521d\u59cb\u5316\u5b8c\u6210\uff0c\u7b49\u5f85\u8fde\u63a5...\");\n}\n\n\/**\n * @brief DHT11\u5f15\u811a\u521d\u59cb\u5316\uff08\u8865\u5145\u5b8c\u6574\uff09\n *\/\nvoid dht11_init(gpio_num_t gpio_num)\n{\n    gpio_config_t io_conf = {\n        .pin_bit_mask = (1ULL &lt;&lt; gpio_num), \/\/ \u9002\u914d\u4f20\u5165\u7684D12(GPIO12)\n        .mode = GPIO_MODE_INPUT_OUTPUT_OD,  \/\/ \u5f00\u6f0f\u6a21\u5f0f\uff08DHT11\u901a\u4fe1\u9700\u8981\uff09\n        .pull_up_en = GPIO_PULLUP_DISABLE,  \/\/ \u7981\u7528\u5185\u90e8\u4e0a\u62c9\uff08DHT11\u81ea\u5e26\u7535\u963b\uff09\n        .pull_down_en = GPIO_PULLDOWN_DISABLE,\n        .intr_type = GPIO_INTR_DISABLE,\n    };\n    gpio_config(&amp;io_conf);\n    \/\/ \u521d\u59cb\u7535\u5e73\u7f6e\u9ad8\uff08\u7b26\u5408DHT11\u7a7a\u95f2\u72b6\u6001\uff09\n    gpio_set_level(gpio_num, 1);\n    ESP_LOGI(TAG, \"DHT11 GPIO%d \u521d\u59cb\u5316\u5b8c\u6210\uff08\u7981\u7528\u5185\u90e8\u4e0a\u62c9\uff0c\u4f7f\u7528DHT11\u81ea\u5e26\u7535\u963b\uff09\", gpio_num);\n}\n\n\/**\n * @brief \u9608\u503c\u8bbe\u7f6e\u5904\u7406\u51fd\u6570\n *\/\nstatic esp_err_t thresholds_handler(httpd_req_t *req)\n{\n    char query_str&#91;128];\n    httpd_req_get_url_query_str(req, query_str, sizeof(query_str));\n    \n    \/\/ \u89e3\u6790\u67e5\u8be2\u53c2\u6570\n    float tempMax, tempMin, humMax, humMin;\n    if (sscanf(query_str, \"tempMax=%f&amp;tempMin=%f&amp;humMax=%f&amp;humMin=%f\", \n               &amp;tempMax, &amp;tempMin, &amp;humMax, &amp;humMin) == 4) {\n        g_temp_max = tempMax;\n        g_temp_min = tempMin;\n        g_hum_max = humMax;\n        g_hum_min = humMin;\n        ESP_LOGI(TAG, \"\u9608\u503c\u8bbe\u7f6e\u66f4\u65b0: \u6e29\u5ea6 %.1f-%.1f\u2103, \u6e7f\u5ea6 %.1f-%.1f%%\", \n                 tempMin, tempMax, humMin, humMax);\n    }\n    \n    httpd_resp_send(req, \"Thresholds saved\", HTTPD_RESP_USE_STRLEN);\n    return ESP_OK;\n}\n\n\/**\n * @brief \u6570\u636e\u5bfc\u51fa\u5904\u7406\u51fd\u6570\n *\/\nstatic esp_err_t export_handler(httpd_req_t *req)\n{\n    \/\/ \u8bbe\u7f6e\u54cd\u5e94\u5934\n    httpd_resp_set_type(req, \"text\/csv\");\n    httpd_resp_set_hdr(req, \"Content-Disposition\", \"attachment; filename=dht11_data.csv\");\n    \n    \/\/ \u53d1\u9001CSV\u8868\u5934\n    const char *header = \"Timestamp,Temperature(\u2103),Humidity(%%RH),Time\\n\";\n    httpd_resp_send_chunk(req, header, strlen(header));\n    \n    \/\/ \u53d1\u9001\u6570\u636e\n    char line&#91;128];\n    for (int i = 0; i &lt; g_record_count; i++) {\n        dht11_record_t *rec = &amp;g_all_records&#91;i];\n        snprintf(line, sizeof(line), \"%llu,%.1f,%.1f,%s\\n\",\n                 (unsigned long long)rec->timestamp,\n                 rec->temperature, rec->humidity, rec->time_str);\n        httpd_resp_send_chunk(req, line, strlen(line));\n    }\n    \n    httpd_resp_send_chunk(req, NULL, 0);\n    return ESP_OK;\n}\n\n\/**\n * @brief \u8bbe\u5907\u91cd\u542f\u5904\u7406\u51fd\u6570\n *\/\nstatic esp_err_t restart_handler(httpd_req_t *req)\n{\n    httpd_resp_send(req, \"Restarting device...\", HTTPD_RESP_USE_STRLEN);\n    \n    \/\/ \u5ef6\u8fdf\u91cd\u542f\uff0c\u786e\u4fdd\u54cd\u5e94\u53d1\u9001\u5b8c\u6210\n    vTaskDelay(pdMS_TO_TICKS(1000));\n    esp_restart();\n    \n    return ESP_OK;\n}\n\n\/**\n * @brief \u6e05\u9664\u8bb0\u5f55\u5904\u7406\u51fd\u6570\n *\/\nstatic esp_err_t clear_handler(httpd_req_t *req)\n{\n    \/\/ \u6e05\u7a7a\u5185\u5b58\u4e2d\u7684\u8bb0\u5f55\n    g_record_count = 0;\n    \n    \/\/ \u6e05\u7a7a\u6587\u4ef6\n    FILE *f = fopen(RECORD_FILE_PATH, \"w\");\n    if (f) fclose(f);\n    \n    ESP_LOGI(TAG, \"\u5386\u53f2\u8bb0\u5f55\u5df2\u6e05\u9664\");\n    httpd_resp_send(req, \"Records cleared\", HTTPD_RESP_USE_STRLEN);\n    return ESP_OK;\n}\n\n\/**\n * @brief \u5904\u7406favicon.ico\u8bf7\u6c42\uff0c\u907f\u514d404\u8b66\u544a\n *\/\nstatic esp_err_t favicon_handler(httpd_req_t *req)\n{\n    httpd_resp_set_status(req, \"404 Not Found\");\n    httpd_resp_send(req, NULL, 0);\n    return ESP_OK;\n}\n\n\/**\n * @brief \u5904\u7406\u65f6\u95f4\u8bf7\u6c42\uff0c\u8fd4\u56de\u5f53\u524d\u65f6\u95f4\n *\/\nstatic esp_err_t time_handler(httpd_req_t *req)\n{\n    char time_str&#91;32];\n    get_current_time_str(time_str, sizeof(time_str));\n    \n    \/\/ \u6dfb\u52a0\u7f13\u5b58\u63a7\u5236\u5934\uff0c\u786e\u4fdd\u6d4f\u89c8\u5668\u4e0d\u4f1a\u7f13\u5b58\u65f6\u95f4\u54cd\u5e94\n    httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache, no-store, must-revalidate\");\n    httpd_resp_set_hdr(req, \"Pragma\", \"no-cache\");\n    httpd_resp_set_hdr(req, \"Expires\", \"0\");\n    \n    httpd_resp_send(req, time_str, HTTPD_RESP_USE_STRLEN);\n    return ESP_OK;\n}\n\n\/**\n * @brief \u5904\u7406NTP\u8bbe\u7f6e\u8bf7\u6c42\n *\/\nstatic esp_err_t ntp_handler(httpd_req_t *req)\n{\n    char query_str&#91;256];\n    httpd_req_get_url_query_str(req, query_str, sizeof(query_str));\n    \n    \/\/ \u89e3\u6790\u67e5\u8be2\u53c2\u6570\n    int interval;\n    char server1&#91;64], server2&#91;64], server3&#91;64];\n    if (sscanf(query_str, \"interval=%d&amp;server1=%63&#91;^&amp;]&amp;server2=%63&#91;^&amp;]&amp;server3=%63s\", \n               &amp;interval, server1, server2, server3) >= 1) {\n        \/\/ \u66f4\u65b0NTP\u540c\u6b65\u95f4\u9694\n        if (interval >= 60 &amp;&amp; interval &lt;= 86400) {\n            g_ntp_interval = interval;\n        }\n        \n        \/\/ \u66f4\u65b0NTP\u670d\u52a1\u5668\u5730\u5740\n        if (strlen(server1) > 0) {\n            strncpy(g_ntp_server1, server1, sizeof(g_ntp_server1) - 1);\n        }\n        if (strlen(server2) > 0) {\n            strncpy(g_ntp_server2, server2, sizeof(g_ntp_server2) - 1);\n        }\n        if (strlen(server3) > 0) {\n            strncpy(g_ntp_server3, server3, sizeof(g_ntp_server3) - 1);\n        }\n        \n        \/\/ \u91cd\u542fNTP\u670d\u52a1\u4ee5\u5e94\u7528\u65b0\u8bbe\u7f6e\n        esp_sntp_stop();\n        esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);\n        esp_sntp_setservername(0, g_ntp_server1);\n        esp_sntp_setservername(1, g_ntp_server2);\n        esp_sntp_setservername(2, g_ntp_server3);\n        esp_sntp_init();\n        \n        ESP_LOGI(TAG, \"NTP\u8bbe\u7f6e\u66f4\u65b0: \u95f4\u9694 %d\u79d2, \u670d\u52a1\u56681: %s, \u670d\u52a1\u56682: %s, \u670d\u52a1\u56683: %s\", \n                 g_ntp_interval, g_ntp_server1, g_ntp_server2, g_ntp_server3);\n    }\n    \n    httpd_resp_send(req, \"NTP settings saved\", HTTPD_RESP_USE_STRLEN);\n    return ESP_OK;\n}\n\n\/**\n * @brief \u4e3b\u51fd\u6570\n *\/\nvoid app_main(void)\n{\n    ESP_LOGI(TAG, \"ESP32-C3 DHT11 \u76d1\u63a7\u7cfb\u7edf\u542f\u52a8\");\n    \n    \/\/ \u521d\u59cb\u5316WiFi\n    wifi_init_sta();\n\n    \/\/ \u521d\u59cb\u5316DHT11\u5f15\u811a\n    dht11_init(DHT11_GPIO_PIN);\n\n    \/\/ \u521b\u5efaDHT11\u91c7\u96c6\u4efb\u52a1\uff08\u589e\u5927\u6808\u7a7a\u95f4\u523016KB\u907f\u514d\u6ea2\u51fa\uff09\n    xTaskCreate(dht11_task, \"dht11_task\", 16384, NULL, 1, NULL);\n    ESP_LOGI(TAG, \"DHT11\u91c7\u96c6\u4efb\u52a1\u5df2\u542f\u52a8\");\n\n    \/\/ \u4e3b\u5faa\u73af\n    while (1) {\n        vTaskDelay(10000 \/ portTICK_PERIOD_MS);\n    }\n}<\/code><\/pre>\n<\/details>\n","protected":false},"excerpt":{"rendered":"<p>\u7cfb\u7edf\u6982\u8ff0 \u672c\u7cfb\u7edf\u57fa\u4e8eESP32-C3\u5f00\u53d1\u677f\u548cDHT1&#46;&#46;&#46;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_exactmetrics_skip_tracking":false,"footnotes":""},"categories":[22],"tags":[23,24],"class_list":["post-244","post","type-post","status-publish","format-standard","hentry","category-esp32","tag-esp32","tag-24"],"_links":{"self":[{"href":"https:\/\/www.isunding.com\/index.php?rest_route=\/wp\/v2\/posts\/244","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.isunding.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.isunding.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.isunding.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.isunding.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=244"}],"version-history":[{"count":5,"href":"https:\/\/www.isunding.com\/index.php?rest_route=\/wp\/v2\/posts\/244\/revisions"}],"predecessor-version":[{"id":255,"href":"https:\/\/www.isunding.com\/index.php?rest_route=\/wp\/v2\/posts\/244\/revisions\/255"}],"wp:attachment":[{"href":"https:\/\/www.isunding.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=244"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.isunding.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=244"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.isunding.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=244"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}