#include <LiquidCrystal_I2C.h>
#include <ezButton.h>
#include <EEPROM.h>
#include <ZMPT101B.h>
#include <EmonLib.h>
#include <Keypad.h>
#define VT_PIN A0
#define CT_PIN A1
#define coinSigPin 2
#define coinSetPin 3
#define relayPin 4
#define buzzPin 5
#define pbSelectPin 6
#define pbLeftPin 7
#define pbRightPin 8
// EEPROM address
const int eeprom_addr_first = 0;
const int eeprom_addr_limit = 1;
const int eeprom_addr_credits = 2;
const int eeprom_addr_consumption = 4;
const int eeprom_addr_rate = 10;
// Buttons
ezButton selButton(pbSelectPin);
ezButton leftButton(pbLeftPin);
ezButton rightButton(pbRightPin);
ezButton buttons[3] = {selButton, leftButton, rightButton};
char keys[3] = { 'S', 'L', 'R' };
LiquidCrystal_I2C lcd(0x27, 20, 4);
ZMPT101B voltageSensor(VT_PIN, 60.0);
EnergyMonitor emon;
// States
bool powerState = false;
bool buzzerState = false;
int buzzerLoop = 0;
bool isOverload = false;
bool creditDisplayState = false;
unsigned long buzzerMillis = 0;
// Interactive
char currentKey;
String screens[2] = { "Dashboard", "Credit" };
int screenCursor = 0;
int dashboardCursor = 0;
// Sensor values
float voltage = 0.00;
float current = 0.00;
// VARIABLES
int credit = 0;
float rate = 0.10f;
int limit = 16;
bool isFirstLaunch = true;
unsigned long timePreviousMillis = 0;
unsigned int time = 0;
unsigned int tempTime = 0;
unsigned int timeLeft = 0;
double consumption = 0.0;
double tempConsumption = 0.0;
float ratedEnergyAvailable = 0.0;
int consumptionPercentage = 0;
String timeLeftString = "";
// Coin acceptor variables
int coinIter = 0;
int coinImpulseCount = 0;
// Coin acceptor Variables
unsigned long lastAction = 0; //When last action was made
unsigned long lastPulse = 0; //When last pulse was send
int pulseCount = 0; //How many pulses we got
int currentCredit = 0; //The current credit
bool coinEnabled = false;
const int pulseTimeout = 300;
const int actionTimeout = 5000;
unsigned long tempAction;
unsigned long tempPulse;
void setup() {
Serial.begin(115200);
Serial.println("[SYS] System started.");
pinMode(VT_PIN, INPUT);
pinMode(CT_PIN, INPUT);
pinMode(buzzPin, OUTPUT);
pinMode(relayPin, OUTPUT);
pinMode(2, INPUT_PULLUP);
pinMode(3, OUTPUT);
attachInterrupt(digitalPinToInterrupt(2), coinInterrupt, RISING);
voltageSensor.setSensitivity(500.0f);
emon.current(CT_PIN, 30);
lcd.init();
lcd.backlight();
showIdleScreen();
Serial.print("[EEPROM] Length: ");
Serial.println(EEPROM.length());
Serial.print("[EEPROM] First launch: ");
Serial.println(EEPROM.read(eeprom_addr_first));
if (EEPROM.read(eeprom_addr_first) == 255) {
Serial.println(F("[EEPROM] System first launch. Setting inital values."));
EEPROM.put(eeprom_addr_first, isFirstLaunch);
EEPROM.put(eeprom_addr_credits, credit);
EEPROM.put(eeprom_addr_rate, rate);
EEPROM.put(eeprom_addr_limit, limit);
Serial.println(F("[EEPROM] Inital values set. Proceeding..."));
delay(500);
}
Serial.println(F("[EEPROM] Loading values..."));
EEPROM.get(eeprom_addr_credits, credit);
EEPROM.get(eeprom_addr_rate, rate);
EEPROM.get(eeprom_addr_limit, limit);
EEPROM.get(eeprom_addr_consumption, time);
Serial.println("[EEPROM] credits: " + String(credit));
Serial.println("[EEPROM] rate: " + String(rate));
Serial.println("[EEPROM] limit: " + String(limit));
ratedEnergyAvailable += (double)(credit * rate);
digitalWrite(coinSetPin, LOW);
// Set initial power state.
if (credit > 0) powerState = true;
else powerState = false;
delay(1000);
tone(buzzPin, 1500);
delay(300);
noTone(buzzPin);
lcd.clear();
}
void loop() {
voltage = map(analogRead(A0), 0, 1023, 0, 250.0);
current = map(analogRead(A1), 0, 1023, 0, 30.0);
// double Vrms = voltageSensor.getRmsVoltage();
// double Irms = emon.calcIrms(1480);
double Vrms = voltage;
double Irms = current;
double Prms = Vrms * Irms;
digitalWrite(relayPin, powerState);
// Serial Commands
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
command.trim();
String commandName = command.substring(0, command.indexOf(' '));
String commandArg = command.substring(command.indexOf(' '), command.length());
commandArg.trim();
Serial.println("[CMD] " + commandName + " arg: " + commandArg);
if (commandName == "reset") {
Serial.println("[SYS] Resetting System...");
resetSystem();
}
else if (commandName == "addCredit") {
Serial.println(F("[SYS] Adding Credit..."));
int cmdCredit = commandArg.toInt();
if (cmdCredit > 0) {
addCredit(cmdCredit);
} else {
Serial.println(F("[SYS] ERROR: Invalid credit number."));
}
}
else if (commandName == "voidCredit") {
Serial.println("[SYS] Voiding Credit...");
voidCredit();
}
else if (commandName == "setLimit") {
Serial.println("[SYS] Setting current limit...");
int limit = commandArg.toInt();
if (limit > 0) {
setLimit(limit);
} else {
Serial.println(F("[SYS] ERROR: Invalid limit number."));
}
}
else if (commandName == "setRate") {
float cmdRate = commandArg.toFloat();
if (cmdRate > 0) {
setRate(cmdRate);
} else {
Serial.println(F("[SYS] ERROR: Invalid rate number."));
}
}
else {
Serial.println(F("[CMD] ERROR: Unrecognized command."));
}
}
// COIN ACCEPTOR
// Activates only when CREDITS screen is active and if credit is less than 100.
if (screenCursor == 1) {
tempAction = lastAction;
tempPulse = lastPulse;
digitalWrite(coinSetPin, coinEnabled);
if (credit < 99) {
if (!isOverload) coinEnabled = true;
else coinEnabled = false;
if (millis() - lastPulse >= pulseTimeout && pulseCount > 0) {
if (tempAction != lastAction || tempPulse != lastPulse) return;
Serial.print("Coin: ");
switch (pulseCount) {
case 20:
Serial.println("20 Pesos");
addCredit(20);
break;
case 10:
Serial.println("10 Pesos");
addCredit(10);
break;
case 5:
Serial.println("5 Pesos");
addCredit(5);
break;
case 1:
Serial.println("1 Peso");
addCredit(1);
break;
}
pulseCount = 0;
} else {
coinEnabled = false;
}
} else {
coinEnabled = false;
}
}
// BUTTONS PROCESSOR
for (int i = 0; i < 5; i++) {
buttons[i].loop();
if (buttons[i].isPressed() == 1) {
currentKey = keys[i];
if (!isOverload) {
processInput(keys[i]);
}
}
}
// Buzzer
if (buzzerState) tone(buzzPin, 1000);
else noTone(buzzPin);
if (current >= limit || isOverload) {
if (buzzerState == true) {
if ((millis() - buzzerMillis) >= 500) {
buzzerMillis = millis();
buzzerState = false;
}
}
else {
if ((millis() - buzzerMillis) >= 100) {
buzzerMillis = millis();
buzzerState = true;
}
}
} else if (credit <= 1) {
if (buzzerLoop < 6) {
if (buzzerState == true) {
if ((millis() - buzzerMillis) >= 1000) {
buzzerMillis = millis();
buzzerState = false;
buzzerLoop += 1;
}
}
else {
if ((millis() - buzzerMillis) >= 500) {
buzzerMillis = millis();
buzzerState = true;
buzzerLoop += 1;
}
}
} else if (buzzerLoop > 6) {
buzzerState = false;
}
}
// CURRENT LIMITER
if (current >= limit) {
powerState = false;
if (!isOverload) {
Serial.println("[SYS] OVERLOAD");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("!!!! OVERLOAD !!!"));
lcd.setCursor(0, 1);
lcd.print(F(" Reduce load to "));
lcd.setCursor(0, 2);
lcd.print(F(" return power "));
lcd.setCursor(0, 3);
lcd.print(F(" then press SEL."));
isOverload = true;
}
} else {
if (isOverload && currentKey == 'S') {
lcd.clear();
lcd.setCursor(0, 0 );
lcd.print("Load Normal");
lcd.setCursor(0, 1);
lcd.print(F("Power will return in "));
lcd.setCursor(0, 2);
lcd.print("a moment.");
delay(2000);
lcd.clear();
isOverload = false;
screenCursor = 0;
buzzerState = false;
} else if (!isOverload) {
if (credit > 0) powerState = true;
else powerState = false;
}
}
// Dashboard
if (!isOverload) {
if (screenCursor == 0) {
lcd.setCursor(0, 0);
lcd.print(" DASHBOARD ");
switch (currentKey) {
case 'S':
if (dashboardCursor < 1) {
dashboardCursor++;
}
else {
dashboardCursor--;
}
break;
}
switch (dashboardCursor) {
case 0:
lcd.setCursor(0, 1);
lcd.print("Cred:");
lcd.setCursor(6, 1);
if (credit < 10) lcd.print(" " + String(credit));
else lcd.print(credit);
lcd.setCursor(9, 1);
lcd.print("(");
lcd.print(ratedEnergyAvailable);
lcd.print(" kWh)");
lcd.setCursor(0, 2);
lcd.print("Cons: ");
lcd.print(consumption);
lcd.print(" kWh ");
if (consumptionPercentage < 100) {
lcd.setCursor(17, 2);
} else {
lcd.setCursor(16, 2);
}
if (consumptionPercentage < 10) {
lcd.print("0");
}
lcd.print(consumptionPercentage);
lcd.print("%");
lcd.setCursor(0, 3);
lcd.print("Left: ");
lcd.print(timeLeftString);
break;
case 1:
lcd.setCursor(0, 1);
lcd.print("Current: ");
lcd.setCursor(9, 1);
lcd.print(Irms);
lcd.setCursor(18, 1);
lcd.print("A");
lcd.setCursor(0, 2);
lcd.print("Voltage: ");
lcd.setCursor(9, 2);
lcd.print(Vrms);
lcd.setCursor(18, 2);
lcd.print("V");
lcd.setCursor(0, 3);
lcd.print("Power : ");
lcd.print(Prms);
lcd.setCursor(18, 3);
lcd.print("W");
break;
}
} else if (screenCursor == 1) {
lcd.setCursor(0, 0);
lcd.print(" CREDIT");
lcd.setCursor(0, 1);
lcd.print("Cred: ");
lcd.setCursor(8, 1);
lcd.print(credit);
lcd.print(" (");
lcd.print(credit * rate);
lcd.print("kWh)");
lcd.setCursor(0, 2);
lcd.print("Rate: ");
lcd.print(rate);
lcd.print("/kWh");
lcd.setCursor(0, 3);
lcd.print(">> Insert Coin <<"); // beep when inserted // insert coin change
}
}
if (!isOverload && credit > 0) {
if ((millis() - timePreviousMillis) >= 1000) {
timePreviousMillis = millis();
if (screenCursor == 0 && dashboardCursor == 0) {
lcd.setCursor(6, 3);
lcd.print(" ");
}
double power = (double)(voltage * current);
double hours = (double)(time / 3600.0);
consumption = (double)((double)(power * hours) / 1000.0);
tempConsumption = (double)((double)(power * (double)(tempTime / 3600.0)) / 1000.0);
timeLeft = (unsigned int)((((double)(ratedEnergyAvailable - consumption)) / (power / 1000)) * 3600);
consumptionPercentage = (consumption / ratedEnergyAvailable) * 100;
timeLeftString = getTimeFromSeconds(timeLeft);
Serial.println(consumption);
Serial.println(ratedEnergyAvailable);
Serial.println(timeLeftString);
EEPROM.put(eeprom_addr_consumption, time);
time += 1;
tempTime += 1;
if (tempConsumption >= rate) {
credit -= 1;
tempTime = 0;
if (credit == 0) {
timeLeft = 0;
timeLeftString = "";
}
}
}
} else if (!isOverload && credit == 0) {
lcd.setCursor(16, 2);
lcd.print(" ");
consumption = 0;
consumptionPercentage = 0;
timeLeftString = "";
time = 0;
timeLeft = 0;
ratedEnergyAvailable = (double)(credit * rate);
}
currentKey = 0x00; // Resets current key, for debounce.
delay(10); // Comment for actual
}
void showIdleScreen() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Digital ");
lcd.setCursor(0, 1);
lcd.print(" Submetering");
lcd.setCursor(0, 3);
lcd.print(" Loading...");
}
void processInput(char key) {
switch (key) {
case 'L':
if (screenCursor == 0) {
screenCursor = 1;
} else {
screenCursor--;
}
break;
case 'R':
if (screenCursor == 1) {
screenCursor = 0;
} else {
screenCursor++;
}
break;
}
switch (screenCursor) {
case 0:
lcd.clear();
break;
case 1:
lcd.clear();
break;
}
}
void addCredit(int value) {
buzzerLoop = 0;
buzzerState = 0;
credit += value;
ratedEnergyAvailable += (double)(value * rate);
EEPROM.put(eeprom_addr_credits, credit);
Serial.println("[SYS] Credit now: " + String(credit));
}
void reduceCredit(int value) {
credit -= value;
EEPROM.put(eeprom_addr_credits, credit);
Serial.println("[SYS] Credit now: " + String(credit));
}
void voidCredit() {
Serial.println("[SYS] Voiding credit...");
credit = 0;
EEPROM.put(eeprom_addr_credits, credit);
Serial.println("[SYS] Credit now: " + String(credit));
}
void setRate(float newRate) {
Serial.println("[SYS] Current rate: " + String(rate));
Serial.println("[SYS] Setting rate...");
rate = newRate;
EEPROM.put(eeprom_addr_rate, newRate);
ratedEnergyAvailable = (double)(credit * rate);
Serial.println("[SYS] Rate was set to " + String(newRate));
}
void setLimit(int newLimit) {
Serial.println("[SYS] Current Limit: " + String(limit) + "A");
Serial.println("[SYS] Setting Limit...");
limit = newLimit;
EEPROM.put(eeprom_addr_limit, newLimit);
Serial.println("[SYS] Limit was set to " + String(newLimit) + "A");
}
void resetSystem() {
Serial.println(F("[EEPROM] Resetting memory..."));
for (int i = 0 ; i < EEPROM.length() ; i++) {
EEPROM.write(i, 255);
}
Serial.println(F("[EEPROM] Memory Cleared."));
screenCursor = 99;
lcd.clear();
lcd.setCursor(4, 0);
lcd.print("SYSTEM RESET");
lcd.setCursor(6, 1);
lcd.print("SUCCCESS");
lcd.setCursor(0, 2);
lcd.print("Please press RESET");
lcd.setCursor(0, 3);
lcd.print("button.");
}
void coinInterrupt() {
lastAction = millis();
lastPulse = millis();
pulseCount++;
Serial.println("Pulse: " + String(pulseCount));
}
String getTimeFromSeconds(int seconds) {
unsigned int sec = seconds;
unsigned int mins = seconds / 60;
unsigned int hour = seconds / 3600;
char buffer[40];
sprintf(buffer, "%dh %dm %ds", hour, int(mins % 60), int(sec % 60));
String timeString = buffer;
return timeString;
}