#include <HardwareSerial.h>
#include <LiquidCrystal_I2C.h>
#include <PZEM004Tv30.h>

#define CONSOLE_SERIAL    Serial   // Текстовий вивід
#define I2C_ADDR          0x27     // адреса дисплея
#define LCD_COLUMNS       20       // Кількість стовпців
#define LCD_LINES         4        // Кількість рядків
#define PZEM_SERIAL       Serial2  // Послідовний інтерфейс комунікації сенсору з контролером
#define PZEM_DEFAULT_ADDR 0xF8     // Aдреса сенсору за замовчуванням
#define PZEM_ADDR         0x55     // Адреса сенсору
#define PZEM_ADDR_INC     true     // Флаг інкременту адреси сенсору кожен цикл
#define PZEM_RX_PIN       16       // Пін RX фізичного послідовного інтерфейсу контролера 
#define PZEM_TX_PIN       17       // Пін TX фізичного послідовного інтерфейсу контролера

// Ініціалізуємо сенсор
// https://github.com/mandulaj/PZEM-004T-v30/blob/eec09ab4574db567306a3d335e5131c766e3333e/src/PZEM004Tv30.h#L82 
// Не оновлена документація - наявна зміна API, згідно якої ця функція тепер приймає посилання на послідовний порт замість вказівника
// Раніше: PZEM004Tv30(Serial2);
// Тепер: PZEM004Tv30(&Serial2);
PZEM004Tv30 pzem(&PZEM_SERIAL, PZEM_RX_PIN, PZEM_TX_PIN); 
// Ініціалізуємо дисплей
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES); 

void setup() {
  CONSOLE_SERIAL.begin(115200);

  lcd.init();
  lcd.backlight();
  // Щоб зкинути внутрішній лічильник енергії - розкоментувати рядок нижче
  // pzem.resetEnergy();
}

void loop() {
  static uint8_t addr = PZEM_ADDR;
  CONSOLE_SERIAL.print("Previous address: 0x");
  CONSOLE_SERIAL.println(pzem.readAddress(), HEX);
  CONSOLE_SERIAL.print("Setting address to 0x");
  CONSOLE_SERIAL.println(addr, HEX);

  if(!pzem.setAddress(addr)) {
    CONSOLE_SERIAL.println("ADDRESS set error!");
  } else {
    CONSOLE_SERIAL.print("Current address: 0x");
    CONSOLE_SERIAL.println(pzem.readAddress(), HEX);  
  }

  if(PZEM_ADDR_INC) {
    addr++;

    if (addr >= PZEM_DEFAULT_ADDR) {
      addr = 0x01;
    }
  }
  delay(5000);
  // lcd.setCursor(0, 0);
  // lcd.print("Custom Address: ");
  // lcd.setCursor(16, 0);
  // lcd.print(pzem.readAddress(), HEX);
  // delay(3500);
  // lcd.clear();

  // Отримуємо дані від сенсора
  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)){
    lcd.setCursor(0, 0);
    lcd.print("VOLTAGE read error!");
    delay(3500);
    lcd.clear();
  } else if (isnan(current)) {
    lcd.setCursor(0, 0);
    lcd.print("CURRENT read error!");
    delay(3500);
    lcd.clear();
  } else if (isnan(power)) {
    lcd.setCursor(0, 0);
    lcd.print("POWER read error!");
    delay(3500);
    lcd.clear();
  } else if (isnan(energy)) {
    lcd.setCursor(0, 0);
    lcd.print("ENERGY read error!");
    delay(3500);
    lcd.clear();
  } else if (isnan(frequency)) {
    lcd.setCursor(0, 0);
    lcd.print("FREQ read error!");
    delay(3500);
    lcd.clear();
  } else if (isnan(pf)) {
    lcd.setCursor(0, 0);
    lcd.print("PF read error!");
    delay(3500);
    lcd.clear();
  } else {
        // Всі дані валідні; послідовно виводимо на екран
        lcd.setCursor(0, 0);
        lcd.print("Voltage: ");
        lcd.write(voltage);
        lcd.print("V");
        lcd.setCursor(0, 1);
        lcd.print("Current: ");
        lcd.write(current);
        lcd.print("A");
        lcd.setCursor(0, 2);
        lcd.print("Power: ");
        lcd.write(power);
        lcd.print("W");
        lcd.setCursor(0, 3);
        lcd.print("Energy: ");
        lcd.write(energy);
        lcd.print("kWh");
        delay(3500);
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Frequency: ");
        lcd.write(frequency);
        lcd.print("Hz");
        lcd.setCursor(0, 1);
        lcd.print("PF: ");
        lcd.write(pf);
        delay(3500);
        lcd.clear();
    }
}
pzem-004tBreakout