# ESP32_SCT013 ## 簡介 這是一個測量電流的專案,主要功能如下 * 測量 1~50A 的交流電流 * 透過 Wifi 與 MQTT 定時上傳 * 使用 TTGO T-Display,TFT 螢幕即時顯示 * 透過開發板上的兩顆按鈕,做簡易的測量校正 * 本程式由 grok3 協助撰寫 ||| |--|--| |![IMG_8834](https://hackmd.io/_uploads/Bk22Z2mhJx.jpg)|![IMG_8833](https://hackmd.io/_uploads/r136bhQ31e.jpg)| :::warning 本專案在家裡用 110V (1~12A) 吹風機當負載,誤差約在+-5%內。 但是拿到公司去量 208V 的負載,誤差變成+-50%,原因目前未知。 後續將使用 PZEM-004T 製作另一個版本 ::: ## 材料與電路 * 使用 TTGO T-Display (esp32) * 使用 SCT-013-050 (50A/1V) * 10K電阻兩個,100uF電解電容一個 * 接線如下圖 ![image](https://hackmd.io/_uploads/HkXTCiQnkl.png) ## 程式碼 ### TTGO_SCT013.ino ```cpp= // Used library Version Path // WiFi 2.0.0 /home/chihhaolai/.arduino15/packages/esp32/hardware/esp32/2.0.14/libraries/WiFi // Wire 2.0.0 /home/chihhaolai/.arduino15/packages/esp32/hardware/esp32/2.0.14/libraries/Wire // TFT_eSPI 2.5.43 /home/chihhaolai/Arduino/libraries/TFT_eSPI // SPI 2.0.0 /home/chihhaolai/.arduino15/packages/esp32/hardware/esp32/2.0.14/libraries/SPI // FS 2.0.0 /home/chihhaolai/.arduino15/packages/esp32/hardware/esp32/2.0.14/libraries/FS // SPIFFS 2.0.0 /home/chihhaolai/.arduino15/packages/esp32/hardware/esp32/2.0.14/libraries/SPIFFS // PubSubClient 2.8 /home/chihhaolai/Arduino/libraries/pubsubclient-master // EEPROM 2.0.0 /home/chihhaolai/.arduino15/packages/esp32/hardware/esp32/2.0.14/libraries/EEPROM // Button2 2.3.4 /home/chihhaolai/Arduino/libraries/Button2-master // // Used platform Version Path // esp32:esp32 2.0.14 /home/chihhaolai/.arduino15/packages/esp32/hardware/esp32/2.0.14 #include <WiFi.h> #include <esp_wifi.h> #include <Wire.h> #include <TFT_eSPI.h> #include <PubSubClient.h> #include <EEPROM.h> #include <Button2.h> // 新增 Button2 函數庫 #include "wifi_config.h" // 引用外部 WiFi 配置文件 #define ADC_PIN 36 #define RANDOM_SEED_PIN 32 TFT_eSPI tft = TFT_eSPI(); const char* VERSION = "2025-03-13a"; const char* topicBase = "SCT013/"; char mqttTopic[20]; char equipmentId[6] = "Eq01"; char clientId[30] = "ESP32_SCT013"; unsigned long lastMsgDisplay = 0; // 記錄最後一次成功顯示的時間 WiFiClient espClient; PubSubClient client(espClient); uint8_t newMACAddress[] = {0x30, 0xC6, 0xF7, 0x51, 0xB3, 0xF8}; const int SAMPLE_INTERVAL_US = 60; // 新增:取樣間隔 60us const int SAMPLES = 2778; // 針對 60Hz,10 個精確週期 const float ADC_SCALE = 3.3 / 4095.0; float BIAS_VOLTAGE = 1.65; // 初始值,後動態更新 float sensorRating = 50.0; float ratio = 1.000; float global_current = 0.0; float global_voltage = 0.0; float global_power = 0.0; const float NOISE_THRESHOLD = 0.4; // <0.4A 視為)0 int voltageIndex = 1; float voltages[] = {110.0, 208.0, 220.0}; #define STATUS_BLOCK_WIDTH 60 #define STATUS_BLOCK_HEIGHT 20 #define WIFI_BLOCK_X 0 #define WIFI_BLOCK_Y 0 #define MQTT_BLOCK_X (STATUS_BLOCK_WIDTH + 5) #define MQTT_BLOCK_Y 0 #define VOLTAGE_BLOCK_X MQTT_BLOCK_X + (STATUS_BLOCK_WIDTH + 5) #define VOLTAGE_BLOCK_Y 0 #define BLINK_INTERVAL 500 #define MQTT_UPLOAD_INTERVAL 20000 //20秒上傳一次 #define MSG_TIMEOUT 5000 // 底部訊息顯示時間 #define WIFI_ICON_X (tft.width() - 32) #define WIFI_ICON_Y 0 #define WIFI_ICON_WIDTH 32 #define WIFI_ICON_HEIGHT 20 #define TFT_GRAY tft.color565(128, 128, 128) #define TFT_SKYBLUE tft.color565(0, 191, 255) #define STATUS_BAR_HEIGHT 24 #define ERROR_BAR_HEIGHT 20 #define DATA_AREA_TOP STATUS_BAR_HEIGHT #define DATA_AREA_BOTTOM (tft.height() - ERROR_BAR_HEIGHT) // 135 - 20 = 115 #define EEPROM_SIZE 14 #define EEPROM_ADDR 0 #define RATIO_EEPROM_ADDR (EEPROM_ADDR + 5) // 在 equipmentId 後儲存 ratio #define RATIO_MIN 0.5 #define RATIO_MAX 1.5 #define RATIO_STEP 0.005 // 增大步長 Button2 button1(35); // GPIO 35 Button2 button2(0); // GPIO 0 volatile bool wifiConnected = false; volatile bool mqttConnected = false; TaskHandle_t mqttTaskHandle = NULL; TaskHandle_t wifiTaskHandle = NULL; TaskHandle_t currentTaskHandle = NULL; SemaphoreHandle_t wifiMutex = NULL; SemaphoreHandle_t serialMutex = NULL; SemaphoreHandle_t dataMutex = NULL; unsigned long lastFastUpdate = 0; unsigned long lastSlowUpdate = 0; unsigned long lastMsgTime = 0; void mySerialPrintln(const String &message) { if (xSemaphoreTake(serialMutex, portMAX_DELAY) == pdTRUE) { Serial.println(message); xSemaphoreGive(serialMutex); } } void mySerialPrint(const String &message) { if (xSemaphoreTake(serialMutex, portMAX_DELAY) == pdTRUE) { Serial.print(message); xSemaphoreGive(serialMutex); } } void displayData() { static float lastCurrent = -1; static float lastPower = -1; if (xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE) { // 計算每個區域的高度 int halfHeight = (DATA_AREA_BOTTOM - DATA_AREA_TOP - 2) / 2; // 45 // 設定文字置中並調大字體 tft.setTextSize(3); tft.setTextDatum(MC_DATUM); // Middle Center 置中對齊 // 上方:電流 (Current) if (global_current != lastCurrent) { tft.fillRect(0, DATA_AREA_TOP, tft.width(), halfHeight, TFT_BLACK); // Y=24 to 69 tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.drawString(String(global_current, 2) + " A", tft.width() / 2, DATA_AREA_TOP + halfHeight / 2); // X=120, Y=47 lastCurrent = global_current; } // 下方:功率 (Power) if (global_power != lastPower) { tft.fillRect(0, DATA_AREA_TOP + halfHeight, tft.width(), halfHeight, TFT_BLACK); // Y=69 to 114 tft.setTextColor(TFT_YELLOW, TFT_BLACK); if (global_power >= 1000) { float powerKW = global_power / 1000.0; tft.drawString(String(powerKW, 3) + " kW", tft.width() / 2, DATA_AREA_TOP + halfHeight + halfHeight / 2); // X=120, Y=92 } else { tft.drawString(String(global_power, 2) + " W", tft.width() / 2, DATA_AREA_TOP + halfHeight + halfHeight / 2); // X=120, Y=92 } lastPower = global_power; } // 恢復預設文字大小和對齊方式 tft.setTextSize(2); tft.setTextDatum(TL_DATUM); xSemaphoreGive(dataMutex); } } void displayError(String message, uint16_t color = TFT_RED) { tft.fillRect(0, DATA_AREA_BOTTOM + 1, tft.width(), ERROR_BAR_HEIGHT - 2, TFT_BLACK); tft.setTextDatum(MC_DATUM); tft.setTextColor(color, TFT_BLACK); tft.drawString(message, tft.width() / 2, DATA_AREA_BOTTOM + ERROR_BAR_HEIGHT / 2 + 1); tft.setTextDatum(TL_DATUM); } void displayMsg(String message, uint16_t color = TFT_SKYBLUE) { displayError(message, color); // 直接顯示,無時間限制 lastMsgTime = millis(); // 更新最後顯示時間 } void loadEEPROM() { // 載入 equipmentId String storedId = ""; for (int i = 0; i < 5; i++) { char c = EEPROM.read(EEPROM_ADDR + i); if (c == 0 || c == 255) break; storedId += c; } if (storedId.length() == 4 && storedId.startsWith("Eq")) { storedId.toCharArray(equipmentId, 6); } else { strcpy(equipmentId, "Eq01"); saveEquipmentId(); } // 載入 ratio float storedRatio; EEPROM.get(RATIO_EEPROM_ADDR, storedRatio); if (storedRatio >= RATIO_MIN && storedRatio <= RATIO_MAX) { ratio = storedRatio; } else { ratio = 1.0; saveRatio(); } } void saveRatio() { EEPROM.put(RATIO_EEPROM_ADDR, ratio); EEPROM.commit(); } void saveEquipmentId() { for (int i = 0; i < 5; i++) { if (i < strlen(equipmentId)) { EEPROM.write(EEPROM_ADDR + i, equipmentId[i]); } else { EEPROM.write(EEPROM_ADDR + i, 0); } } EEPROM.commit(); } void setEquipmentIdFromSerial() { if (Serial.available() > 0) { String input = Serial.readStringUntil('\n'); input.trim(); if (input.length() == 4 && input.startsWith("Eq") && isDigit(input[2]) && isDigit(input[3])) { int num = input.substring(2).toInt(); if (num >= 1 && num <= 20) { input.toCharArray(equipmentId, 6); saveEquipmentId(); mySerialPrintln("Equipment ID set to: " + String(equipmentId)); updateMqttTopicAndClientId(); } else { mySerialPrintln("Invalid number! Please enter Eq01 to Eq20."); } } else { mySerialPrintln("Invalid format! Please enter in format 'EqXX' (e.g., Eq01 to Eq20)."); } } } void updateMqttTopicAndClientId() { // 從 PIN32 讀取模擬值並用作隨機數種子 int analogValue = analogRead(RANDOM_SEED_PIN); // 讀取模擬值 srand(analogValue); int randomSuffix = rand() % 1000000; // 生成0到999999的隨機數 snprintf(clientId, sizeof(clientId), "ESP32_SCT013_%s_%06d", equipmentId, randomSuffix); // 格式化字串 snprintf(mqttTopic, sizeof(mqttTopic), "%s%s", topicBase, equipmentId); mySerialPrintln("Client ID updated to: " + String(clientId)); mySerialPrintln("MQTT Topic updated to: " + String(mqttTopic)); } void connectToWiFiTask(void *pvParameters) { int failedAttempts = 0; while (true) { if (xSemaphoreTake(wifiMutex, portMAX_DELAY) == pdTRUE) { if (WiFi.status() != WL_CONNECTED) { wifiConnected = false; mqttConnected = false; mySerialPrintln("Wi-Fi not connected, attempting to reconnect..."); WiFi.disconnect(); vTaskDelay(1000 / portTICK_PERIOD_MS); mySerialPrintln("Connecting to SSID: " + String(ssid)); WiFi.begin(ssid, password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 20) { vTaskDelay(500 / portTICK_PERIOD_MS); mySerialPrint("."); attempts++; } mySerialPrintln(""); if (WiFi.status() == WL_CONNECTED) { wifiConnected = true; mySerialPrintln("Wi-Fi Connected"); mySerialPrint("IP Address: "); mySerialPrintln(WiFi.localIP().toString()); failedAttempts = 0; } else { mySerialPrint("Wi-Fi Connection Failed: "); mySerialPrint("Status Code: "); mySerialPrintln(String(WiFi.status())); wifiConnected = false; mqttConnected = false; WiFi.disconnect(); failedAttempts++; } } else { wifiConnected = true; } xSemaphoreGive(wifiMutex); } vTaskDelay(10000 / portTICK_PERIOD_MS); } } void connectToMqtt() { mqttConnected = false; client.disconnect(); client.setServer(mqttServer, mqttPort); client.setSocketTimeout(10); if (client.connect(clientId, mqttUser, mqttPassword)) { mqttConnected = true; mySerialPrintln("Connected to MQTT broker"); } else { mqttConnected = false; mySerialPrintln("MQTT Connect failed: " + String(client.state())); } } void mqttPublishTask(void *pvParameters) { char payload[128]; while (true) { if (xSemaphoreTake(wifiMutex, portMAX_DELAY) == pdTRUE) { if (WiFi.status() == WL_CONNECTED) { if (!client.connected()) { connectToMqtt(); } if (client.connected()) { if (xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE) { snprintf(payload, sizeof(payload), "{\"voltage\":%.2f,\"current\":%.2f,\"power\":%.2f}", global_voltage, global_current, global_power); if (client.publish(mqttTopic, payload)) { mqttConnected = true; displayMsg("Data upload OK!"); mySerialPrintln("Data published to MQTT: " + String(payload)); } else { mqttConnected = false; displayError("Data upload Fail!"); mySerialPrintln("Publish failed"); client.disconnect(); } xSemaphoreGive(dataMutex); } } } else { wifiConnected = false; mqttConnected = false; client.disconnect(); } xSemaphoreGive(wifiMutex); } vTaskDelay(MQTT_UPLOAD_INTERVAL / portTICK_PERIOD_MS); } } void measureCurrentTask(void *pvParameters) { static bool firstRun = true; static unsigned long lastBiasUpdate = 0; vTaskDelay(3000 / portTICK_PERIOD_MS); // 延遲啟動 // 啟動時測量初始偏置電壓 if (firstRun) { float sum = 0; for (int i = 0; i < 5000; i++) { // 增加樣本數到 5000 sum += analogRead(ADC_PIN) * ADC_SCALE; delayMicroseconds(SAMPLE_INTERVAL_US); } BIAS_VOLTAGE = sum / 5000.0; mySerialPrint("Initial Bias Voltage: "); mySerialPrintln(String(BIAS_VOLTAGE, 3)); firstRun = false; } while (true) { // 動態更新偏置電壓(每 60 秒檢查無負載,或每小時強制更新) if ((millis() - lastBiasUpdate > 60000 && global_current < 0.1) || (millis() - lastBiasUpdate > 3600000)) { float sum = 0; for (int i = 0; i < 5000; i++) { sum += analogRead(ADC_PIN) * ADC_SCALE; delayMicroseconds(SAMPLE_INTERVAL_US); } BIAS_VOLTAGE = sum / 5000.0; mySerialPrint("Updated Bias Voltage: "); mySerialPrintln(String(BIAS_VOLTAGE, 3)); lastBiasUpdate = millis(); } // RMS 計算 float sum_sq = 0; for (int i = 0; i < SAMPLES; i++) { int adc_raw = analogRead(ADC_PIN); float voltage_adc = adc_raw * ADC_SCALE; float voltage_ac = voltage_adc - BIAS_VOLTAGE; sum_sq += voltage_ac * voltage_ac; delayMicroseconds(SAMPLE_INTERVAL_US); } float voltage_rms = sqrt(sum_sq / SAMPLES); float rawCurrent = voltage_rms * sensorRating; if (xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE) { global_current = rawCurrent * ratio; if (global_current < NOISE_THRESHOLD) global_current = 0.0; if (voltageIndex == 1) { // 208V 對應三相 global_power = global_voltage * global_current * sqrt(3.0); } else{ global_power = global_voltage * global_current ; } xSemaphoreGive(dataMutex); } // Serial.printf("%lf A\n", global_current); vTaskDelay(10 / portTICK_PERIOD_MS); // 每 190ms 更新一次 } } void changeRatio(float value){ ratio += value; if (ratio > RATIO_MAX) ratio = RATIO_MAX; if (ratio < RATIO_MIN) ratio = RATIO_MIN; mySerialPrint("Ratio: "); mySerialPrintln(String(ratio,3)); saveRatio(); // 儲存到 EEPROM displayMsg("Ratio: " + String(ratio, 3)); } void setup() { Serial.begin(115200); while (!Serial); serialMutex = xSemaphoreCreateMutex(); if (serialMutex == NULL) { Serial.println("Failed to create serial mutex"); while (1); } wifiMutex = xSemaphoreCreateMutex(); if (wifiMutex == NULL) { mySerialPrintln("Failed to create WiFi mutex"); while (1); } dataMutex = xSemaphoreCreateMutex(); if (dataMutex == NULL) { mySerialPrintln("Failed to create data mutex"); while (1); } mySerialPrintln("System starting..."); mySerialPrintln("VERSION: " + String(VERSION)); EEPROM.begin(EEPROM_SIZE); loadEEPROM(); updateMqttTopicAndClientId(); pinMode(ADC_PIN, INPUT); // 配置 GPIO 36 為輸入 pinMode(RANDOM_SEED_PIN, INPUT); // 配置 GPIO 32 為輸入 global_voltage = voltages[voltageIndex]; // Button2 初始化 static unsigned long lastButton1PressTime = 0; static unsigned long lastButton2PressTime = 0; button1.setPressedHandler([](Button2& btn) { unsigned long currentTime = millis(); if (currentTime - lastButton1PressTime < 200) return; lastButton1PressTime = currentTime; if (button2.isPressed()) { voltageIndex = (voltageIndex + 1) % 3; if (xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE) { global_voltage = voltages[voltageIndex]; if (voltageIndex == 1) { global_power = global_voltage * global_current * sqrt(3.0); } else { global_power = global_voltage * global_current; } xSemaphoreGive(dataMutex); } } else { changeRatio(RATIO_STEP); // 使用 changeRatio 增加 ratio } displayData(); }); button2.setPressedHandler([](Button2& btn) { unsigned long currentTime = millis(); if (currentTime - lastButton2PressTime < 200) return; lastButton2PressTime = currentTime; if (button1.isPressed()) { voltageIndex = (voltageIndex + 1) % 3; if (xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE) { global_voltage = voltages[voltageIndex]; if (voltageIndex == 1) { global_power = global_voltage * global_current * sqrt(3.0); } else { global_power = global_voltage * global_current; } xSemaphoreGive(dataMutex); } } else { changeRatio(-RATIO_STEP); // 使用 changeRatio 減少 ratio } displayData(); // 更新顯示 }); tft.init(); tft.setRotation(3); tft.fillScreen(TFT_BLACK); pinMode(4, OUTPUT); digitalWrite(4, HIGH); tft.setTextSize(2); tft.setTextDatum(TL_DATUM); // 左上對齊 // 畫兩條白線 tft.drawFastHLine(0, STATUS_BAR_HEIGHT - 1, tft.width(), TFT_GRAY); // Y=23 tft.drawFastHLine(0, DATA_AREA_BOTTOM - 2, tft.width(), TFT_GRAY); // Y=113 // 初始顯示 updateStatusBlocks(); displayData(); displayMsg(VERSION); // 設定 MAC (開發階段使用) WiFi.mode(WIFI_STA); esp_wifi_set_mac(WIFI_IF_STA, newMACAddress); mySerialPrintln("MAC Address Changed to: 30:C6:F7:51:B3:F8"); if (xTaskCreatePinnedToCore(connectToWiFiTask, "WiFi Task", 8192, NULL, 2, &wifiTaskHandle, 1) != pdPASS) { mySerialPrintln("Failed to create WiFi Task"); while (1); } client.setServer(mqttServer, mqttPort); if (xTaskCreatePinnedToCore(mqttPublishTask, "MQTT Publish Task", 8192, NULL, 1, &mqttTaskHandle, 1) != pdPASS) { mySerialPrintln("Failed to create MQTT Task"); while (1); } if (xTaskCreatePinnedToCore(measureCurrentTask, "Current Task", 4096, NULL, 2, &currentTaskHandle, 0) != pdPASS) { mySerialPrintln("Failed to create Current Task"); while (1); } } void loop() { if (client.connected() && WiFi.status() == WL_CONNECTED) { client.loop(); } setEquipmentIdFromSerial(); button1.loop(); button2.loop(); // static unsigned long lastCheck = 0; // if (millis() - lastCheck > 10000) { // mySerialPrintln("wifiTaskHandle Remaining: " + String(uxTaskGetStackHighWaterMark(wifiTaskHandle)) + "/8192"); // mySerialPrintln("mqttTaskHandle Remaining: " + String(uxTaskGetStackHighWaterMark(mqttTaskHandle)) + "/8192"); // mySerialPrintln("currentTaskHandle Remaining: " + String(uxTaskGetStackHighWaterMark(currentTaskHandle))+ "/4096"); // lastCheck = millis(); // } unsigned long currentTime = millis(); // 清空底部訊息 if (lastMsgTime > 0 && (currentTime - lastMsgTime >= MSG_TIMEOUT)) { tft.fillRect(0, DATA_AREA_BOTTOM + 1, tft.width(), ERROR_BAR_HEIGHT - 2, TFT_BLACK); // 清空錯誤訊息區域 lastMsgTime = 0; } if (currentTime - lastFastUpdate >= BLINK_INTERVAL) { updateStatusBlocks(); updateWiFiSignalIcon(); lastFastUpdate = currentTime; } if (currentTime - lastSlowUpdate >= 333) { displayData(); lastSlowUpdate = currentTime; } } void updateWiFiSignalIcon() { static int lastSignalLevel = -1; int signalLevel = getWiFiSignalLevel(); if (signalLevel == lastSignalLevel) return; tft.fillRect(WIFI_ICON_X, WIFI_ICON_Y, WIFI_ICON_WIDTH + 1, WIFI_ICON_HEIGHT + 1, TFT_BLACK); if (signalLevel == 0) { for (int i = 0; i < 5; i++) { int barHeight = (i + 1) * 4; // 調整為 4 的倍數以適應 20px 高度 int barY = WIFI_ICON_Y + WIFI_ICON_HEIGHT - barHeight; int barWidth = 4; int barX = WIFI_ICON_X + i * 6; tft.fillRect(barX, barY, barWidth, barHeight, TFT_GRAY); } int thickness = 5; for (int i = 0; i < thickness; i++) { tft.drawLine(WIFI_ICON_X + i, WIFI_ICON_Y, WIFI_ICON_X + WIFI_ICON_WIDTH + i, WIFI_ICON_Y + WIFI_ICON_HEIGHT, TFT_RED); tft.drawLine(WIFI_ICON_X + WIFI_ICON_WIDTH - i, WIFI_ICON_Y, WIFI_ICON_X + i, WIFI_ICON_Y + WIFI_ICON_HEIGHT, TFT_RED); } } else { for (int i = 0; i < signalLevel; i++) { int barHeight = (i + 1) * 4; // 同樣調整為 4 的倍數 int barY = WIFI_ICON_Y + WIFI_ICON_HEIGHT - barHeight; int barWidth = 4; int barX = WIFI_ICON_X + i * 6; tft.fillRect(barX, barY, barWidth, barHeight, TFT_GREEN); } } lastSignalLevel = signalLevel; } int getWiFiSignalLevel() { int rssi = WiFi.RSSI(); if (rssi == 0) return 0; if (rssi > -50) return 5; if (rssi > -60) return 4; if (rssi > -70) return 3; if (rssi > -80) return 2; if (rssi > -90) return 1; return 0; } void updateStatusBlocks() { static int lastWifiStatus = -1; static bool lastMqttConnected = false; static int lastVoltageIndex = -1; // 新增:追蹤電壓變化 static bool firstRun = true; int currentWifiStatus = WiFi.status(); bool mqttStatusChanged = (mqttConnected != lastMqttConnected); bool voltageChanged = (voltageIndex != lastVoltageIndex); // 新增:檢查電壓是否改變 static bool blinkState = false; blinkState = !blinkState; tft.setTextSize(2); tft.setTextDatum(MC_DATUM); // WiFi 狀態 int WIFI_BLOCK_TEXT_X = WIFI_BLOCK_X + STATUS_BLOCK_WIDTH/2 + 2; // X=25 int WIFI_BLOCK_TEXT_Y = WIFI_BLOCK_Y + STATUS_BLOCK_HEIGHT/2 + 1; // Y=13 if (firstRun || currentWifiStatus != lastWifiStatus) { if (currentWifiStatus == WL_CONNECTED) { tft.fillRect(WIFI_BLOCK_X, WIFI_BLOCK_Y, STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT, TFT_GREEN); tft.setTextColor(TFT_BLACK, TFT_GREEN); if (currentWifiStatus != lastWifiStatus) mySerialPrintln("WiFi Status: Connected"); } else { tft.fillRect(WIFI_BLOCK_X, WIFI_BLOCK_Y, STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT, blinkState ? TFT_YELLOW : TFT_GRAY); tft.setTextColor(TFT_BLACK, blinkState ? TFT_YELLOW : TFT_GRAY); if (currentWifiStatus != lastWifiStatus) mySerialPrintln("WiFi Status: Connecting"); } tft.drawString("WIFI", WIFI_BLOCK_TEXT_X, WIFI_BLOCK_TEXT_Y); } else if (currentWifiStatus != WL_CONNECTED) { tft.fillRect(WIFI_BLOCK_X, WIFI_BLOCK_Y, STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT, blinkState ? TFT_YELLOW : TFT_GRAY); tft.setTextColor(TFT_BLACK, blinkState ? TFT_YELLOW : TFT_GRAY); tft.drawString("WIFI", WIFI_BLOCK_TEXT_X, WIFI_BLOCK_TEXT_Y); } // MQTT 狀態 int MQTT_BLOCK_TEXT_X = MQTT_BLOCK_X + STATUS_BLOCK_WIDTH/2 + 2; // X=77 int MQTT_BLOCK_TEXT_Y = MQTT_BLOCK_Y + STATUS_BLOCK_HEIGHT/2 + 1; // Y=13 if (firstRun || mqttStatusChanged) { if (mqttConnected) { tft.fillRect(MQTT_BLOCK_X, MQTT_BLOCK_Y, STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT, TFT_GREEN); tft.setTextColor(TFT_BLACK, TFT_GREEN); mySerialPrintln("MQTT Status: Connected"); } else { tft.fillRect(MQTT_BLOCK_X, MQTT_BLOCK_Y, STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT, blinkState ? TFT_YELLOW : TFT_GRAY); tft.setTextColor(TFT_BLACK, blinkState ? TFT_YELLOW : TFT_GRAY); mySerialPrintln("MQTT Status: Connecting"); } tft.drawString("MQTT", MQTT_BLOCK_TEXT_X, MQTT_BLOCK_TEXT_Y); } else if (!mqttConnected) { tft.fillRect(MQTT_BLOCK_X, MQTT_BLOCK_Y, STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT, blinkState ? TFT_YELLOW : TFT_GRAY); tft.setTextColor(TFT_BLACK, blinkState ? TFT_YELLOW : TFT_GRAY); tft.drawString("MQTT", MQTT_BLOCK_TEXT_X, MQTT_BLOCK_TEXT_Y); } // 新增:電壓狀態 (Voltage) int VOLTAGE_BLOCK_TEXT_X = VOLTAGE_BLOCK_X + STATUS_BLOCK_WIDTH/2 + 2; // X=127 int VOLTAGE_BLOCK_TEXT_Y = VOLTAGE_BLOCK_Y + STATUS_BLOCK_HEIGHT/2 + 1; // Y=13 if (firstRun || voltageChanged) { tft.fillRect(VOLTAGE_BLOCK_X, VOLTAGE_BLOCK_Y, STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT, TFT_GREEN); tft.setTextColor(TFT_RED, TFT_GREEN); tft.drawString(String(global_voltage, 0) + "V", VOLTAGE_BLOCK_TEXT_X, VOLTAGE_BLOCK_TEXT_Y); } lastWifiStatus = currentWifiStatus; lastMqttConnected = mqttConnected; lastVoltageIndex = voltageIndex; // 更新電壓狀態 firstRun = false; tft.setTextDatum(TL_DATUM); } ``` ### wifi_config.h ```cpp #ifndef WIFI_CONFIG_H #define WIFI_CONFIG_H const char* ssid = "***"; const char* password = "***"; const char* mqttServer = "***"; const int mqttPort = 1883; const char* mqttUser = "***"; const char* mqttPassword = "***"; #endif ```