โค้ดการเปลี่ยน Address ของ PZEM

/*
เครื่องมือ การตั้งค่า Address PZEM 
=============================
เครื่องมือนี้รวมฟังก์ชันการเปลี่ยน address และการอ่านข้อมูล

การต่อสายฮาร์ดแวร์:
- ESP32 GPIO16 -> PZEM TX
- ESP32 GPIO17 -> PZEM RX
- ESP32 GND -> PZEM GND
- PZEM L, N -> แหล่งจ่ายไฟ (ต้องต่อเพื่อเปลี่ยน address)

ฟีเจอร์:
1. อ่านข้อมูล PZEM อัตโนมัติ (แรงดัน, กระแส, กำลัง, พลังงาน, ความถี่, PF)
2. เปลี่ยน address PZEM โดยตรงผ่านคำสั่ง
3. ทดสอบการสื่อสารกับ PZEM

คำสั่ง:
- 'r' - เปลี่ยนเป็นโหมดอ่านข้อมูล (เริ่มต้น)
- 't' - ทดสอบการสื่อสาร PZEM
- '0xXX' - ตั้ง address PZEM โดยตรง (เช่น 0x05, 0x10, 0xFF)
- 'reset' - รีเซ็ตตัวนับพลังงาน (kWh) เป็นศูนย์

ตัวอย่างการใช้งาน:
1. เริ่มต้น: อ่านข้อมูล PZEM อัตโนมัติทุก 1 วินาที
2. เปลี่ยน address: พิมพ์ "0x05" เพื่อตั้ง address เป็น 0x05
3. ทดสอบ: พิมพ์ "t" เพื่อทดสอบการสื่อสาร
4. อ่านข้อมูล: พิมพ์ "r" เพื่อเปลี่ยนเป็นโหมดอ่านข้อมูล
5. รีเซ็ตพลังงาน: พิมพ์ "reset" เพื่อรีเซ็ตตัวนับ kWh เป็นศูนย์

ช่วง Address: 0x01 ถึง 0xF7 (0x00 และ 0xF8 เป็นที่สงวน)

*/

#include <PZEM004Tv30.h>

// ============================================================================
// การตั้งค่าฮาร์ดแวร์
// ============================================================================
// การต่อสายระหว่าง ESP32 กับ PZEM
#define PZEM_RX_PIN 16        // ESP32 GPIO16 -> PZEM TX
#define PZEM_TX_PIN 17        // ESP32 GPIO17 -> PZEM RX
#define PZEM_SERIAL Serial2   // ใช้ Serial2 สำหรับการสื่อสาร
PZEM004Tv30 pzem(PZEM_SERIAL, PZEM_RX_PIN, PZEM_TX_PIN);

// ============================================================================
// การตั้งค่าซอฟต์แวร์
// ============================================================================
#define MODE_READ_DATA 1      // โหมดสำหรับอ่านข้อมูล PZEM

int currentMode = MODE_READ_DATA; // เริ่มต้นด้วยโหมดอ่านข้อมูล

// ตัวแปรสำหรับจัดการข้อมูลจาก Serial
String inputString = "";      // บัฟเฟอร์สำหรับข้อมูลที่เข้ามาจาก Serial
bool stringComplete = false;  // ฟลากแสดงว่าคำสั่งครบถ้วนแล้ว

// ============================================================================
// ฟังก์ชัน SETUP
// ============================================================================
void setup() {
    Serial.begin(115200);
    
    // รอให้ Serial port เชื่อมต่อ
    while (!Serial) {
        delay(10);
    }
    
    // แสดงข้อความต้อนรับและคำสั่งที่ใช้ได้
    Serial.println("PZEM Complete Tool");
    Serial.println("=================");
    Serial.println();
    Serial.println("Commands:");
    Serial.println("  'r' - Read data mode (default)");
    Serial.println("  't' - Test communication");
    Serial.println("  '0xXX' - Set address directly (e.g., 0x05)");
    Serial.println("  'reset' - Reset energy counter (kWh) to zero");
    Serial.println();
    
    // ทดสอบการสื่อสารกับ PZEM ก่อน
    testCommunication();
}

// ============================================================================
// ลูปหลัก
// ============================================================================
void loop() {
    // ตรวจสอบคำสั่งที่เข้ามาจาก Serial
    if (Serial.available()) {
        char inChar = (char)Serial.read();
        
        // ตรวจจับจุดสิ้นสุดคำสั่ง (newline หรือ carriage return)
        if (inChar == '\n' || inChar == '\r') {
            stringComplete = true;
        } else {
            inputString += inChar;  // สร้างสตริงคำสั่ง
        }
    }
    
    // ประมวลผลคำสั่งที่ครบถ้วนแล้ว
    if (stringComplete) {
        inputString.trim();           // ลบช่องว่าง
        handleCommand(inputString);   // ดำเนินการคำสั่ง
        inputString = "";             // ล้างบัฟเฟอร์
        stringComplete = false;       // รีเซ็ตฟลาก
    }
    
    // ดำเนินการตามโหมด
    if (currentMode == MODE_READ_DATA) {
        readDataMode();  // อ่านข้อมูล PZEM อัตโนมัติ
    }
    
    delay(1000);  // รอ 1 วินาทีก่อนรอบถัดไป
}

// ============================================================================
// ตัวจัดการคำสั่ง
// ============================================================================
void handleCommand(String command) {
    command.toLowerCase();
    
    if (command == "r") {
        // เปลี่ยนเป็นโหมดอ่านข้อมูล
        currentMode = MODE_READ_DATA;
        Serial.println("Switched to Data Reading Mode");
    }
    else if (command == "t") {
        // ทดสอบการสื่อสาร PZEM
        testCommunication();
    }
    else if (command == "reset") {
        // รีเซ็ตตัวนับพลังงาน
        resetEnergyCounter();
    }
    else if (command.startsWith("0x")) {
        // แปลง address แบบ hex และตั้งค่าโดยตรง
        String hexStr = command.substring(2);  // ลบ "0x" ออก
        uint8_t newAddr = (uint8_t)strtol(hexStr.c_str(), NULL, 16);
        
        // ตรวจสอบช่วง address (0x01 ถึง 0xF7)
        if (newAddr >= 0x01 && newAddr <= 0xF7) {
            Serial.print("Setting address to 0x");
            Serial.print(newAddr, HEX);
            Serial.print("... ");
            
            // พยายามตั้ง address
            if (pzem.setAddress(newAddr)) {
                Serial.println("SUCCESS");
                delay(500);  // รอให้ address ถูกตั้งค่า
                uint8_t currentAddr = pzem.readAddress();
                Serial.print("Current address: 0x");
                Serial.println(currentAddr, HEX);
            } else {
                Serial.println("FAILED");
            }
        } else {
            Serial.println("Invalid address! Must be between 0x01 and 0xF7");
        }
    }
    else {
        // คำสั่งไม่รู้จัก - แสดงความช่วยเหลือ
        Serial.println("Unknown command. Available commands:");
        Serial.println("  'r' - Read data mode (default)");
        Serial.println("  't' - Test communication");
        Serial.println("  '0xXX' - Set address directly (e.g., 0x05)");
        Serial.println("  'reset' - Reset energy counter (kWh) to zero");
    }
}


// ============================================================================
// โหมดอ่านข้อมูล
// ============================================================================
void readDataMode() {
    // อ่าน address หลายครั้งเพื่อความเสถียร (PZEM บางครั้งคืนค่าที่ไม่สอดคล้องกัน)
    uint8_t address = 0;
    int validReads = 0;
    
    // ลองอ่าน address 5 ครั้งและใช้ผลที่พบบ่อยที่สุด
    uint8_t addresses[5] = {0};
    for(int i = 0; i < 5; i++) {
        addresses[i] = pzem.readAddress();
        delay(150);  // หน่วงเวลาเล็กน้อยระหว่างการอ่าน
    }
    
    // นับจำนวนครั้งของ address แต่ละค่า
    int count2 = 0, count0 = 0, countOther = 0;
    for(int i = 0; i < 5; i++) {
        if(addresses[i] == 2) count2++;        // address ที่กำหนดเองที่พบบ่อยที่สุด
        else if(addresses[i] == 0) count0++;   // address เริ่มต้น/ข้อผิดพลาด
        else countOther++;                     // address อื่นๆ
    }
    
    // ใช้ address ที่พบบ่อยที่สุดสำหรับการแสดงผล
    if(count2 >= count0 && count2 >= countOther) {
        address = 2;
        validReads = count2;
    } else if(count0 >= countOther) {
        address = 0;
        validReads = count0;
    } else {
        address = addresses[0];
        validReads = 1;
    }
    
    // แสดง address ปัจจุบันของ PZEM พร้อมตัวบ่งชี้ความเสถียร
    Serial.print("Custom Address: 0x");
    if(validReads > 0) {
        Serial.print(address, HEX);
        Serial.print(" (");
        Serial.print(validReads);
        Serial.print("/5 reads)");
        Serial.println();
    } else {
        Serial.println("ERROR (No valid address read)");
    }

    // อ่านข้อมูลเซ็นเซอร์ PZEM ทั้งหมด
    float voltage = pzem.voltage();      // แรงดันในหน่วยโวลต์
    float current = pzem.current();      // กระแสในหน่วยแอมแปร์
    float power = pzem.power();          // กำลังในหน่วยวัตต์
    float energy = pzem.energy();        // พลังงานในหน่วยกิโลวัตต์-ชั่วโมง
    float frequency = pzem.frequency();  // ความถี่ในหน่วยเฮิรตซ์
    float pf = pzem.pf();               // ตัวประกอบกำลัง

    // ตรวจสอบและแสดงข้อมูลเซ็นเซอร์
    if(isnan(voltage)){
        Serial.println("Error reading voltage");
    } else if (isnan(current)) {
        Serial.println("Error reading current");
    } else if (isnan(power)) {
        Serial.println("Error reading power");
    } else if (isnan(energy)) {
        Serial.println("Error reading energy");
    } else if (isnan(frequency)) {
        Serial.println("Error reading frequency");
    } else if (isnan(pf)) {
        Serial.println("Error reading power factor");
    } else {
        // แสดงการอ่านค่าเซ็นเซอร์ที่ถูกต้องทั้งหมด
        Serial.print("Voltage: ");      Serial.print(voltage);      Serial.println("V");
        Serial.print("Current: ");      Serial.print(current);      Serial.println("A");
        Serial.print("Power: ");        Serial.print(power);        Serial.println("W");
        Serial.print("Energy: ");       Serial.print(energy,3);     Serial.println("kWh");
        Serial.print("Frequency: ");    Serial.print(frequency, 1); Serial.println("Hz");
        Serial.print("PF: ");           Serial.println(pf);
    }
    Serial.println();  
}


// ============================================================================
// ฟังก์ชันทดสอบการสื่อสาร
// ============================================================================
void testCommunication() {
    Serial.println("Testing PZEM communication...");
    
    // ทดสอบการอ่านแรงดัน 
    float voltage = pzem.voltage();
    if (!isnan(voltage)) {
        Serial.print("Voltage: ");
        Serial.print(voltage);
        Serial.println("V");
    } else {
        Serial.println("Voltage reading failed");
    }
    
    // ทดสอบการอ่าน address
    uint8_t addr = pzem.readAddress();
    Serial.print("Address: 0x");
    Serial.println(addr, HEX);
    
    // ทดสอบการอ่านความถี่ 
    float frequency = pzem.frequency();
    if (!isnan(frequency)) {
        Serial.print("Frequency: ");
        Serial.print(frequency);
        Serial.println("Hz");
    } else {
        Serial.println("Frequency reading failed");
    }
    
    Serial.println();  // บรรทัดว่างเพื่อความอ่านง่าย
}

// ============================================================================
// ฟังก์ชันรีเซ็ตตัวนับพลังงาน
// ============================================================================
void resetEnergyCounter() {
    Serial.println("Resetting energy counter...");
    
    // อ่านค่าพลังงานปัจจุบันก่อนรีเซ็ต
    float currentEnergy = pzem.energy();
    if (!isnan(currentEnergy)) {
        Serial.print("Current energy: ");
        Serial.print(currentEnergy, 3);
        Serial.println(" kWh");
    }
    
    // พยายามรีเซ็ตตัวนับพลังงาน
    if (pzem.resetEnergy()) {
        Serial.println("Energy counter reset SUCCESS");
        
   
        delay(1000);
        
        // ตรวจสอบการรีเซ็ตโดยอ่านพลังงานอีกครั้ง
        float newEnergy = pzem.energy();
        if (!isnan(newEnergy)) {
            Serial.print("New energy value: ");
            Serial.print(newEnergy, 3);
            Serial.println(" kWh");
            
            if (newEnergy < 0.001) {  
                Serial.println("Energy counter successfully reset to zero");
            } else {
                Serial.println("Warning: Energy counter may not have reset completely");
            }
        }
    } else {
        Serial.println("Energy counter reset FAILED");
        Serial.println("Make sure PZEM is powered and connected properly");
    }
    
    Serial.println();
}