Giám sát điện trong nhà bằng ESP32 và nhiều PZEM-004T: từng nhánh, quá tải, quên tắt, công suất nền
Dùng ESP32 đọc nhiều PZEM-004T chung một đường Modbus để giám sát điện từng nhánh: cảnh báo quá tải, điện áp thấp, bình nóng lạnh quên tắt, điện năng từng ngày, công suất nền.
Hóa đơn tiền điện chỉ cho biết cả tháng dùng bao nhiêu. Muốn biết thiết bị nào ngốn điện, lúc nào nhà gần quá tải, điện áp có bị sụt giờ cao điểm hay ai đó quên tắt bình nóng lạnh, cần đo theo từng nhánh và theo thời gian thực. Bài này làm một bộ giám sát điện trong nhà bằng ESP32 và nhiều module PZEM-004T v3 nối chung một đường dây TTL: mỗi PZEM đo một nhánh (tổng, điều hòa, bình nóng lạnh), ESP32 ghi điện năng từng ngày, đo công suất nền ban đêm, cảnh báo và hiện tất cả trên một trang web.
Bài này nối tiếp bài PZEM-004T (đọc một module qua Modbus) và bài đo điện năng, tính tiền điện bậc thang trong cùng loạt. Ở đây trọng tâm là nhiều nhánh và cảnh báo, không tính tiền.
1. An toàn trước tiên
PZEM-004T có phần nối trực tiếp với điện 220 V. Phần trong tủ điện (đấu dây đo áp, luồn vòng CT) phải cắt CB tổng trước khi làm và nên nhờ thợ điện. PZEM và ESP32 đặt trong hộp nhựa cách điện, không chạm vào khi đang có điện. Phía TTL của PZEM cách ly với 220 V bằng opto bên trong module, nên ESP32 và dây tín hiệu an toàn khi module được lắp đúng.
2. Đặt PZEM theo từng nhánh

- Nhánh tổng (sau CB tổng): biết cả nhà đang dùng bao nhiêu, điện áp lưới, dùng để cảnh báo quá tải.
- Nhánh riêng cho thiết bị công suất lớn hoặc hay bị quên: điều hòa, bình nóng lạnh, bếp từ, máy bơm.
- Phần còn lại (tổng trừ các nhánh riêng) là đèn, tủ lạnh, tivi, ổ cắm. Chương trình dùng phần này để đo công suất nền ban đêm.
- Vòng CT của PZEM-004T 100 A là loại kẹp, chỉ luồn một dây pha (L) qua vòng. Luồn cả L và N thì dòng hai chiều triệt tiêu, số đo gần bằng 0.
3. Nối nhiều PZEM vào một ESP32

Modbus RTU cho phép nhiều thiết bị dùng chung một đường truyền: ESP32 gửi câu hỏi kèm địa chỉ, chỉ thiết bị có đúng địa chỉ đó trả lời. Vì vậy TX của mọi PZEM nối chung vào GPIO16, RX nối chung vào GPIO17. Hai, ba module mắc song song thường chạy ổn; nhiều module hơn hoặc dây dài thì nên đo lại dạng tín hiệu, hoặc chuyển sang mạch RS-485.
4. Đặt địa chỉ cho từng PZEM
PZEM mới xuất xưởng đều có địa chỉ 1. Trước khi nối chung, đặt địa chỉ cho từng module:
- Nạp chương trình, chỉ nối một PZEM vào ESP32 (phía 220 V cần có điện để PZEM chạy).
- Mở Serial Monitor tốc độ 115200, chọn kiểu xuống dòng Newline, gõ
A 2rồi Enter. Serial báoDa dat dia chi PZEM = 2. - Làm lần lượt với module thứ ba (
A 3). Module đo nhánh tổng giữ địa chỉ 1.

Lệnh đặt địa chỉ gửi tới địa chỉ chung 0xF8: mọi PZEM trên đường dây đều nhận và làm theo, nên chỉ được gõ khi đang nối đúng một module.
5. Chuẩn bị
- ESP32 DevKit V1, dây USB, nguồn 5 V cho ESP32
- Hai đến bốn module PZEM-004T v3 (loại 100 A có vòng CT kẹp)
- Dây cắm và breadboard cho phía TTL; hộp nhựa cách điện để lắp
6. Chương trình
Sửa bảng NHANH[] theo nhà mình: địa chỉ, tên (không dấu), ngưỡng quá tải, thời gian chạy liên tục tối đa. Ngưỡng quá tải nhánh tổng đặt thấp hơn dòng CB tổng: CB 32 A ở 220 V tương ứng khoảng 7 kW, đặt cảnh báo khoảng 6 kW là vừa.
// PYWORLD.VN - Giám sát điện trong nhà bằng ESP32 và nhiều PZEM-004T v3 chung một đường TTL: từng nhánh, cảnh báo, trang web
// PYWORLD.VN - PZEM TX vào GPIO16 (RX2), RX vào GPIO17 (TX2), 5V và GND chung; đặt địa chỉ từng PZEM bằng lệnh "A n" khi chỉ nối một module
// PYWORLD.VN - Cảnh báo quá tải, điện áp ngoài ngưỡng, thiết bị chạy quá lâu; điện năng từng ngày, công suất nền ban đêm
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <time.h>
#include <stdarg.h>
const char *TEN_WIFI = "TEN_WIFI";
const char *MAT_KHAU_WIFI = "MAT_KHAU_WIFI";
const int CHAN_RX2 = 16;
const int CHAN_TX2 = 17;
const float AP_THAP = 200, AP_CAO = 240; // ngưỡng cảnh báo điện áp ở nhánh tổng
const float NGUONG_CHAY_W = 30; // trên mức này coi như thiết bị trong nhánh đang chạy
const uint32_t CHU_KY_DOC_MS = 2000;
const int SO_SU_KIEN = 20;
// Các struct khai báo trước mọi hàm.
struct Nhanh { uint8_t diaChi; const char *ten; float congSuatMax; uint16_t chayToiDaPhut; };
struct SoDo { float dienAp, dong, congSuat, tanSo, heSoCongSuat; uint32_t dienNangWh; };
struct TrangThai { SoDo s; bool doc, coDauNgay, baoQuaTai, baoChayLau; uint32_t whDauNgay; time_t chayTu; };
struct SuKien { time_t luc; char noiDung[96]; };
// Mỗi nhánh một PZEM, địa chỉ khác nhau. chayToiDaPhut = 0: không cảnh báo chạy lâu.
const Nhanh NHANH[] = {
{1, "Tong", 6000, 0},
{2, "Dieu hoa", 1600, 0},
{3, "Binh nong lanh", 3000, 90},
};
const int SO_NHANH = sizeof(NHANH) / sizeof(NHANH[0]);
HardwareSerial pzem(2);
WebServer may(80);
Preferences boNho;
TrangThai tt[SO_NHANH];
SuKien suKien[SO_SU_KIEN];
int soSuKien = 0, viTriSuKien = -1;
int ngayDaChot = -1; // năm * 1000 + thứ tự ngày trong năm
bool baoAp = false;
float nenDangDo = NAN, congSuatNen = NAN;
// ---- Modbus RTU tới PZEM (như bài PZEM-004T) ----
uint16_t crcModbus(const uint8_t *du, size_t n) {
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < n; i++) {
crc ^= du[i];
for (int b = 0; b < 8; b++) crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
}
return crc;
}
bool traoDoi(uint8_t *gui, size_t n, uint8_t *nhan, size_t canNhan) {
uint16_t crc = crcModbus(gui, n);
gui[n] = crc & 0xFF;
gui[n + 1] = crc >> 8;
while (pzem.available()) pzem.read();
pzem.write(gui, n + 2);
size_t da = 0;
uint32_t batDau = millis();
while (da < canNhan && millis() - batDau < 200) {
if (pzem.available()) nhan[da++] = pzem.read();
}
if (da < canNhan) return false;
return crcModbus(nhan, canNhan - 2) == (uint16_t)(nhan[canNhan - 2] | (nhan[canNhan - 1] << 8));
}
uint16_t tu(const uint8_t *p) { return (p[0] << 8) | p[1]; }
uint32_t tu32(const uint8_t *p) { return tu(p) | ((uint32_t)tu(p + 2) << 16); }
bool docPzem(uint8_t diaChi, SoDo &s) {
uint8_t gui[8] = {diaChi, 0x04, 0x00, 0x00, 0x00, 0x0A};
uint8_t nhan[25];
if (!traoDoi(gui, 6, nhan, 25) || nhan[0] != diaChi || nhan[1] != 0x04 || nhan[2] != 20) return false;
const uint8_t *d = nhan + 3;
s.dienAp = tu(d) * 0.1f;
s.dong = tu32(d + 2) * 0.001f;
s.congSuat = tu32(d + 6) * 0.1f;
s.dienNangWh = tu32(d + 10);
s.tanSo = tu(d + 14) * 0.1f;
s.heSoCongSuat = tu(d + 16) * 0.01f;
return true;
}
// Ghi thanh ghi 0x0002 (địa chỉ Modbus) của module duy nhất đang nối, gửi tới địa chỉ chung 0xF8.
bool datDiaChi(uint8_t moi) {
uint8_t gui[8] = {0xF8, 0x06, 0x00, 0x02, 0x00, moi};
uint8_t nhan[8];
return traoDoi(gui, 6, nhan, 8) && nhan[1] == 0x06 && nhan[5] == moi;
}
// ---- Sự kiện ----
void ghiSuKien(const char *dang, ...) {
viTriSuKien = (viTriSuKien + 1) % SO_SU_KIEN;
if (soSuKien < SO_SU_KIEN) soSuKien++;
SuKien &e = suKien[viTriSuKien];
e.luc = time(nullptr);
va_list ds;
va_start(ds, dang);
vsnprintf(e.noiDung, sizeof(e.noiDung), dang, ds);
va_end(ds);
struct tm t;
localtime_r(&e.luc, &t);
Serial.printf("[%02d:%02d] %s\n", t.tm_hour, t.tm_min, e.noiDung);
}
// ---- Điện năng theo ngày ----
// Số đầu ngày của nhánh i: khởi động lại trong ngày thì lấy số đã lưu, không thì lấy số hiện tại của PZEM.
void layDauNgay(int i, bool homNayDaLuu) {
char khoa[8];
snprintf(khoa, sizeof(khoa), "wh%d", i);
if (homNayDaLuu && boNho.isKey(khoa)) {
tt[i].whDauNgay = boNho.getUInt(khoa, tt[i].s.dienNangWh);
} else {
tt[i].whDauNgay = tt[i].s.dienNangWh;
boNho.putUInt(khoa, tt[i].whDauNgay);
}
tt[i].coDauNgay = true;
}
void chotNgay(const struct tm &t) {
static bool homNayDaLuu = false;
int ngay = (t.tm_year + 1900) * 1000 + t.tm_yday;
if (ngay != ngayDaChot) {
for (int i = 0; i < SO_NHANH; i++) {
if (ngayDaChot >= 0 && tt[i].coDauNgay) ghiSuKien("%s hom qua: %.2f kWh", NHANH[i].ten, (tt[i].s.dienNangWh - tt[i].whDauNgay) / 1000.0f);
tt[i].coDauNgay = false;
}
homNayDaLuu = boNho.getInt("ngay", -1) == ngay;
boNho.putInt("ngay", ngay);
ngayDaChot = ngay;
}
for (int i = 0; i < SO_NHANH; i++) {
if (tt[i].doc && !tt[i].coDauNgay) layDauNgay(i, homNayDaLuu);
}
}
// Công suất nền: nhỏ nhất của phần còn lại (tổng trừ các nhánh riêng) từ 1 giờ tới 5 giờ sáng,
// lúc thiết bị dùng điện thường xuyên đã tắt; phần còn lại là những thứ cắm điện suốt ngày đêm.
void theoDoiNen(const struct tm &t, float w) {
if (t.tm_hour >= 1 && t.tm_hour < 5) {
nenDangDo = isnan(nenDangDo) ? w : min(nenDangDo, w);
} else if (!isnan(nenDangDo)) {
congSuatNen = nenDangDo;
nenDangDo = NAN;
ghiSuKien("Cong suat nen dem qua %.0f W (ngoai cac nhanh rieng), khoang %.0f kWh/thang", congSuatNen, congSuatNen * 24 * 30 / 1000);
}
}
void kiemTra(int i, time_t bay) {
TrangThai &a = tt[i];
const Nhanh &n = NHANH[i];
if (a.s.congSuat > n.congSuatMax && !a.baoQuaTai) {
a.baoQuaTai = true;
ghiSuKien("QUA TAI %s: %.0f W (nguong %.0f W)", n.ten, a.s.congSuat, n.congSuatMax);
} else if (a.s.congSuat < 0.9f * n.congSuatMax) {
a.baoQuaTai = false;
}
if (a.s.congSuat > NGUONG_CHAY_W) {
if (!a.chayTu) a.chayTu = bay;
if (n.chayToiDaPhut && !a.baoChayLau && bay - a.chayTu >= (time_t)n.chayToiDaPhut * 60) {
a.baoChayLau = true;
ghiSuKien("%s chay lien tuc %u phut: co the quen tat", n.ten, (unsigned)n.chayToiDaPhut);
}
} else {
a.chayTu = 0;
a.baoChayLau = false;
}
}
void docTatCa() {
time_t bay = time(nullptr);
struct tm t;
bool coGio = getLocalTime(&t, 0);
for (int i = 0; i < SO_NHANH; i++) {
SoDo s;
bool doc = docPzem(NHANH[i].diaChi, s);
if (!doc && tt[i].doc) ghiSuKien("Mat ket noi PZEM dia chi %u (%s)", NHANH[i].diaChi, NHANH[i].ten);
if (doc && !tt[i].doc) ghiSuKien("Doc duoc PZEM dia chi %u (%s)", NHANH[i].diaChi, NHANH[i].ten);
tt[i].doc = doc;
if (!doc) continue;
if (s.dienNangWh < tt[i].s.dienNangWh) tt[i].whDauNgay = s.dienNangWh; // bộ đếm của PZEM vừa bị xóa
tt[i].s = s;
kiemTra(i, bay);
}
if (!tt[0].doc) return;
float v = tt[0].s.dienAp;
if (!baoAp && (v < AP_THAP || v > AP_CAO)) {
baoAp = true;
ghiSuKien("Dien ap bat thuong: %.1f V", v);
} else if (baoAp && v > AP_THAP + 2 && v < AP_CAO - 2) {
baoAp = false;
ghiSuKien("Dien ap tro lai binh thuong: %.1f V", v);
}
if (coGio) {
chotNgay(t);
float conLai = tt[0].s.congSuat;
for (int i = 1; i < SO_NHANH; i++) {
if (tt[i].doc) conLai -= tt[i].s.congSuat;
}
theoDoiNen(t, max(0.0f, conLai));
}
}
// ---- Trang web ----
const char TRANG_WEB[] = R"rawliteral(<!doctype html><html lang="vi"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Giám sát điện</title>
<style>body{font-family:sans-serif;margin:14px;background:#f4f6f9;color:#1d2b3a}table{border-collapse:collapse;width:100%;background:#fff}
td,th{padding:8px;border-bottom:1px solid #e3e8ef;text-align:right}td:first-child,th:first-child{text-align:left}
.w{font-weight:700;font-size:18px}.loi{color:#c92a2a}ul{background:#fff;padding:10px 26px;border-radius:8px}</style></head><body>
<h2>Giám sát điện trong nhà</h2><p id="gio"></p>
<table><thead><tr><th>Nhánh</th><th>V</th><th>A</th><th>W</th><th>PF</th><th>kWh hôm nay</th><th>Đang chạy</th></tr></thead><tbody id="b"></tbody></table>
<p id="nen"></p><h3>Sự kiện gần đây</h3><ul id="sk"></ul>
<script>
function tai(){fetch('/du-lieu').then(function(r){return r.json();}).then(function(j){
document.getElementById('gio').textContent='Cập nhật lúc '+j.gio;var h='';
j.nhanh.forEach(function(n){h+=n.ok?'<tr><td>'+n.ten+'</td><td>'+n.v.toFixed(1)+'</td><td>'+n.a.toFixed(2)+'</td><td class="w">'+n.w.toFixed(0)+
'</td><td>'+n.pf.toFixed(2)+'</td><td>'+n.kwh.toFixed(2)+'</td><td>'+(n.chay>0?n.chay+' phút':'')+'</td></tr>':
'<tr><td>'+n.ten+'</td><td class="loi" colspan="6">không đọc được PZEM</td></tr>';});
document.getElementById('b').innerHTML=h;
document.getElementById('nen').textContent=j.nen===null?'Công suất nền: đo từ 1 giờ tới 5 giờ sáng':'Công suất nền đêm qua: '+j.nen.toFixed(0)+' W';
document.getElementById('sk').innerHTML=j.sk.map(function(e){return '<li>'+e[0]+': '+e[1]+'</li>';}).join('');});}
tai();setInterval(tai,3000);
</script></body></html>)rawliteral";
void trangChu() {
may.send(200, "text/html; charset=utf-8", TRANG_WEB);
}
void duLieu() {
time_t bay = time(nullptr);
char dem[48];
struct tm t;
localtime_r(&bay, &t);
strftime(dem, sizeof(dem), "%H:%M:%S %d/%m/%Y", &t);
String s;
s.reserve(3000);
s += "{\"gio\":\"";
s += dem;
s += "\",\"nen\":";
s += isnan(congSuatNen) ? String("null") : String(congSuatNen, 0);
s += ",\"nhanh\":[";
for (int i = 0; i < SO_NHANH; i++) {
const TrangThai &a = tt[i];
snprintf(dem, sizeof(dem), "%s{\"ten\":\"", i ? "," : "");
s += dem;
s += NHANH[i].ten;
s += "\",\"ok\":";
s += a.doc ? "true" : "false";
if (a.doc) {
char so[160];
snprintf(so, sizeof(so), ",\"v\":%.1f,\"a\":%.3f,\"w\":%.1f,\"pf\":%.2f,\"kwh\":%.3f,\"chay\":%ld",
a.s.dienAp, a.s.dong, a.s.congSuat, a.s.heSoCongSuat, a.coDauNgay ? (a.s.dienNangWh - a.whDauNgay) / 1000.0f : 0.0f,
(i > 0 && a.chayTu) ? (long)((bay - a.chayTu) / 60) : 0L); // nhánh tổng luôn có tải nên không đếm
s += so;
}
s += "}";
}
s += "],\"sk\":[";
for (int k = 0; k < soSuKien; k++) { // mới nhất trước
const SuKien &e = suKien[(viTriSuKien - k + SO_SU_KIEN) % SO_SU_KIEN];
localtime_r(&e.luc, &t);
strftime(dem, sizeof(dem), "%H:%M %d/%m", &t);
s += k ? ",[\"" : "[\"";
s += dem;
s += "\",\"";
s += e.noiDung;
s += "\"]";
}
s += "]}";
may.send(200, "application/json", s);
}
void lenhSerial() {
if (!Serial.available()) return;
String lenh = Serial.readStringUntil('\n');
lenh.trim();
int so;
if (sscanf(lenh.c_str(), "A %d", &so) == 1 && so >= 1 && so <= 247) {
Serial.printf(datDiaChi(so) ? "Da dat dia chi PZEM = %d\n" : "Khong dat duoc dia chi %d (chi noi mot PZEM)\n", so);
} else if (lenh.length()) {
Serial.println("Lenh: A n dat dia chi n (1-247) cho PZEM dang noi mot minh");
}
}
void setup() {
Serial.begin(115200);
pzem.begin(9600, SERIAL_8N1, CHAN_RX2, CHAN_TX2);
boNho.begin("diennha", false);
WiFi.mode(WIFI_STA);
WiFi.begin(TEN_WIFI, MAT_KHAU_WIFI);
configTzTime("ICT-7", "pool.ntp.org", "time.google.com");
for (int i = 0; i < 30 && WiFi.status() != WL_CONNECTED; i++) delay(500);
if (WiFi.status() == WL_CONNECTED) Serial.printf("Mo trinh duyet: http://%s\n", WiFi.localIP().toString().c_str());
else Serial.println("Chua ket noi Wi-Fi: van doc PZEM va in canh bao ra Serial");
for (int i = 0; i < SO_NHANH; i++) tt[i].doc = true; // để lần đọc đầu báo nhánh nào không đọc được
may.on("/", trangChu);
may.on("/du-lieu", duLieu);
may.begin();
Serial.printf("Giam sat %d nhanh. Go \"A n\" de dat dia chi cho PZEM moi\n", SO_NHANH);
}
void loop() {
may.handleClient();
lenhSerial();
static uint32_t lanDoc = 0;
if (millis() - lanDoc >= CHU_KY_DOC_MS) {
lanDoc = millis();
docTatCa();
}
}
Giải thích
- Đọc từng nhánh: mỗi 2 giây,
docTatCa()hỏi lần lượt các địa chỉ trongNHANH[]bằng hàm Modbus 0x04 (10 thanh ghi: điện áp, dòng, công suất, điện năng, tần số, hệ số công suất). Phần Modbus giống bài PZEM-004T, chỉ thêm tham số địa chỉ. - Quá tải: công suất vượt ngưỡng thì ghi một sự kiện; phải xuống dưới 90 % ngưỡng mới coi là hết quá tải, tránh báo liên tục khi công suất dao động quanh ngưỡng.
- Chạy quá lâu: nhánh có công suất trên 30 W liên tục quá
chayToiDaPhutthì báo "có thể quên tắt". Bình nóng lạnh thường chỉ cần 20–40 phút để nóng nước. - Điện áp (đo ở nhánh tổng): dưới 200 V hoặc trên 240 V thì báo; phải trở về trong khoảng 202–238 V mới báo bình thường.
- Điện năng từng ngày: bộ đếm của PZEM tăng mãi; đầu mỗi ngày chương trình lưu số đếm vào Preferences, điện năng hôm nay là số hiện tại trừ số đầu ngày. Khởi động lại giữa ngày thì đọc lại số đã lưu. Sang ngày mới thì ghi sự kiện điện năng hôm qua của từng nhánh.
- Công suất nền: từ 1 giờ tới 5 giờ sáng, lấy giá trị nhỏ nhất của phần còn lại (tổng trừ các nhánh riêng). Đó là những thiết bị cắm điện suốt ngày đêm: modem, camera, đồng hồ của lò vi sóng, sạc để quên trên ổ cắm, tủ lạnh lúc nghỉ.
- Giờ lấy từ Internet theo múi giờ Việt Nam; 20 sự kiện gần nhất giữ trong RAM để trang web hiện ra.
7. Kết quả
Hình dưới là một ngày dùng điện theo mô hình: sáng đun bình nóng lạnh, trưa bật điều hòa, tối nấu ăn. Ngày này bình nóng lạnh bật lúc 19 giờ rồi bị quên tới 21 giờ 10, đúng lúc bếp từ và ấm siêu tốc cùng chạy.



8. Đọc số liệu để tiết kiệm điện
- Công suất nền nhỏ nhưng chạy 24 giờ: 46 W là khoảng 33 kWh (33 số điện) mỗi tháng. Rút sạc, tắt hẳn thiết bị chờ là cách giảm dễ nhất.
- Điện năng từng nhánh mỗi ngày cho biết thiết bị nào chiếm phần lớn hóa đơn. Mùa nóng thường là điều hòa; mùa lạnh ở miền Bắc là bình nóng lạnh.
- Điện áp sụt giờ cao điểm: dưới khoảng 200 V thì điều hòa, tủ lạnh khó khởi động, bóng đèn tối. Ghi lại thời điểm, điện áp để báo cho điện lực nếu tình trạng lặp lại nhiều ngày.
- Hệ số công suất (PF) thấp ở nhánh có động cơ, đèn LED giá rẻ là bình thường; hóa đơn hộ gia đình tính theo kWh, không tính phần công suất phản kháng.
9. Lỗi hay gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| "Mat ket noi PZEM dia chi 2" | Chưa đặt địa chỉ, hai module trùng địa chỉ, phía 220 V của module đó mất điện | Nối riêng từng module, gõ lại A n; PZEM chỉ trả lời khi phía 220 V có điện |
| "Khong dat duoc dia chi" | Đang nối nhiều module, sai dây TX/RX | Chỉ nối một module; đổi chéo TX, RX |
| Dòng của nhánh gần bằng 0 dù tải đang chạy | Luồn cả dây pha và dây trung tính qua CT | Chỉ luồn dây pha |
| Tổng nhỏ hơn tổng các nhánh riêng | CT tổng đặt sai chỗ, không bao hết các nhánh | CT tổng phải ở sau CB tổng, trước khi chia nhánh |
| Điện năng hôm nay bị nhảy về 0 | Bộ đếm của PZEM vừa bị xóa | Chương trình tự lấy lại số đầu ngày; tránh xóa bộ đếm giữa ngày |
| Giờ trên trang web sai | Chưa kết nối Internet nên chưa lấy được giờ | Kiểm tra Wi-Fi; điện năng theo ngày chỉ bắt đầu khi có giờ |
10. Xem thêm
- Modbus RTU (phần 1): khung tin, mã hàm, CRC và ESP32 làm thiết bị chủ
- UART là gì: khung truyền, tốc độ baud và cách dùng Serial2 trên ESP32
- Điện một chiều, xoay chiều và an toàn khi làm việc gần điện 220 V
- Bài PZEM-004T với ESP32 trong loạt mô-đun và bài đo điện năng, tính tiền điện bậc thang trong loạt năng lượng.
Xem thêm