# ESP32_PZEM004T (改DC供電)
| ESP32_PZEM004T |
|:---------------------------------------------------:|
||
## 簡介
這是一個測量電流的專案,主要功能如下
* 測量 1~100A 的交流電流
* 透過 Wifi 與 MQTT 定時上傳
* 使用 TTGO T-Display,TFT 螢幕即時顯示
* 透過開發板上的兩顆按鈕,做簡易的測量校正
* 本程式由 grok3 協助撰寫
## 材料與電路
* TTGO T-Display (esp32)
* PZEM-004T 與 CT
* SMA01L-12 (1W DC-DC Unregulated Single Output Converter)
* 1K 電阻 * 1
* 接線如下圖
| 接線圖 |
|:---------------------------------------------------------------------------------------------------------------:|
||
| 訂正:圖中 26 與 RX 之間不需要電阻,否則通訊不穩定) |
| 圖中 27 與 TX 之間的電阻,是用比較簡單的方式配合 ESP32 3.3V 的電平 |
| 為什麼用5V而不是3.3V?這樣就不需要那顆電阻。因為稍後要用 5V 轉 12V 的模組,我一時買不到 3.3V 轉 12V 的模組。|
| PZEM-004T |
|:---------------------------------------------------:|
|  |
| PZEM-004T 原理圖 (我只找到這板,與實體略有不同) |
|:---------------------------------------------------:|
|  |
## 我的特殊需求
* PZEM-004T 預設使用 AC 供電。但在我的需求中,==不允許用 AC 供電==,所以必須把它改成 DC 供電。ESP32 使用電池供電。
* 由原理圖與實際測量得知,AC 電源經過 12V 穩壓二極體,在經過 7133 LDO,轉換成 3.3V 供晶片使用。
* 我將使用一個 DC-DC Converter (5V轉12V),直接飛線到 LDO 的 VIN,提供 DC 12V 的電源
| DC DC Converter |
|:---------------------------------------------------:|
|  |
| 魔改版 PZEM-004T |
|:---------------------------------------------------:|
||
||
||
## 程式碼
:::spoiler TTGO_PZEM004T.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
// PZEM004Tv30 1.2.1 /home/chihhaolai/Arduino/libraries/PZEM004Tv30-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 <TFT_eSPI.h>
#include <PubSubClient.h>
#include <EEPROM.h>
#include <Button2.h>
#include "wifi_config.h" // 引用外部 WiFi 配置文件
#include <PZEM004Tv30.h>
#define ENABLE_WIFI true
#define PZEM_RX_PIN 26
#define PZEM_TX_PIN 25
#define PZEM_SERIAL Serial2
PZEM004Tv30 pzem(PZEM_SERIAL, PZEM_RX_PIN, PZEM_TX_PIN);
#define ADC_PIN 36
#define RANDOM_SEED_PIN 32
TFT_eSPI tft = TFT_eSPI();
const char* VERSION = "2025-03-16a";
const char* topicBase = "ESP32_PZEM/";
char mqttTopic[30];
char equipmentId[6] = "Eq01";
char clientId[30] = "ESP32_PZEM";
unsigned long lastMsgDisplay = 0; // 記錄最後一次成功顯示的時間
WiFiClient espClient;
PubSubClient client(espClient);
uint8_t newMACAddress[] = {0x30, 0xC6, 0xF7, 0x51, 0xB3, 0xF8};
float ratio = 1.000;
float global_current = 0.0;
float global_voltage = 0.0;
float global_power = 0.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_PZEM_%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) {
while (true) {
float rawCurrent = pzem.current();
if (xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE) {
global_current = rawCurrent * ratio;
if (voltageIndex == 1) { // 208V 對應三相
global_power = global_voltage * global_current * sqrt(3.0);
}
else{
global_power = global_voltage * global_current ;
}
xSemaphoreGive(dataMutex);
}
// Serial.printf("%f A\n", rawCurrent);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
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);
if(ENABLE_WIFI){
WiFi.mode(WIFI_STA);
esp_wifi_set_mac(WIFI_IF_STA, newMACAddress); // 設定 MAC (開發階段使用)
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, ¤tTaskHandle, 1) != 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);
}
```
:::
:::spoiler 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
```
:::