#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <FS.h>
#include "Free_Fonts.h"
#include <qrcode_espi.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <TFT_eWidget.h>
#include <HardwareSerial.h>
#include "config.h"
#define CALIBRATION_FILE "/TouchCalData1"
#define REPEAT_CAL true
#define BUTTON_W 160
#define BUTTON_H 240
#define CONFIRMATION_BUTTON_H 80
HardwareSerial SerialPort(0);
const int washerPin = 14;
const int dryerPin = 12;
TFT_eSPI tft = TFT_eSPI();
QRcode_eSPI qrcode (&tft);
ButtonWidget btnDry = ButtonWidget(&tft);
ButtonWidget btnWash = ButtonWidget(&tft);
ButtonWidget btnFS = ButtonWidget(&tft);
ButtonWidget btnFSDry = ButtonWidget(&tft);
ButtonWidget btnFSWash = ButtonWidget(&tft);
ButtonWidget btnConfirm = ButtonWidget(&tft);
ButtonWidget btnCancel = ButtonWidget(&tft);
ButtonWidget* btn[] = {&btnDry , &btnWash};
uint8_t buttonCount = sizeof(btn) / sizeof(btn[0]);
ButtonWidget* btnFSMenu[] = {&btnFSDry , &btnFSWash};
uint8_t buttonFSCount = sizeof(btnFSMenu) / sizeof(btnFSMenu[0]);
ButtonWidget* btnConfirmation[] = {&btnCancel , &btnConfirm};
uint8_t buttonConfirmationCount = sizeof(btnConfirmation) / sizeof(btnConfirmation[0]);
String selectedService; // Washer / Dryer / Full Service
String selectedConfirmation; // Confirm / Cancel
String superCardId;
bool machineStatus = false; // To wait for service to be selected (Washer / Dryer / Full Service)
bool FSstatus = false; // To wait for Full Service service to be selected (After QR scanned)
bool phpDBValidate = false; // Check if QR is stored in our database (if yes --> phpDBValidate = false)
bool confirmationButtonPressed = false; // Check if confirmation page has been pressed
bool confirm = false; // If confirm button pressed, confirm = true
String machineNo;
String custName;
String custPhoneNumber;
String custEmail;
const char characters[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const int length = 8; // Set the desired length of the random alphanumeric string
char charinvoiceId[length + 1]; // +1 for null terminator
String invoiceId;
int price;
void setup(){
Serial.begin(115200);
SerialPort.begin(115200, SERIAL_8N1, 3, 1); // Initialize the second Serial port
WiFi.mode(WIFI_STA); //Optional
WiFi.begin(ssid, password);
Serial.println("\nConnecting");
while(WiFi.status() != WL_CONNECTED){
Serial.print(".");
delay(100);
}
Serial.println("\nConnected to the WiFi network");
Serial.print("Local ESP32 IP: ");
Serial.println(WiFi.localIP());
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);
touch_calibrate();
pinMode(washerPin, OUTPUT);
pinMode(dryerPin, OUTPUT);
}
void loop(){
Serial.println();
Serial.println("1. Washer");
Serial.println("2. Dryer");
Serial.println("3. Full Service");
Serial.println("Which service would you like to choose?");
uint16_t t_x = 9999, t_y = 9999; // To store the touch coordinates
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);
tft.setFreeFont(FF18);
initButtons();
while(machineStatus == false) {
// Pressed will be set true if there is a valid touch on the screen
bool pressed = tft.getTouch(&t_x, &t_y);
for (uint8_t b = 0; b < buttonCount; b++) {
if (pressed) {
if (btn[b]->contains(t_x, t_y)) {
btn[b]->press(true);
btn[b]->pressAction();
}
}
else {
btn[b]->press(false);
btn[b]->releaseAction();
}
}
// Check if data is available to read
if (Serial.available() > 0) {
// Read the incoming data until a newline character is encountered
superCardId = Serial.readStringUntil('\n');
if (superCardId.length() > 0) {
superCardId.trim();
selectedService = "fs";
machineStatus = true;
textDisplay("QR Scanned!");
delay(1000);
}
}
}
selectedService.trim(); // Remove leading and trailing whitespaces
// Process the choice based on the input string
if (selectedService.equals("w")) {
price = washer_price;
machineNo = machineNoWash;
confirmationPrompt("Self Service Washer", price);
Serial.print(confirm);
if(confirm == true) {
runMachine(washerPin, "Washing", "self-service");
}
} else if (selectedService.equals("d")) {
price = dryer_price;
machineNo = machineNoDry;
confirmationPrompt("Self Service Dryer", price);
Serial.print(confirm);
if(confirm == true) {
runMachine(dryerPin, "Drying", "self-service");
}
} else if (selectedService.equals("fs")) {
lgFullservice();
}
delay(100);
resetDefault();
}
void fullserviceMenu() {
Serial.println();
Serial.println("1. Washer");
Serial.println("2. Dryer");
Serial.println("Which service would you like to choose?");
uint16_t t_x = 9999, t_y = 9999; // To store the touch coordinates
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);
tft.setFreeFont(FF18);
initFullserviceButtons();
while(FSstatus == false) {
// Pressed will be set true if there is a valid touch on the screen
bool pressed = tft.getTouch(&t_x, &t_y);
for (uint8_t c = 0; c < buttonFSCount; c++) {
if (pressed) {
if (btnFSMenu[c]->contains(t_x, t_y)) {
btnFSMenu[c]->press(true);
btnFSMenu[c]->pressAction();
}
}
else {
btnFSMenu[c]->press(false);
btnFSMenu[c]->releaseAction();
}
}
}
// Read the input string from Serial
selectedService.trim(); // Remove leading and trailing whitespaces
// Process the choice based on the input string
if (selectedService.equals("w")) {
price = 0;
machineNo = machineNoWash;
confirmationPrompt("Full Service Washer", price);
Serial.print(confirm);
if (confirm == true) {
Serial.println(invoiceId);
saveDB("Washer", "FS");
if (invoiceId == supercard_code) {
Serial.println("QR validated");
runMachine(washerPin, "Washing", "full-service");
} else {
textDisplay("QR not registered!");
Serial.println("QR not validated");
delay(2000);
}
}
} else if (selectedService.equals("d")) {
price = 0;
machineNo = machineNoDry;
confirmationPrompt("Full Service Dryer", price);
Serial.print(confirm);
if (confirm == true) {
Serial.println(invoiceId);
saveDB("Dryer", "FS");
if (invoiceId == "supercard_code") {
Serial.println("QR validated");
runMachine(dryerPin, "Drying", "full-service");
} else {
textDisplay("QR not registered!");
Serial.println("QR not validated");
delay(2000);
}
}
}
delay(100);
}
void confirmationPrompt(String serviceActivity, int servicePrice) {
Serial.println("Confirm?");
uint16_t t_x = 9999, t_y = 9999; // To store the touch coordinates
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_WHITE);
tft.setTextFont(2);
tft.setCursor(20, 20);
tft.setTextColor(TFT_BLACK);
tft.setTextSize(2);
tft.print(serviceActivity);
tft.setCursor(20, 60);
tft.setTextColor(TFT_BLACK);
tft.setTextSize(2);
tft.print("Price: Rp. " + String(servicePrice));
tft.setFreeFont(FF18);
initConfirmationButtons();
while(confirmationButtonPressed == false) {
// Pressed will be set true if there is a valid touch on the screen
bool pressed = tft.getTouch(&t_x, &t_y);
for (uint8_t c = 0; c < buttonConfirmationCount; c++) {
if (pressed) {
if (btnConfirmation[c]->contains(t_x, t_y)) {
btnConfirmation[c]->press(true);
btnConfirmation[c]->pressAction();
}
}
else {
btnConfirmation[c]->press(false);
btnConfirmation[c]->releaseAction();
}
}
}
// Read the input string from Serial
selectedConfirmation.trim(); // Remove leading and trailing whitespaces
// Process the choice based on the input string
Serial.print(selectedConfirmation);
if (selectedConfirmation.equals("confirm")) {
confirm = true;
Serial.println("Confirmed");
} else if (selectedConfirmation.equals("cancel")) {
Serial.println("Canceled");
}
delay(100);
}
bool qris_transaction() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin("https://api.midtrans.com/v2/charge");
// Add headers
http.addHeader("Accept", "application/json");
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", midtrans_token);
// Get the current date and time
generateRandomAlphanumeric(charinvoiceId, length);
invoiceId = String(charinvoiceId);
String payload = "{\"payment_type\":\"qris\",\"transaction_details\":{\"order_id\":\"DLT-" + invoiceId + "\",\"gross_amount\":" + price + "},\"custom_expiry\":{\"expiry_duration\":\"1\",\"unit\":\"minute\"},\"qris\":{\"acquirer\":\"gopay\"}}";
// Make the POST request
for(int request = 0; request < 3; request++) {
Serial.print(request);
int httpCode = http.POST(payload);
if (httpCode > 0) {
Serial.printf("[HTTP] POST... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {
String response = http.getString();
Serial.println(response);
// Parse JSON response
DynamicJsonDocument jsonDoc(4096);
DeserializationError error = deserializeJson(jsonDoc, response);
if (error) {
Serial.print("JSON parsing error: ");
Serial.println(error.c_str());
} else {
// Extract URL from the response
const String qrCode = jsonDoc["qr_string"];
Serial.print("QR Code URL: ");
Serial.println(qrCode);
tft.begin();
qrcode.init();
qrcode.create(qrCode);
return true;
}
break;
}
} else {
Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
}
http.end();
} else{
textDisplay("Wi-Fi not connected :(");
}
return false;
}
bool readQr() {
invoiceId = String(superCardId);
Serial.print("");
Serial.println(invoiceId);
custName = "Super Card Dalton";
custPhoneNumber = "";
custEmail = "";
return true;
}
String qrisTransactionStatus() {
String status;
while(true) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
String url = "https://api.midtrans.com/v2/DLT-" + invoiceId + "/status"; // Fix the URL
http.begin(url.c_str());
// Add headers
http.addHeader("Accept", "application/json");
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", midtrans_token);
// Make the GET request without payload
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {
String response = http.getString();
Serial.println(response);
// Parse JSON response
DynamicJsonDocument jsonDoc(2048);
DeserializationError error = deserializeJson(jsonDoc, response);
if (error) {
Serial.print("JSON parsing error: ");
Serial.println(error.c_str());
} else {
// Extract transaction_status
const char* transactionStatus = jsonDoc["transaction_status"];
status = String(transactionStatus);
if (status == "pending") {
Serial.print("Transaction is pending");
delay(1000);
} else if (status == "settlement") {
Serial.print("Transaction is Successful!");
break;
} else if (status == "expire") {
Serial.print("Transaction is Expired!");
textDisplay("Transaction is Expired! Please try again");
break;
}
}
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
}
return status;
}
void saveDB(String machineType, String orderType) {
while(true) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin("https://midtrans.zaplaundry.com/api/transaction");
// Add headers
http.addHeader("Accept", "application/json");
http.addHeader("Content-Type", "application/json");
String payload = "{\"location\":\"" + location + "\",\"orderType\":\"" + orderType + "\",\"invoiceId\":\"" + invoiceId + "\",\"custName\":\"" + custName + "\",\"custPhoneNumber\":\"" + custPhoneNumber + "\",\"custEmail\":\"" + custEmail + "\",\"machineType\":\"" + machineType + "\",\"machineNo\":\"" + machineNo + "\",\"price\":\"" + price + "\"}";
Serial.println(payload);
int httpCode = http.POST(payload);
if (httpCode > 0) {
Serial.printf("[HTTP] POST... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {
String response = http.getString();
Serial.println(response);
// Parse JSON response
DynamicJsonDocument jsonDoc(4096);
DeserializationError error = deserializeJson(jsonDoc, response);
if (error) {
Serial.print("JSON parsing error: ");
Serial.println(error.c_str());
delay(100);
} else {
// Extract transaction_status
Serial.println("accepted");
phpDBValidate = true;
delay(100);
}
}
else {
Serial.println("rejected");
phpDBValidate = false;
Serial.println("Invoice already used");
}
break;
} else {
Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
delay(1000);
}
}
void runMachine(int pin, String activity, String serviceType) {
if(serviceType == "self-service"){
textDisplay("Requesting QR...");
bool qrisRequestStatus = qris_transaction();
if (qrisRequestStatus == true) {
String status = qrisTransactionStatus();
Serial.println("Status: "+status);
if(status == "settlement"){
custName = "";
custPhoneNumber = "";
custEmail = "";
String machineType = (activity == "Washing") ? "washer" : "dryer";
Serial.println(machineType);
saveDB(machineType, "SS");
}
}
}
textDisplay("Activating Machine...");
digitalWrite(pin, HIGH); // Turn on the optocoupler LED
delay(25); // Wait for 25ms
digitalWrite(pin, LOW); // Turn off the optocoupler LED
delay(500);
textDisplay("Happy "+ activity + " ^_^");
delay(2000);
}
void lgFullservice() {
bool readQrSuccess = readQr();
if(readQrSuccess) {
fullserviceMenu();
delay(100);
}
}
void btnDry_pressAction(void)
{
if (btnDry.justPressed()) {
btnDry.drawSmoothButton(true);
}
}
void btnDry_releaseAction(void)
{
if (btnDry.justReleased()) {
Serial.println("Dry Button just pressed");
btnDry.drawSmoothButton(false);
selectedService = "d";
machineStatus = true;
}
}
void btnWash_pressAction(void)
{
if (btnWash.justPressed()) {
btnWash.drawSmoothButton(true);
}
}
void btnWash_releaseAction(void)
{
if (btnWash.justReleased()) {
Serial.println("Wash Button just pressed");
btnWash.drawSmoothButton(false);
selectedService = "w";
machineStatus = true;
}
}
void btnFS_pressAction(void)
{
if (btnFS.justPressed()) {
btnFS.drawSmoothButton(true);
}
}
void btnFS_releaseAction(void)
{
if (btnFS.justReleased()) {
Serial.println("FS Button just pressed");
btnFS.drawSmoothButton(false);
selectedService = "fs";
machineStatus = true; // Set machineStatus to true after processing the user input
}
}
void btnFSDry_pressAction(void)
{
if (btnFSDry.justPressed()) {
btnFSDry.drawSmoothButton(true);
}
}
void btnFSDry_releaseAction(void)
{
if (btnFSDry.justReleased()) {
Serial.println("Dry Button just pressed");
btnFSDry.drawSmoothButton(false);
selectedService = "d";
FSstatus = true;
}
}
void btnFSWash_pressAction(void)
{
if (btnFSWash.justPressed()) {
btnFSWash.drawSmoothButton(true);
}
}
void btnFSWash_releaseAction(void)
{
if (btnFSWash.justReleased()) {
Serial.println("Wash Button just pressed");
btnFSWash.drawSmoothButton(false);
selectedService = "w";
FSstatus = true;
}
}
void btnCancel_pressAction(void)
{
if (btnCancel.justPressed()) {
btnCancel.drawSmoothButton(true);
}
}
void btnCancel_releaseAction(void)
{
if (btnCancel.justReleased()) {
Serial.println("Cancel Button just pressed");
btnCancel.drawSmoothButton(false);
selectedConfirmation = "cancel";
confirmationButtonPressed = true;
}
}
void btnConfirm_pressAction(void)
{
if (btnConfirm.justPressed()) {
btnConfirm.drawSmoothButton(true);
}
}
void btnConfirm_releaseAction(void)
{
if (btnConfirm.justReleased()) {
Serial.println("Confirm Button just pressed");
btnConfirm.drawSmoothButton(false);
selectedConfirmation = "confirm";
confirmationButtonPressed = true;
}
}
void initButtons() {
uint16_t x = 0;
uint16_t y = 0;
btnDry.initButtonUL(x, y, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_RED, TFT_WHITE, "Dryer", 1);
btnDry.setPressAction(btnDry_pressAction);
btnDry.setReleaseAction(btnDry_releaseAction);
btnDry.drawSmoothButton(false, 0, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
x = 160;
btnWash.initButtonUL(x, y, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_BLUE, TFT_WHITE, "Washer", 1);
btnWash.setPressAction(btnWash_pressAction);
btnWash.setReleaseAction(btnWash_releaseAction);
btnWash.drawSmoothButton(false, 0, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
}
void initFullserviceButtons() {
uint16_t x = 0;
uint16_t y = 0;
btnFSDry.initButtonUL(x, y, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_GREEN, TFT_WHITE, "FS Dryer", 1);
btnFSDry.setPressAction(btnFSDry_pressAction);
btnFSDry.setReleaseAction(btnFSDry_releaseAction);
btnFSDry.drawSmoothButton(false, 0, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
x = 160;
btnFSWash.initButtonUL(x, y, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_PURPLE, TFT_WHITE, "FS Washer", 1);
btnFSWash.setPressAction(btnFSWash_pressAction);
btnFSWash.setReleaseAction(btnFSWash_releaseAction);
btnFSWash.drawSmoothButton(false, 0, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
}
void initConfirmationButtons() {
uint16_t x = 0;
uint16_t y = 160;
btnCancel.initButtonUL(x, y, BUTTON_W, CONFIRMATION_BUTTON_H, TFT_WHITE, TFT_ORANGE, TFT_WHITE, "Cancel", 1);
btnCancel.setPressAction(btnCancel_pressAction);
btnCancel.setReleaseAction(btnCancel_releaseAction);
btnCancel.drawSmoothButton(false, 0, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
x = 160;
btnConfirm.initButtonUL(x, y, BUTTON_W, CONFIRMATION_BUTTON_H, TFT_WHITE, TFT_BLUE, TFT_WHITE, "Confirm", 1);
btnConfirm.setPressAction(btnConfirm_pressAction);
btnConfirm.setReleaseAction(btnConfirm_releaseAction);
btnConfirm.drawSmoothButton(false, 0, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
}
void textDisplay(String text) {
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_WHITE);
tft.setTextFont(2);
tft.setCursor(20, 120);
tft.setTextColor(TFT_BLACK);
tft.setTextSize(2);
tft.print(text);
}
void touch_calibrate() {
uint16_t calData[5];
uint8_t calDataOK = 0;
// check file system exists
if (!LittleFS.begin()) {
Serial.println("Formating file system");
LittleFS.format();
LittleFS.begin();
}
// check if calibration file exists and size is correct
if (LittleFS.exists(CALIBRATION_FILE)) {
if (REPEAT_CAL)
{
// Delete if we want to re-calibrate
LittleFS.remove(CALIBRATION_FILE);
}
else
{
File f = LittleFS.open(CALIBRATION_FILE, "r");
if (f) {
if (f.readBytes((char *)calData, 14) == 14)
calDataOK = 1;
f.close();
}
}
}
if (calDataOK && !REPEAT_CAL) {
// calibration data valid
tft.setTouch(calData);
} else {
// data not valid so recalibrate
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 0);
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.println("Touch corners as indicated");
tft.setTextFont(1);
tft.println();
if (REPEAT_CAL) {
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.println("Set REPEAT_CAL to false to stop this running again!");
}
tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.println("Calibration complete!");
// store data
File f = LittleFS.open(CALIBRATION_FILE, "w");
if (f) {
f.write((const unsigned char *)calData, 14);
f.close();
}
}
}
void resetDefault() {
FSstatus = false;
machineStatus = false;
confirmationButtonPressed = false;
confirm = false;
}
void generateRandomAlphanumeric(char* result, int length) {
for (int i = 0; i < length; ++i) {
int randomIndex = random(sizeof(characters) - 1);
result[i] = characters[randomIndex];
}
result[length] = '\0'; // Null-terminate the string
}Loading
ili9341-cap-touch
ili9341-cap-touch