#include <Arduino.h>

#define _ROUNDUP_  // show voltage rounded up
//#define _TIMEDSAMPLES_
//#define _DEBUG_

#include <ZMPT101B.h>
#include "TM1637_Micro.h"
#include "MeanFilter.h"

const float SENSITIVITY = /*385.75f;*/ 112.50f;  // wokwi
const float SOURCEFREQ = 50.0f;

const int16_t SAMPLESPERREAD = 20;
const int16_t MAINSVOLTAGE = 220;
const int16_t VOLTAGETOLERANCEPERCENT = 8;                                                // +/- voltage tolerance %
const int16_t LOWVOLTAGE = (MAINSVOLTAGE * (100 - VOLTAGETOLERANCEPERCENT) + 50) / 100;   // rounded calculated low voltage
const int16_t HIGHVOLTAGE = (MAINSVOLTAGE * (100 + VOLTAGETOLERANCEPERCENT) + 50) / 100;  // rounded calculated high voltage
const int16_t ZEROVOLTCORRECTION = 10;
const int16_t LEDHYSTERESISVOLTAGE = 3;
const int16_t MEANFILTERWINDOW = 3;

const uint8_t ZMPT101B_IN = A0;
const uint8_t GREENLED_OUT = 2;  // Green half LED
const uint8_t REDLED_OUT = 3;    // Red half LED
const uint8_t TM1637_CLK = 4;
const uint8_t TM1637_DIO = 5;
const uint8_t SHOWMEAN_IN = 6;

const uint8_t VOLTAGELEVELNULL = 0;
const uint8_t VOLTAGELEVELNORMAL = 1;
const uint8_t VOLTAGELEVELLOW = 2;
const uint8_t VOLTAGELEVELHIGH = 3;

#ifdef _TIMEDSAMPLES_
const uint32_t SAMPLEDELAY = 1000UL;
uint32_t voltageLastSampleTime;
#endif

float rmsVolts_f = 0.0;
int16_t rmsVolts = 0;
uint8_t prevVoltageLevel = VOLTAGELEVELNULL;
uint8_t voltageLevel;
bool showMeanVoltage = true;

// ZMPT101B sensor output connected to analog pin ZMPT101B_IN
// and the voltage source frequency is SOURCEFREQ Hz.
ZMPT101B voltageSensor(ZMPT101B_IN, SOURCEFREQ);
TM1637Micro display(TM1637_CLK, TM1637_DIO);
MeanFilter<float> meanFilter(MEANFILTERWINDOW);

/* ---------------------------------------------------------------------------------------------- */

void set_led_color() {
  if (rmsVolts > HIGHVOLTAGE - (voltageLevel == VOLTAGELEVELHIGH ? LEDHYSTERESISVOLTAGE : 0)) {
    voltageLevel = VOLTAGELEVELHIGH;
  } else if (rmsVolts < LOWVOLTAGE + (voltageLevel == VOLTAGELEVELLOW ? LEDHYSTERESISVOLTAGE : 0)) {
    voltageLevel = VOLTAGELEVELLOW;
  } else {
    voltageLevel = VOLTAGELEVELNORMAL;
  }
  if (voltageLevel != prevVoltageLevel) {
    digitalWrite(GREENLED_OUT, voltageLevel != VOLTAGELEVELHIGH);
    digitalWrite(REDLED_OUT, voltageLevel != VOLTAGELEVELNORMAL);
    prevVoltageLevel = voltageLevel;
  }
}

/* ---------------------------------------------------------------------------------------------- */

void show_voltage() {
  rmsVolts_f = voltageSensor.getRmsVoltage(SAMPLESPERREAD);
  if (showMeanVoltage) {
    rmsVolts_f = meanFilter.AddValue(rmsVolts_f);
  }
#ifdef _ROUNDUP_
  rmsVolts = int(rmsVolts_f + 0.5f);
#else
  rmsVolts = int(rmsVolts_f);
#endif
  if (rmsVolts < ZEROVOLTCORRECTION) rmsVolts = 0;
  display.showNum(rmsVolts);
#ifdef _DEBUG_
  Serial.print(rmsVolts);
  Serial.println(F(" Vrms"));
#endif
}

/* ---------------------------------------------------------------------------------------------- */

void blink_all(byte g = LOW, byte r = LOW, int num = 8888) {
  delay(100);
  digitalWrite(GREENLED_OUT, g);
  digitalWrite(REDLED_OUT, r);
  display.showNum(num);
  delay(400);
  digitalWrite(GREENLED_OUT, LOW);
  digitalWrite(REDLED_OUT, LOW);
  display.clear();
}
/* ---------------------------------------------------------------------------------------------- */

void test_all_leds() {
  blink_all(HIGH, LOW);
  blink_all(HIGH, HIGH);
  blink_all(LOW, HIGH);
  delay(1000);
}

/* ---------------------------------------------------------------------------------------------- */

void setup() {
  pinMode(GREENLED_OUT, OUTPUT);
  pinMode(REDLED_OUT, OUTPUT);
  pinMode(SHOWMEAN_IN, INPUT_PULLUP);
  digitalWrite(GREENLED_OUT, LOW);
  digitalWrite(REDLED_OUT, LOW);
  showMeanVoltage = digitalRead(SHOWMEAN_IN);
  display.init();
  display.brightness(/*DARKEST*/TYPICAL);
  display.clear();
  test_all_leds();
  voltageSensor.setSensitivity(SENSITIVITY);
#ifdef _DEBUG_
  Serial.begin(115200);
  Serial.println(HIGHVOLTAGE);
  Serial.println(LOWVOLTAGE);
#endif
#ifdef _TIMEDSAMPLES_
  // First voltage reading
  voltageLastSampleTime = millis();
  show_voltage();
  set_led_color();
#endif
}

/* ---------------------------------------------------------------------------------------------- */

void loop() {
#ifdef _TIMEDSAMPLES_
  if (millis() - voltageLastSampleTime >= SAMPLEDELAY) {
    voltageLastSampleTime += SAMPLEDELAY;
    show_voltage();
    set_led_color();
  }
#else
  show_voltage();
  set_led_color();
#endif
}

/* ---------------------------------------------------------------------------------------------- */
Signal GeneratorBreakout
4-Digit Display