#include <LiquidCrystal_I2C.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <array>
#include <numeric>
#define LCD_SCL_PIN 25 // 27
#define LCD_SDA_PIN 33 // 14
#define BUTTON_PIN 22
#define WAKE_PIN BUTTON_PIN
#define WIFI_LED_PIN 27
#define API_LED_PIN 14
#define COUNT_LED_PIN 12
#define RGB_LED_RED_PIN 21
#define RGB_LED_GREEN_PIN 19
#define RGB_LED_BLUE_PIN 18
// WiFi credentials
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// API credentials
const char* api_user = "[email protected]";
const char* api_pass = "XXXX";
String api_host = "https://powertrainindustries.net";
String api_auth_url = api_host + "/api/auth/login";
String api_update_url = api_host + "/api/auth/login";
String api_auth_key = "991d4860883cbdec8effc3f3a3b71bce";
// String api_key = "";
// Your API endpoint
const char* api_url = "https://postman-echo.com/post";
// If your module is 0x3F, change 0x27 to 0x3F
LiquidCrystal_I2C lcd(0x27, 20, 4);
int timeoutSecs = 10;
int countdownValue = 0;
unsigned long lastTick = 0;
bool countdownRunning = false;
// Track blinking state
bool rgbBlinking = false;
unsigned long rgbLastToggle = 0;
int rgbBlinkInterval = 500; // ms
bool rgbOnState = false;
int redLevel = 0;
int greenLevel = 0;
int blueLevel = 0;
std::array<int, 3> ledPins = {COUNT_LED_PIN, WIFI_LED_PIN, API_LED_PIN};
std::array<int, 3> ledBlinkStates = {0, 0, 0};
unsigned long ledBlinkLastToggle = 0;
int ledBlinkInterval = 500; // ms
bool ledBlinkOnState = false;
void setup()
{
int wakeup_cause = esp_sleep_get_wakeup_cause();
Serial.begin(115200);
Serial.println("Hello, ESP32!");
setPinModes();
// init LCD
Wire.begin(LCD_SDA_PIN, LCD_SCL_PIN);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("ESP32 LCD2004 READY");
lcdInitProgressBar(lcd);
// sleep_enable
esp_sleep_enable_ext0_wakeup((gpio_num_t) WAKE_PIN, 0); // wake when pin == LOW
// do we have a wakeup_cause?
if (wakeup_cause)
{
// updateState
updateState(1);
}
}
void loop()
{
ledBlinkUpdate();
if (digitalRead(BUTTON_PIN) == LOW)
{
if (!countdownRunning)
{
updateState(1);
}
start_countdown(timeoutSecs);
}
update_countdown();
delay(100); // this speeds up the simulation
}
void setPinModes()
{
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(WAKE_PIN, INPUT_PULLUP);
pinMode(RGB_LED_RED_PIN, OUTPUT);
pinMode(RGB_LED_GREEN_PIN, OUTPUT);
pinMode(RGB_LED_BLUE_PIN, OUTPUT);
pinMode(WIFI_LED_PIN, OUTPUT);
pinMode(API_LED_PIN, OUTPUT);
pinMode(COUNT_LED_PIN, OUTPUT);
}
void setLedPinState(int ledPin, bool ledPinState)
{
// set the pin to ledPinState
digitalWrite(ledPin, ledPinState);
}
void setLedBlinkState(int ledPin, bool ledBlinkState)
{
// loop thru ledPins
for (size_t i = 0; i < ledPins.size(); i++)
{
// is i the ledPin?
if (ledPins[i] == ledPin)
{
// update state
ledBlinkStates[i] = ledBlinkState;
}
}
}
void ledBlinkUpdate()
{
bool isBlinking = std::accumulate(ledBlinkStates.begin(), ledBlinkStates.end(), 0) > 0;
// are we NOT blinking?
if (!isBlinking)
{
return;
}
// should we NOT toggle?
if (millis() - ledBlinkLastToggle < (unsigned long) ledBlinkInterval)
{
return;
}
// reset ledBlinkLastToggle
ledBlinkLastToggle = millis();
// flip ledBlinkOnState
ledBlinkOnState = !ledBlinkOnState;
// loop thru ledPins
for (size_t i = 0; i < ledPins.size(); i++)
{
// is i state on?
if (ledBlinkStates[i])
{
setLedPinState(ledPins[i], ledBlinkOnState);
}
}
}
void start_countdown(int seconds)
{
setLedBlinkState(COUNT_LED_PIN, true);
//digitalWrite(COUNT_LED_PIN, HIGH);
//rgbSetColor(255, 0, 0);
//rgbBlinkOn();
Serial.println("start_countdown");
countdownValue = seconds;
countdownRunning = true;
lastTick = millis();
lcd.setCursor(0, 2);
lcd.print(" ");
}
void update_countdown()
{
if (millis() - lastTick >= 1000)
{
if (countdownRunning)
{
// percent left (1.0->0.0)
float percent = (float) countdownValue / (float) timeoutSecs;
Serial.println(percent);
lcdDrawProgressBar(lcd, 0, 3, 20, percent);
}
lcd.setCursor(0, 1);
lcd.print("Time left: ");
lcd.setCursor(11, 1);
lcd.print(countdownValue);
lastTick = millis();
countdownValue--;
if (countdownValue < 0)
{
if (countdownRunning)
{
setLedBlinkState(COUNT_LED_PIN, false);
setLedPinState(COUNT_LED_PIN, LOW);
//digitalWrite(COUNT_LED_PIN, LOW);
countdownRunning = false;
lcd.setCursor(0, 1);
lcd.print("Countdown done! ");
updateState(0);
}
// is countdownValue < - timout?
if (countdownValue < -1 * timeoutSecs)
{
// esp_deep_sleep_start
// esp_deep_sleep_start();
}
}
}
}
void updateState(const bool state)
{
rgbSetColor(0, 0, 255);
rgbBlinkOn();
turnWiFiOn();
if (WiFi.status() == WL_CONNECTED)
{
digitalWrite(API_LED_PIN, HIGH);
lcd.setCursor(0, 2);
lcd.print("updateState(");
lcd.print(state);
lcd.print(") ");
HTTPClient http;
// Add Basic Auth header
// http.setAuthorization(api_user, api_pass);
// http.begin(api_auth_url);
// http.addHeader("X-API-KEY", api_key);
// String _payload = "";
// int httpCode = http.POST(_payload); // send POST
// String _response = http.getString();
// Serial.println(api_auth_url);
// Serial.println("response");
// Serial.println(_response);
// return;
http.begin(api_url);
http.addHeader("Content-Type", "application/json");
// JSON body
String payload = String("{\"station\":1, \"state\":\"") + state + "\"}";
int httpCode = http.POST(payload); // send POST
if (httpCode > 0) {
Serial.printf("API response: %d\n", httpCode);
String response = http.getString();
Serial.println(response);
lcd.setCursor(0, 2);
lcd.print("Done. [");
lcd.print(httpCode);
lcd.print("] ");
rgbBlinkOff();
rgbSetColor(0, 255, 0);
rgbOn();
}
else
{
lcd.setCursor(0, 2);
lcd.print("HTTP ERROR. ");
lcd.setCursor(0, 3);
lcd.print(http.errorToString(httpCode).c_str());
Serial.printf("API error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
} else {
Serial.println("WiFi not connected");
}
digitalWrite(API_LED_PIN, LOW);
turnWiFiOff();
}
// Turn Wi-Fi ON and connect
void turnWiFiOn()
{
// start led blink
setLedBlinkState(WIFI_LED_PIN, true);
if (WiFi.getMode() == WIFI_OFF)
{
WiFi.mode(WIFI_STA);
}
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("Wi-Fi ON: connecting...");
WiFi.begin(ssid, password);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 10000)
{
delay(200);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED)
{
// stop led blink
setLedBlinkState(WIFI_LED_PIN, false);
// turn led on
setLedPinState(WIFI_LED_PIN, HIGH);
Serial.println("\nConnected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
else
{
Serial.println("\nFailed to connect.");
}
}
}
// Turn Wi-Fi OFF to save power
void turnWiFiOff()
{
if (WiFi.status() == WL_CONNECTED)
{
digitalWrite(WIFI_LED_PIN, LOW);
WiFi.disconnect(true); // drop connection and clear state
}
WiFi.mode(WIFI_OFF);
Serial.println("Wi-Fi OFF");
}
void connectWifi()
{
rgbSetColor(0, 0, 255);
rgbBlinkOn();
// Connect to WiFi
WiFi.begin(ssid, password);
lcd.setCursor(0, 2);
lcd.print("Connecting to WiFi");
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
lcd.print(".");
rgbBlinkOff();
rgbSetColor(0, 255, 0);
rgbOn();
}
Serial.println(" connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// lcd.setCursor(0, 1);
// lcd.print("IP: ");
// lcd.print(WiFi.localIP());
}
// RBG LED JAZZ
void rgbSetColor(int r, int g, int b)
{
redLevel = r;
greenLevel = g;
blueLevel = b;
}
// Turn on a solid color
void rgbOn()
{
analogWrite(RGB_LED_RED_PIN, redLevel); // 0–255
analogWrite(RGB_LED_GREEN_PIN, greenLevel);
analogWrite(RGB_LED_BLUE_PIN, blueLevel);
}
void rgbOff()
{
analogWrite(RGB_LED_RED_PIN, 0); // 0–255
analogWrite(RGB_LED_GREEN_PIN, 0);
analogWrite(RGB_LED_BLUE_PIN, 0);
}
void rgbBlinkOn()
{
rgbBlinking = true;
rgbLastToggle = millis();
rgbOnState = true;
}
void rgbBlinkOff()
{
rgbBlinking = false;
}
void rgbBlinkUpdate()
{
if (rgbBlinking && millis() - rgbLastToggle >= (unsigned long) rgbBlinkInterval)
{
rgbLastToggle = millis();
rgbOnState = !rgbOnState;
if (rgbOnState)
{
rgbOn();
}
else
{
rgbOff();
}
}
}
// Initialize 0..5 pixel-wide custom chars
void lcdInitProgressBar(LiquidCrystal_I2C &lcd)
{
byte barChars[6][8] = {
{B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000}, // empty
{B10000,B10000,B10000,B10000,B10000,B10000,B10000,B10000}, // 1 px
{B11000,B11000,B11000,B11000,B11000,B11000,B11000,B11000}, // 2 px
{B11100,B11100,B11100,B11100,B11100,B11100,B11100,B11100}, // 3 px
{B11110,B11110,B11110,B11110,B11110,B11110,B11110,B11110}, // 4 px
{B11111,B11111,B11111,B11111,B11111,B11111,B11111,B11111}, // 5 px
};
for (int i=0; i<6; i++) lcd.createChar(i, barChars[i]);
}
// fraction: 1.0 = full, 0.0 = empty
// width = number of LCD cells (e.g. 16 for full row)
void lcdDrawProgressBar(LiquidCrystal_I2C &lcd, uint8_t col, uint8_t row,
uint8_t width, float fraction) {
if (fraction < 0) fraction = 0;
if (fraction > 1) fraction = 1;
uint16_t totalPixels = width * 5; // total pixels across bar
uint16_t filled = (uint16_t)roundf(fraction * totalPixels);
lcd.setCursor(col, row);
for (uint8_t i=0; i<width; i++) {
if (filled >= 5) {
lcd.write((uint8_t)5); // full block
filled -= 5;
} else {
lcd.write((uint8_t)filled); // 0..4 pixels
filled = 0;
}
}
}