#include <Arduino.h>

#define _USEMEANFILTER_  // show mean voltage
#define _ROUNDUP_  // show voltage rounded up
//#define _TIMEDSAMPLES_

#include <ZMPT101B.h>
#include "TM1637_Micro.h"
#ifdef _USEMEANFILTER_
#include "MeanFilter.h"
#endif

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 LOWVOLTAGELEVEL = (MAINSVOLTAGE * (100 - VOLTAGETOLERANCEPERCENT) + 50) / 100;   // rounded calculated low voltage
const int16_t HIGHVOLTAGELEVEL = (MAINSVOLTAGE * (100 + VOLTAGETOLERANCEPERCENT) + 50) / 100;  // rounded calculated high voltage
const int16_t ZEROVOLTCORRECTION = 10;
const int16_t LEDHYSTERESISVOLTAGE = 3;
#ifdef _USEMEANFILTER_
const int16_t MEANFILTERWINDOW = 3;
#endif

const uint8_t ZMPT101B_IN = A1;
const uint8_t GREENLED_OUT = 4;  // Green half LED
const uint8_t REDLED_OUT = 3;    // Red half LED
const uint8_t TM1637_CLK = 0;
const uint8_t TM1637_DIO = 1;

const uint8_t VOLTAGENULL = 0;
const uint8_t VOLTAGENORMAL = 1;
const uint8_t VOLTAGELOW = 3;
const uint8_t VOLTAGEHIGH = 2;

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

float rmsVolts_f = 0.0;
int16_t rmsVolts = 0;
uint8_t prevVoltageLevel = VOLTAGENULL;
uint8_t voltageLevel;

// 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);
#ifdef _USEMEANFILTER_
MeanFilter<float> meanFilter(MEANFILTERWINDOW);
#endif

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

void set_led_color() {
  if (rmsVolts > HIGHVOLTAGELEVEL - (voltageLevel == VOLTAGEHIGH ? LEDHYSTERESISVOLTAGE : 0)) {
    voltageLevel = VOLTAGEHIGH;
  } else if (rmsVolts < LOWVOLTAGELEVEL + (voltageLevel == VOLTAGELOW ? LEDHYSTERESISVOLTAGE : 0)) {
    voltageLevel = VOLTAGELOW;
  } else {
    voltageLevel = VOLTAGENORMAL;
  }
  if (voltageLevel != prevVoltageLevel) {
    digitalWrite(GREENLED_OUT, bitRead(voltageLevel, 0));
    digitalWrite(REDLED_OUT, bitRead(voltageLevel, 1));
    prevVoltageLevel = voltageLevel;
  }
}

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

void show_voltage() {
  rmsVolts_f = voltageSensor.getRmsVoltage(SAMPLESPERREAD);
#ifdef _USEMEANFILTER_
  rmsVolts_f = meanFilter.AddValue(rmsVolts_f);
#endif
#ifdef _ROUNDUP_
  rmsVolts = int(rmsVolts_f + 0.5f);
#else
  rmsVolts = int(rmsVolts_f);
#endif
  if (rmsVolts < ZEROVOLTCORRECTION) rmsVolts = 0;
  display.showNum(rmsVolts);
}

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

void blink_all(byte led) {
  delay(100);
  digitalWrite(GREENLED_OUT, bitRead(led, 0));
  digitalWrite(REDLED_OUT, bitRead(led, 1));
  display.showNum(8888);
  delay(400);
  digitalWrite(GREENLED_OUT, LOW);
  digitalWrite(REDLED_OUT, LOW);
  display.clear();
}

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

void test_all_leds() {
  blink_all(1);
  blink_all(3);
  blink_all(2);
  delay(1000);
}

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

void setup() {
  pinMode(GREENLED_OUT, OUTPUT);
  pinMode(REDLED_OUT, OUTPUT);
  digitalWrite(GREENLED_OUT, LOW);
  digitalWrite(REDLED_OUT, LOW);
  display.init();
  display.brightness(/*DARKEST*/TYPICAL);
  display.clear();
  test_all_leds();
  voltageSensor.setSensitivity(SENSITIVITY);
#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
}

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