#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
//#include <SD.h>
#include <SPI.h>
#include <SdFat.h>
#include <avr/wdt.h>
//#include <EEPROM.h>
//#include <SoftwareSerial.h>
#define buttonPin 9
//const int SIM800_TX_PIN = 3;
//const int SIM800_RX_PIN = 2;
//SoftwareSerial sim800(SIM800_TX_PIN, SIM800_RX_PIN);
// LCD setup
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display
// Keypad setup
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
// Update the pin numbers according to Arduino Mega
byte rowPins[ROWS] = {22, 23, 24, 25}; // Connect to the row pinouts of the keypad
byte colPins[COLS] = {26, 27, 28, 29}; // Connect to the column pinouts of the keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
byte signalLevel[6][8] = {
{B00100, B01110, B01110, B01110, B00100, B00000, B00100, B01110}, // No signal
{B00000, B00000, B00000, B00000, B00000, B10000, B10000, B10000}, // Level 1
{B00000, B00000, B00000, B00000, B01000, B01000, B11000, B11000}, // Level 2
{B00000, B00000, B00000, B00100, B00100, B01100, B11100, B11100}, // Level 3
{B00000, B00010, B00010, B00110, B00110, B01110, B11110, B11110}, // Level 4
{B00001, B00011, B00011, B00111, B00111, B01111, B11111, B11111} // Full signal
};
// Custom character for signal icon (Trident looking MF)
byte signalIcon[8] = {
B10101, // *
B10101, // ***
B10101, // *****
B11111, // ***
B00100, // *
B00100, // *
B01110, // *
B11111 //
};
// Define the SD card chip select pin
const uint8_t chipSelect = 53;
// Create an SDFat object
SdFat SD;
// Relay, LED, and button setup
const int relayPin = 8; // Update if needed
const int ledPin = 11; // Update if needed
//const int buttonPin = 10; // Update if needed
const int led = 12;
// Admin settings
String transactionID = "";
float ratePerLiter;
float pumpFlowRate;
float remainingLiquid;
float amountPaid = 0.0;
bool dispensing = false;
String adminPIN;
bool isBuyMode = false;
// Pending and complete orders files
String pendingOrdersFile = "PENDING.TXT";
String completeOrdersFile = "COMPLETE.TXT";
const int SHORT_PRESS_TIME = 1000; // 1000 milliseconds
unsigned long pressedTime = 0;
unsigned long releasedTime = 0;
bool isPressing = false;
bool click = false;
char characters[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int charIndex[4] = {0, 0, 0, 0};
int cursorPosition = 0;
unsigned long lastMessageCheckTime = 0; // Last time we checked for messages
const long messageCheckInterval = 100; // Interval at which to check for messages (100ms)
unsigned long signalCheckInterval = 10000; // Check signal every 5 seconds
int currentSignalLevel = 0; // Default signal level
unsigned long previousMillis1 = 0;
unsigned long previousMillis = 0;
int scrollIndex = 0;
String message = "Hello! Press * to cancel and # to enter. Pay any amount to 0743994562 or scan the QR code. ";
int messageLength = message.length();
int lcdCols = 16; // Assuming 16 columns for a 16x2 LCD display
int scrollDelay = 250; // Adjust scroll speed if needed
bool shortPressDetected = false; // Flag to indicate a short button press
String messageBuffer = ""; // Buffer to store incoming message
bool messageReady = false; // Flag to indicate when a full message has been received
bool isProcessingMessage = false; // Flag to indicate if a message is being processed
// Function prototypes
void initSIM800L();
//void checkForMessages();
//void processMessage(String message);
void logPendingOrder(String transID, float amountPaid);
enum States {START, CHECK_ORDERS, ENTER_TRANSACTION, VERIFY_TRANSACTION, DISPENSE, ADMIN_MODE, ADMIN_MENU, DISPENSE_BY_PRICE} state;
void setup() {
//Checking if watchdog timer triggered reset
if (MCUSR & (1 << WDRF)) {
MCUSR &= ~(1 << WDRF); // Clear the WDT reset flag
lcd.print("Recovered by WT!");
delay(2000);
}
// Create custom characters for signal levels
for (int i = 0; i < 6; i++) {
lcd.createChar(i, signalLevel[i]);
}
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(9600);
Serial3.begin(9600);
// Initialize the SIM800L module
initSIM800L();
lcd.init();
lcd.backlight();
// Create custom characters
for (int i = 0; i < 6; i++) {
lcd.createChar(i, signalLevel[i]); // Triangles for signal levels
}
lcd.createChar(6, signalIcon); // Signal label graphic
lcd.setCursor(3, 0);
lcd.print("WELCOME");
lcd.setCursor(0, 1);
lcd.print("Joe, Dan & Yvon");
delay(2000);
initializeSDCard();
/*
if (!SD.begin(chipSelect)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SD init failed!");
while (1);
}
*/
pinMode(relayPin, OUTPUT);
pinMode(ledPin, OUTPUT);
pinMode(led, OUTPUT);
digitalWrite(relayPin, HIGH);
digitalWrite(ledPin, LOW);
digitalWrite(led, LOW);
readConfigFromSD(); // Read the configuration from SD card
Serial.print("Admin PIN read from SD: ");
Serial.println(adminPIN);
Serial.print("Rate/L read from SD: ");
Serial.println(ratePerLiter);
Serial.print("PumpFlowRate read from SD: ");
Serial.println(pumpFlowRate);
checkActiveOrder();
state = START;
}
void loop() {
// Periodically check for signal strength updates
updateSignalStrength();
updateSerial();
if (isButtonPressed() && state == START) {
enterBuyMode(); // Trigger BUY mode when the button is pressed
return; // Return to avoid running the rest of the loop until BUY mode completes
}
switch (state) {
case START:
displayRateAndMessage();
handleStartState();
break;
case CHECK_ORDERS:
checkOrdersState();
break;
case ENTER_TRANSACTION:
verifyPendingOrder();
break;
case VERIFY_TRANSACTION:
// verifyTransactionState();
break;
case DISPENSE:
dispenseLiquid(transactionID, amountPaid);
state = START;
break;
case ADMIN_MODE:
enterAdminMode();
state = START;
break;
case ADMIN_MENU:
adminMenu();
state = START;
break;
case DISPENSE_BY_PRICE:
dispenseByPrice();
state = START;
break;
}
}
void handleStartState() {
char key = keypad.getKey();
if (key == 'A') {
state = ADMIN_MODE;
} else if (key) {
state = CHECK_ORDERS;
}
}
/////////////////////////////////////////////////
void checkOrdersState() {
if (hasPendingOrders()) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pending orders");
lcd.setCursor(0, 1);
lcd.print(" available");
delay(2000);
state = ENTER_TRANSACTION;
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("No pending orders");
lcd.setCursor(0, 1);
lcd.print(" Pay Please");
delay(2000);
state = START;
}
}
/////////////////////////////////////////////////////////
void enterBuyMode() {
isBuyMode = true; // Indicate the system is in BUY mode
const int paymentTimeout = 120; // 2 minutes in seconds
unsigned long startTime = millis();
bool paymentReceived = false; // Local flag to track payment during this mode
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Please pay!");
lcd.setCursor(0, 1);
lcd.print("Time: 1:59");
while (true) {
// Calculate elapsed time
unsigned long elapsedTime = (millis() - startTime) / 1000;
int remainingTime = paymentTimeout - elapsedTime;
// Format time as MM:SS
int minutes = remainingTime / 60;
int seconds = remainingTime % 60;
// Update timer display on LCD
lcd.setCursor(6, 1);
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
// Check for messages and process payments
updateSerial(); // Updates the serial interface and handles incoming messages
/*
if (!isBuyMode) {
// If payment was successfully processed and `isBuyMode` was reset in processMpesaMessage
lcd.clear();
lcd.print("Payment Received!");
delay(2000);
paymentReceived = true; // Mark payment as received
break;
}
*/
// Allow user to cancel the buy mode
char key = keypad.getKey();
if (key == '*') {
isBuyMode = false; // Reset BUY mode
lcd.clear();
lcd.print(" Cancelled");
delay(2000);
return; // Exit BUY mode
}
// Handle timeout
if (remainingTime <= 0) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Timeout!!");
lcd.setCursor(0, 1);
lcd.print("Enter Details");
delay(2000);
isBuyMode = false; // Reset BUY mode
return; // Exit BUY mode
}
delay(200); // Small delay to avoid excessive loop iterations
}
/*
if (paymentReceived) {
// Wait for user to press the dispense button
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Press button to");
lcd.setCursor(0, 1);
lcd.print("dispense");
waitForButtonPress();
// Dispense liquid using the last transaction details
dispenseLiquid(transactionID, amountPaid); // Dispense the received payment
}
*/
// Reset state and return to the main menu
state = START;
}
void verifyPendingOrder() {
String phone, transactionID;
float amount, remainingAmount;
float cancel = 0;
// Prompt user to enter details
enterDetails(phone, amount, cancel);
// Verify details
if (verifyDetails(phone, amount, transactionID, remainingAmount)) {
lcd.clear();
lcd.setCursor(0, 0);
// Check if this is a continuation of a previous dispense
if (amount == remainingAmount) {
lcd.print(" Verified!");
} else {
lcd.print("Resume Dispense!");
lcd.setCursor(0, 1);
lcd.print("Rem: ");
lcd.print(remainingAmount, 2);
lcd.print(" Ksh");
}
delay(2000);
// Calculate volume to dispense based on the remaining amount
float volumeToDispenseLiters = remainingAmount / ratePerLiter;
float volumeToDispenseMl = volumeToDispenseLiters * 1000; // Convert to milliliters
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Amt: ");
lcd.print((int)volumeToDispenseMl);
lcd.print(" ml");
lcd.setCursor(0, 1);
lcd.print("Press Button");
waitForButtonPress();
dispensing = true;
// Start dispensing based on transaction ID and remaining amount
dispenseLiquid(transactionID, remainingAmount);
dispensing = false;
state = START;
return;
} else if (cancel == 1) {
cancel = 0;
state = START;
} else if (!verifyDetails(phone, amount, transactionID, remainingAmount)){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("No Match Found!");
delay(1000);
state = START;
}
}
/*
void verifyPendingOrder() {
String phone, transactionID;
float amount;
// Prompt user to enter details
enterDetails(phone, amount);
// Verify details
if (verifyDetails(phone, amount, transactionID)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Verified!");
delay (1000);
float volumeToDispenseLiters = amount / ratePerLiter;
float volumeToDispenseMl = volumeToDispenseLiters * 1000; // Convert to milliliters
lcd.clear();
lcd.setCursor(0, 0);
//lcd.print("Ksh: ");
//lcd.print((int)amount);
lcd.print("Amt: ");
lcd.print((int)volumeToDispenseMl);
lcd.print(" ml");
lcd.setCursor(0, 1);
lcd.print("Press Button");
waitForButtonPress();
dispensing = true;
dispenseLiquid(transactionID, amount);
dispensing = false;
state = START;
return;
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("No Match Found!");
delay(2000);
state = START;
}
}
*/
void enterDetails(String &enteredPhone, float &enteredAmount, float &cancel) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Phone No:");
String phone = "";
String amount = "";
bool enteringPhone = true;
float out;
unsigned long lastInputTime = millis(); // Track time of the last input
while (true) {
char key = keypad.getKey();
// Check if timeout has occurred
if (millis() - lastInputTime > 30000) { // 30-second timeout
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Timeout!");
delay(2000); // Show message for 2 seconds
return; // Exit function and return to the start menu
}
if (key) {
lastInputTime = millis(); // Reset timeout timer on input
if (key == '*') { // Backspace or Cancel
if (enteringPhone && phone.length() == 0) {
// Exit to the start menu if no phone number entered
//lcd.clear();
//lcd.setCursor(0, 0);
//lcd.print("Returning to Menu");
//delay(2000);
out = 1;
cancel = out;
return;
} else if (enteringPhone && phone.length() > 0) {
// Remove last digit of the phone number
phone.remove(phone.length() - 1);
} else if (!enteringPhone && amount.length() == 0) {
// Go back to editing phone number
enteringPhone = true;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Phone No:");
} else if (!enteringPhone && amount.length() > 0) {
// Remove last digit of the amount
amount.remove(amount.length() - 1);
}
} else if (key == '#') { // Confirm
if (enteringPhone) {
if (phone.length() == 0) {
// Prompt user to enter a valid phone number
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Phone Required");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Phone No:");
} else {
// Move to entering amount
enteringPhone = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Amount:");
}
} else {
if (amount.length() == 0) {
// Prompt user to enter a valid amount
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Amount Required");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Amount:");
} else {
// Finished entering details
enteredPhone = phone;
enteredAmount = amount.toFloat();
break;
}
}
} else if (key >= '0' && key <= '9') { // Numeric input
if (enteringPhone) {
phone += key;
} else {
amount += key;
}
}
// Update LCD display
lcd.clear();
lcd.setCursor(0, 0);
if (enteringPhone) {
lcd.print("Enter Phone No:");
lcd.setCursor(0, 1);
lcd.print(phone);
} else {
lcd.print("Enter Amount:");
lcd.setCursor(0, 1);
lcd.print(amount);
}
}
}
}
/*
void enterDetails(String &enteredPhone, float &enteredAmount) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Phone No:");
String phone = "";
String amount = "";
bool enteringPhone = true;
unsigned long lastInputTime = millis(); // Track time of the last input
while (true) {
char key = keypad.getKey();
// Check if timeout has occurred
if (millis() - lastInputTime > 30000) { // 1 minute timeout
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Timeout!");
delay(2000); // Show message for 2 seconds
//state = START;
return; // Exit function and go back to the start menu
}
if (key) {
lastInputTime = millis(); // Reset timeout timer on input
if (key == '*') { // Backspace
if (enteringPhone && phone.length() > 0) {
phone.remove(phone.length() - 1);
} else if (!enteringPhone && amount.length() > 0) {
amount.remove(amount.length() - 1);
}
} else if (key == '#') { // Confirm
if (enteringPhone) {
enteringPhone = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Amount:");
} else {
// Finished entering both details
enteredPhone = phone;
enteredAmount = amount.toFloat();
break;
}
} else if (key >= '0' && key <= '9') { // Numeric input
if (enteringPhone) {
phone += key;
} else {
amount += key;
}
}
// Update LCD display
lcd.clear();
lcd.setCursor(0, 0);
if (enteringPhone) {
lcd.print("Enter Phone No: ");
lcd.setCursor(0, 1);
lcd.print(phone);
} else {
lcd.print("Enter Amount: ");
lcd.setCursor(0, 1);
lcd.print(amount);
}
}
}
}
*/
bool verifyDetails(String phone, float amount, String &transactionID, float &remainingAmount) {
File file = SD.open(pendingOrdersFile, FILE_READ);
if (!file) {
Serial.println("Error opening pending orders file!");
return false;
}
// Normalize the user-entered phone number to a standard format
phone.trim();
phone.replace("+", ""); // Remove '+' if it exists
if (phone.startsWith("0")) {
phone = "254" + phone.substring(1); // Convert "0XXXXXXXXX" to "254XXXXXXXXX"
}
while (file.available()) {
String line = file.readStringUntil('\n');
line.trim();
if (line.length() == 0) continue; // Skip empty lines
// Split line into components
int firstComma = line.indexOf(',');
int secondComma = line.indexOf(',', firstComma + 1);
int thirdComma = line.indexOf(',', secondComma + 1);
int lastComma = line.lastIndexOf(',');
// Extract details from the line
String storedTransID = line.substring(0, firstComma);
float storedAmount = line.substring(firstComma + 1, secondComma).toFloat();
String storedPhone = line.substring(secondComma + 1, thirdComma);
float rem = line.substring(lastComma + 1).toFloat(); // Extract remaining amount
// Normalize the stored phone number
storedPhone.trim();
storedPhone.replace("+", ""); // Remove '+' if it exists
if (storedPhone.startsWith("0")) {
storedPhone = "254" + storedPhone.substring(1); // Convert "0XXXXXXXXX" to "254XXXXXXXXX"
}
// Check if phone numbers match based on the last 9 digits
if (storedPhone.endsWith(phone) && storedAmount == amount) {
transactionID = storedTransID; // Extract the transaction ID
remainingAmount = rem;
file.close();
return true;
}
}
file.close();
return false; // No match found
}
/*
bool verifyDetails(String phone, float amount, String &transactionID) {
File file = SD.open(pendingOrdersFile, FILE_READ);
if (!file) {
Serial.println("Error opening pending orders file!");
return false;
}
// Normalize the user-entered phone number to a standard format
phone.trim();
phone.replace("+", ""); // Remove '+' if it exists
if (phone.startsWith("0")) {
phone = "254" + phone.substring(1); // Convert "0XXXXXXXXX" to "254XXXXXXXXX"
}
while (file.available()) {
String line = file.readStringUntil('\n');
line.trim();
if (line.length() == 0) continue; // Skip empty lines
// Split line into components
int firstComma = line.indexOf(',');
int secondComma = line.indexOf(',', firstComma + 1);
int thirdComma = line.indexOf(',', secondComma + 1);
String storedTransID = line.substring(0, firstComma);
float storedAmount = line.substring(firstComma + 1, secondComma).toFloat();
String storedPhone = line.substring(secondComma + 1, thirdComma);
// Normalize the stored phone number
storedPhone.trim();
storedPhone.replace("+", ""); // Remove '+' if it exists
if (storedPhone.startsWith("0")) {
storedPhone = "254" + storedPhone.substring(1); // Convert "0XXXXXXXXX" to "254XXXXXXXXX"
}
// Check if phone numbers match based on the last 9 digits
if (storedPhone.endsWith(phone) && storedAmount == amount) {
transactionID = storedTransID; // Extract the transaction ID
file.close();
return true;
}
}
file.close();
return false; // No match found
}
*/
/*
bool verifyDetails(String phone, float amount, String &transactionID) {
File file = SD.open(pendingOrdersFile, FILE_READ);
if (!file) {
Serial.println("Error opening pending orders file!");
return false;
}
while (file.available()) {
String line = file.readStringUntil('\n');
line.trim();
if (line.length() == 0) continue; // Skip empty lines
// Split line into components
int firstComma = line.indexOf(',');
int secondComma = line.indexOf(',', firstComma + 1);
int thirdComma = line.indexOf(',', secondComma + 1);
String storedTransID = line.substring(0, firstComma);
float storedAmount = line.substring(firstComma + 1, secondComma).toFloat();
String storedPhone = line.substring(secondComma + 1, thirdComma);
// Check if phone and amount match
if (storedPhone == phone && storedAmount == amount) {
transactionID = storedTransID; // Extract the transaction ID
file.close();
return true;
}
}
file.close();
return false; // No match found
}
*/
void initSIM800L() {
// Send AT commands to initialize the module
Serial3.println("AT"); // Test AT communication
updateSerial();
Serial3.println("AT+CMGF=1"); // Set SMS to text mode
updateSerial();
Serial3.println("AT+CNMI=1,2,0,0,0"); // Configure module to receive SMS messages automatically
updateSerial();
}
void updateSignalStrength() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis1 >= signalCheckInterval) {
previousMillis1 = currentMillis;
currentSignalLevel = getSignalLevel();
}
}
void updateSerial() {
static String messageBuffer = ""; // Accumulate message data
static unsigned long lastReceivedTime = 0; // Last time data was received
// Handle input from the Serial Monitor and forward to SIM800
while (Serial.available()) {
char inputChar = Serial.read();
Serial3.write(inputChar); // Forward user input to SIM800
}
// Handle input from SIM800 and accumulate the message in messageBuffer
while (Serial3.available()) {
char c = Serial3.read(); // Read a character from SIM800
messageBuffer += c; // Add character to message buffer
lastReceivedTime = millis(); // Update the time when the last character was received
}
// If 200ms have passed since the last received character, process the accumulated message
if (millis() - lastReceivedTime >= 200) {
if (messageBuffer.length() > 0) {
// Debug: Display the full message as it's accumulated
Serial.println("Full Message Received:");
Serial.println(messageBuffer);
// Process the message buffer
processMessageFromBuffer(messageBuffer);
messageBuffer = ""; // Clear the buffer for the next message
}
}
}
void processMessageFromBuffer(String buffer1) {
// Clean up the buffer to ensure no leading or trailing whitespace
buffer1.trim();
// Debug: Print the buffer before processing
Serial.println("Processing Buffer: " + buffer1);
// Ensure the buffer starts with "+CMT" (valid SMS message)
if (buffer1.startsWith("+CMT")) {
// Find the position of the newline after the sender info (metadata)
int firstNewLine = buffer1.indexOf("\n");
if (firstNewLine != -1) {
// Start from the newline and work backwards to find the last quotation mark
int lastQuoteEnd = buffer1.lastIndexOf("\"", firstNewLine);
int lastQuoteStart = buffer1.lastIndexOf("\"", lastQuoteEnd - 1);
// Extract the timestamp between these two quotation marks
if (lastQuoteStart != -1 && lastQuoteEnd != -1 && lastQuoteEnd > lastQuoteStart) {
String timestamp = buffer1.substring(lastQuoteStart + 1, lastQuoteEnd);
// Debug: Display the extracted timestamp
Serial.println("Timestamp: " + timestamp);
// Extract the sender (first set of quotation marks) and message content (after first newline)
int firstQuote = buffer1.indexOf("\"");
int secondQuote = buffer1.indexOf("\"", firstQuote + 1);
if (firstQuote != -1 && secondQuote != -1) {
// Extract the sender's number
String sender = buffer1.substring(firstQuote + 1, secondQuote);
Serial.println("Sender: " + sender);
// Extract the message content starting just after the first newline
String content = buffer1.substring(firstNewLine + 1); // Skip the newline character itself
content.trim(); // Remove extra spaces or newlines from the message content
// Debug: Display the message content
Serial.println("Message Content: " + content);
// Pass sender, content, and timestamp to the filtering function
processFilteredMessage(sender, content, timestamp);
} else {
Serial.println("Error: Could not find sender in the message.");
}
} else {
Serial.println("Error: Could not extract timestamp.");
}
} else {
Serial.println("Error: Newline not found after sender info.");
}
} else {
Serial.println("Error: Buffer does not start with +CMT.");
}
}
void processFilteredMessage(String sender, String content, String timestamp) {
// Define the valid sender numbers (e.g., M-PESA or your specific number)
String validSender1 = "w405543514"; // M-PESA number
String validSender2 = "+254784015186"; // Replace with your trusted sender's number
// Store the message to SD card
// storeMessageInSDCard(sender, content, timestamp);
// Check if the sender matches any of the valid senders
if (sender == validSender1 || sender == validSender2) {
// Check if the message contains the word "received" (case-sensitive)
if (content.indexOf("received") != -1) {
// Debug: Show that the message passed the filter
Serial.println("Message passed filter. Forwarding to processMpesaMessage...");
Serial.println("Sender: " + sender);
Serial.println("Content: " + content);
Serial.println("Timestamp: " + timestamp); // Display the timestamp
// Call the MPESA message processing function
processMpesaMessage(content, timestamp);
} else {
// Debug: Show that the message didn't contain "received"
Serial.println("Message does not contain the keyword 'received'. Ignoring...");
}
} else {
// Debug: Show that the sender was not valid
Serial.println("Sender not valid. Ignoring message...");
}
}
void processMpesaMessage(String messageContent, String timestamp) {
// Debug: Display the full message content
Serial.println("Parsing MPESA Message: " + messageContent);
// Extract transaction ID
int transIDEnd = messageContent.indexOf(" Confirmed."); // Find where the transaction ID ends
String transactionID = messageContent.substring(0, transIDEnd); // Extract transaction ID
String lastFourTransactionID = transactionID.substring(transactionID.length() - 4); // Last 4 characters
// Extract amount received
int amountStart = messageContent.indexOf("Ksh") + 3; // Position after "Ksh"
int amountEnd = messageContent.indexOf(" ", amountStart); // Find the end of the amount
String amountStr = messageContent.substring(amountStart, amountEnd);
float amountReceived = amountStr.toFloat();
// Extract phone number
String phoneNumber = "";
for (int i = 0; i < messageContent.length() - 9; i++) { // Scan up to the 10th last character
if (isDigit(messageContent[i]) && isDigit(messageContent[i + 1]) &&
isDigit(messageContent[i + 2]) && isDigit(messageContent[i + 3]) &&
isDigit(messageContent[i + 4]) && isDigit(messageContent[i + 5]) &&
isDigit(messageContent[i + 6]) && isDigit(messageContent[i + 7]) &&
isDigit(messageContent[i + 8]) && isDigit(messageContent[i + 9])) {
// Found a sequence of 10 digits
int start = messageContent.lastIndexOf(' ', i - 1); // Find the space before the number
int end = messageContent.indexOf(' ', i + 10); // Find the space after the number
phoneNumber = messageContent.substring(start + 1, end);
break;
}
}
// Debug output
Serial.println("Processed MPESA Message:");
Serial.println("Transaction ID (last 4): " + lastFourTransactionID);
Serial.println("Amount Received: " + String(amountReceived, 2));
Serial.println("Phone Number: " + phoneNumber);
Serial.println("Timestamp: " + timestamp);
// You can now log, store, or process the transaction details as needed
logPendingOrder(lastFourTransactionID, amountReceived, phoneNumber, timestamp);
if (isBuyMode) {
// Stop the timer display
isBuyMode = false; // Reset Buy Mode status immediately
lcd.clear();
// Display payment acknowledgment
lcd.setCursor(0, 0);
lcd.print("Payment Received:");
lcd.setCursor(0, 1);
lcd.print("Ksh ");
lcd.print(amountPaid);
delay(2000); // Display for 3 seconds
// Prompt user to press the dispense button
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Press button to");
lcd.setCursor(0, 1);
lcd.print(" dispense");
// Wait for dispense button press
waitForButtonPress();
// Proceed to dispensing
lcd.clear();
dispenseLiquid(transactionID, amountPaid);
// Return to the main menu
state = START;
}
}
void logPendingOrder(String transID, float amount, String phoneNumber, String timedate) {
// Open the file for append (write at the end of the file)
SdFile file;
if (file.open(pendingOrdersFile.c_str(), O_WRONLY | O_CREAT | O_APPEND)) {
// Write the transaction details to the file
file.print(transID);
file.print(",");
file.print(amount);
file.print(",");
file.print(phoneNumber);
file.print(",");
file.print((amount / ratePerLiter) * 1000, 2); // Liquid amount in ML
file.print(",");
file.print(timedate); // Timestamp for simplicity
file.print(",");
file.print(amount); // Remaining amount in Ksh (initially equal to total amount)
file.println(); // Add a newline for each order
file.close(); // Close the file
// Print to Serial Monitor for debugging
Serial.println("Pending order logged:");
Serial.println("Transaction ID: " + transID);
Serial.println("Amount Paid: " + String(amount));
} else {
// Error handling
Serial.println("Error: Unable to open file for logging pending order.");
}
}
/*
void logPendingOrder( String transID, float amount, String phoneNumber, String timedate) {
File file = SD.open(pendingOrdersFile, FILE_WRITE);
if (file) {
file.print(transID);
file.print(",");
file.print(amount);
file.print(",");
file.print(phoneNumber);
file.print(",");
file.print(amount); // Remaining amount in Ksh (initially equal to total amount)
file.print(",");
file.print((amount / ratePerLiter)*1000, 2); // Liquid amount in ML
file.print(",");
file.print(timedate); // Timestamp for simplicity
file.close();
Serial.print("Transaction ID: ");
Serial.println(transID);
Serial.print("Amount Paid: ");
Serial.println(amount);
}
}
*/
void displayRateAndMessage() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= scrollDelay) {
previousMillis = currentMillis;
// Display rate per liter
lcd.setCursor(0, 0);
lcd.print("Rate:KSH");
lcd.print((int)ratePerLiter);
lcd.print("/L ");
// Display signal strength graphic
lcd.setCursor(14, 0); // Adjust as needed to fit LCD
lcd.write(6);
lcd.setCursor(15, 0); // Adjust as needed to fit LCD
lcd.write(currentSignalLevel); // Write appropriate signal level
// Prepare for scrolling
lcd.setCursor(0, 1);
String scrollingText = message + " " + message; // Duplicate the message with a space for smooth looping
int effectiveIndex = scrollIndex % messageLength;
// Display a portion of the scrolling text
lcd.print(scrollingText.substring(effectiveIndex, effectiveIndex + lcdCols));
// Increment scroll index
scrollIndex++;
}
}
// Function to query SIM800 for signal strength and map to levels
int getSignalLevel() {
Serial3.println("AT+CSQ");
delay(100); // Wait for response
String response = "";
while (Serial3.available()) {
char c = Serial3.read();
response += c;
}
Serial.println("Processing Signal Check: " + response);
int startIndex = response.indexOf("+CSQ: ");
if (startIndex != -1) {
startIndex += 6;
int endIndex = response.indexOf(",", startIndex);
String rssiStr = response.substring(startIndex, endIndex);
int rssi = rssiStr.toInt();
// Map RSSI to signal level (0-5)
if (rssi <= 4) return 0; // No signal
if (rssi <= 9) return 1; // Low signal
if (rssi <= 14) return 2; // Medium signal
if (rssi <= 19) return 3; // Strong signal
if (rssi <= 24) return 4; // Very strong signal
return 5; // Full signal
}
return 0; // Default to no signal if parsing fails
}
bool hasPendingOrders() {
SdFile file;
// Open the file for reading
if (file.open(pendingOrdersFile.c_str(), O_RDONLY)) {
// Check if the file contains any data
if (file.available()) {
file.close();
return true; // Pending orders exist
}
file.close();
}
return false; // No pending orders or file does not exist
}
/*
bool hasPendingOrders() {
File file = SD.open(pendingOrdersFile);
if (file) {
if (file.available()) {
file.close();
return true;
}
file.close();
}
return false;
}
*/
void enterAdminMode() {
lcd.clear();
lcd.print("Enter PIN:");
String inputPIN = "";
char key;
unsigned long lastInputTime = millis(); // Track time of last input
while (true) {
lcd.setCursor(0, 1);
// Display masked PIN
for (int i = 0; i < inputPIN.length(); i++) {
lcd.print("*"); // Show * for entered characters
}
for (int i = inputPIN.length(); i < 4; i++) {
lcd.print("_"); // Show underscores for remaining positions
}
// Check for timeout
if (millis() - lastInputTime > 15000) { // 1 minute timeout
lcd.clear();
lcd.print(" Timeout!");
delay(2000); // Display timeout message for 2 seconds
return; // Exit to the start menu
}
key = keypad.getKey();
if (key) {
lastInputTime = millis(); // Reset timeout timer on input
if (key >= '0' && key <= '9' && inputPIN.length() < 4) {
// Add digit to PIN
inputPIN += key;
} else if (key == '*') {
if (inputPIN.length() == 0) {
// Exit admin mode if no PIN is entered
lcd.clear();
lcd.print("Exiting...");
delay(1000);
return; // Exit the function
} else {
// Remove the last character if '*' is pressed
inputPIN.remove(inputPIN.length() - 1);
}
} else if (key == '#' && inputPIN.length() == 4) {
// Proceed if '#' is pressed and PIN is complete
if (inputPIN == adminPIN) {
lcd.clear();
lcd.print("Access Granted");
delay(1000);
adminMenu();
} else {
lcd.clear();
lcd.print("Invalid PIN");
delay(2000);
}
return; // Exit the function
}
}
delay(100); // Simple debounce
}
}
void dispenseLiquid(String transID, float amount) {
// Calculate total liquid to dispense
float totalLiquidAmount = (amount / ratePerLiter) * 1000; // Convert amount to ml
float dispenseRate = pumpFlowRate; // ml/s
float dispensedLiquid = 0; // Track dispensed liquid
float paidAmount = 0;
float RemainingAmount;
unsigned long previousMillis = millis(); // Store the start time
// writeActiveOrder(transID);
//Create the Active txt file to store updated liquid amount
digitalWrite(relayPin, LOW); // Start dispensing
// Enable the Watchdog Timer with a 4-second timeout
wdt_enable(WDTO_4S);
while (dispensedLiquid < totalLiquidAmount) {
wdt_reset(); // Reset the Watchdog Timer to avoid a system reset
if (isButtonPressed()) { // Check if button is pressed to pause
digitalWrite(relayPin, HIGH); // Stop the pump
lcd.clear();
lcd.print("Paused. Press");
lcd.setCursor(0, 1);
lcd.print("Button To Resume");
wdt_reset(); // Reset the Watchdog Timer to avoid a system reset
while (true) {
wdt_reset(); // Reset the Watchdog Timer to avoid a system reset
/*
if (keypad.getKey() == '*') { // Cancel dispensing
wdt_disable();
RemainingAmount = totalLiquidAmount - dispensedLiquid;
updateDispense(RemainingAmount); // Save progress
appendToPending();
Serial.println("Dispense canceled by user.");
return; // Exit the function
}
*/
if (isButtonPressed() || keypad.getKey() == '#') { // Resume dispensing
wdt_reset(); // Reset the Watchdog Timer to avoid a system reset
lcd.clear();
lcd.print("Resuming...");
delay(500); // Show message for 1 second
digitalWrite(relayPin, LOW); // Start the pump again
break; // Exit the pause loop
}
}
}
unsigned long currentMillis = millis();
unsigned long elapsedMillis = currentMillis - previousMillis; // Time since last update
// Calculate dispensed liquid since the last update
dispensedLiquid = dispenseRate * (elapsedMillis / 1000.0); // Convert elapsed time to seconds
paidAmount = (dispensedLiquid / 1000.0) * ratePerLiter; // Amount paid for dispensed volume
// Ensure dispensedLiquid doesn't exceed totalLiquidAmount
if (dispensedLiquid >= totalLiquidAmount) {
dispensedLiquid = totalLiquidAmount;
break;
}
RemainingAmount = amount - paidAmount;
// updateDispense(RemainingAmount);
// Update the LCD display with the dispensed liquid
lcd.setCursor(0, 0);
lcd.print(" Ksh: ");
lcd.print((int)paidAmount);
lcd.print("/");
lcd.print((int)amount);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("Disp: ");
lcd.print((int)dispensedLiquid);
lcd.print("/");
lcd.print((int)totalLiquidAmount);
lcd.print(" ml");
delay(200); // Short delay for updating the display
}
digitalWrite(relayPin, HIGH); // Stop dispensing
lcd.clear();
lcd.print("Dispense Complete");
//delay(2000);
RemainingAmount = 0;
//updateDispense(RemainingAmount);
//Remove the active order file
delay (500);
//appendToPending();
// Clear the pending order and mark it as completed
delay (500);
moveOrderToComplete(transID);
// Reset the Watchdog Timer to its default state
delay (500);
wdt_disable();
state = START; // Return to the START state
}
void writeActiveOrder(String transID) {
SdFile pendingFile;
SdFile activeFile;
if (!pendingFile.open(pendingOrdersFile.c_str(), O_READ)) {
Serial.println("Error: Unable to open pending orders file.");
return;
}
if (!activeFile.open("ACTIVE.TXT", O_WRITE | O_CREAT | O_TRUNC)) {
Serial.println("Error: Unable to open active file.");
pendingFile.close();
return;
}
char line[256];
while (pendingFile.fgets(line, sizeof(line))) {
String currentLine = String(line);
currentLine.trim();
if (currentLine.startsWith(transID)) {
activeFile.println(currentLine);
Serial.println("Active order created: " + currentLine);
break;
}
}
pendingFile.close();
activeFile.close();
}
/*
void updateDispense(float newRemainingAmount) {
SdFile activeFile;
if (!activeFile.open("ACTIVE.TXT", O_RDWR)) {
Serial.println("Error: Unable to open active file.");
return;
}
char line[256];
if (activeFile.fgets(line, sizeof(line))) {
String currentLine = String(line);
currentLine.trim();
int lastCommaIndex = currentLine.lastIndexOf(',');
String updatedLine = currentLine.substring(0, lastCommaIndex) + "," + String(newRemainingAmount, 2);
activeFile.seek(0); // Reset pointer to start of file
activeFile.truncate(); // Clear file contents
activeFile.println(updatedLine);
Serial.println("Active order updated: " + updatedLine);
}
activeFile.close();
}
*/
void checkActiveOrder() {
File activeFile = SD.open("ACTIVE.TXT", FILE_READ);
if (!activeFile) {
Serial.println("No active order to check.");
return;
}
// Read the active order file
String orderDetails = activeFile.readStringUntil('\n');
orderDetails.trim(); // Remove any leading/trailing whitespace
activeFile.close();
if (orderDetails.length() == 0) {
Serial.println("Active order file is empty.");
SD.remove("ACTIVE.TXT"); // Ensure the file is removed if empty
return;
}
// Append the order details to pending orders
appendToPending();
Serial.println("Recovered active order: " + orderDetails);
}
void appendToPending() {
// Open the active order file
File activeFile = SD.open("ACTIVE.TXT", FILE_READ);
if (!activeFile) {
Serial.println("Error: Unable to open active order file.");
return;
}
// Read the active order details (assuming only one line exists)
String orderDetails = activeFile.readStringUntil('\n');
orderDetails.trim(); // Remove any leading/trailing whitespace
activeFile.close();
if (orderDetails.length() == 0) {
Serial.println("Active order file is empty.");
return;
}
// Extract the transaction ID from the active order line
int firstComma = orderDetails.indexOf(',');
String transID = orderDetails.substring(0, firstComma);
// Open the pending orders file for reading
SdFile pendingFile;
if (!pendingFile.open(pendingOrdersFile.c_str(), O_READ)) {
Serial.println("Error: Unable to open pending orders file.");
return;
}
// Temporary file for rewriting the pending orders file
SdFile tempFile;
if (!tempFile.open("temp.txt", O_WRITE | O_CREAT | O_TRUNC)) {
Serial.println("Error: Unable to create temporary file.");
pendingFile.close();
return;
}
char line[256];
bool orderAppended = false;
// Process each line in the pending orders file
while (pendingFile.fgets(line, sizeof(line))) {
String currentLine = String(line);
currentLine.trim(); // Remove leading and trailing whitespace
if (currentLine.startsWith(transID) && !orderAppended) {
// Do not write this line back to the temporary file (skip it)
orderAppended = true;
} else {
// Write all other lines to the temporary file
tempFile.println(currentLine);
}
}
// Append the active order to the temporary file
tempFile.println(orderDetails);
Serial.println("Order appended to pending: " + orderDetails);
// Close files
pendingFile.close();
tempFile.close();
// Replace the original pending orders file with the temporary file
if (SD.exists(pendingOrdersFile.c_str())) {
if (!SD.remove(pendingOrdersFile.c_str())) {
Serial.println("Error: Unable to delete original pending orders file.");
return;
}
}
if (!SD.rename("temp.txt", pendingOrdersFile.c_str())) {
Serial.println("Error: Unable to rename temporary file to pending orders file.");
return;
}
// Optionally remove the active order file if no longer needed
SD.remove("ACTIVE.TXT");
Serial.println("Active order file cleared after appending.");
}
void moveOrderToComplete(String transID) {
SdFile pendingFile;
SdFile completeFile;
// Open the pending orders file for reading
if (!pendingFile.open(pendingOrdersFile.c_str(), O_READ)) {
Serial.println("Error: Unable to open pending orders file.");
return;
}
// Open the completed orders file for appending
if (!completeFile.open(completeOrdersFile.c_str(), O_WRITE | O_APPEND | O_CREAT)) {
Serial.println("Error: Unable to open completed orders file.");
pendingFile.close();
return;
}
char line[256];
bool transactionMoved = false;
// Temporary file for rewriting the pending orders file
SdFile tempFile;
if (!tempFile.open("temp.txt", O_WRITE | O_CREAT | O_TRUNC)) {
Serial.println("Error: Unable to create temporary file.");
pendingFile.close();
completeFile.close();
return;
}
// Process each line in the pending orders file
while (pendingFile.fgets(line, sizeof(line))) {
String currentLine = String(line);
currentLine.trim(); // Remove leading and trailing whitespace
if (currentLine.startsWith(transID) && !transactionMoved) {
// Write the matching line to the completed orders file
completeFile.println(currentLine);
Serial.println("Transaction moved: " + currentLine);
transactionMoved = true;
} else {
// Write all other lines to the temporary file
tempFile.println(currentLine);
}
}
// Close all files
pendingFile.close();
completeFile.close();
tempFile.close();
// Replace the original pending orders file with the temporary file
if (SD.exists(pendingOrdersFile.c_str())) {
if (!SD.remove(pendingOrdersFile.c_str())) {
Serial.println("Error: Unable to delete original pending orders file.");
return;
}
}
if (!SD.rename("temp.txt", pendingOrdersFile.c_str())) {
Serial.println("Error: Unable to rename temporary file to pending orders file.");
return;
}
if (transactionMoved) {
Serial.println("Transaction successfully moved to completed orders.");
} else {
Serial.println("Transaction ID not found in pending orders.");
}
}
void adminMenu() {
const char* menuItems[] = {
"1. View Pending",
"2. Set Price",
"3. Measure Flow",
"4. View FlowRate",
"5. Change Admin PIN",
"6. Price Dispense",
};
const int menuLength = sizeof(menuItems) / sizeof(menuItems[0]);
int currentIndex = 0;
while (true) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("> ");
lcd.print(menuItems[currentIndex]);
lcd.setCursor(0, 1);
lcd.print(menuItems[(currentIndex + 1) % menuLength]);
char key = keypad.getKey();
if (key == 'A') { // Up key
currentIndex = (currentIndex - 1 + menuLength) % menuLength;
} else if (key == 'B') { // Down key
currentIndex = (currentIndex + 1) % menuLength;
} else if (key == '#') { // Enter key
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Selected:");
lcd.setCursor(0, 1);
lcd.print(menuItems[currentIndex]);
delay(1000);
switch (currentIndex) {
case 0:
viewPendingOrders();
break;
case 1:
setPricePerLiter();
break;
case 2:
measureFlowRate();
break;
case 3:
displayFlowRate();
break;
case 4:
changeAdminPIN();
break;
case 5:
dispenseByPrice();
break;
}
} else if (key == '*') { // Back key
return;
}
delay(200); // Simple debounce
}
}
void dispenseByPrice() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter price: Ksh:");
String priceStr = "";
while (true) {
char key = keypad.getKey();
if (key) {
if (key == '#') { // Confirm input
float price = priceStr.toFloat();
if (price <= 0) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid Price");
delay(2000);
return;
}
float volumeToDispenseLiters = price / ratePerLiter;
float volumeToDispenseMl = volumeToDispenseLiters * 1000; // Convert to milliliters
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Amount: Ksh ");
lcd.print(price);
lcd.setCursor(0, 1);
lcd.print("Dispense: ");
lcd.print((int)volumeToDispenseMl);
lcd.print(" ml");
delay(2000); // Brief pause to display info
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Press Button");
lcd.setCursor(0, 1);
lcd.print("*, to cancel");
// Wait for button press or cancellation
while (true) {
detectButtonPress(); // Continuously check for button presses
char key = keypad.getKey();
if (key == '*') { // Cancel input
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cancelled");
delay(2000);
return;
} else if (key == '#') { // Proceed to dispensing
break;
} else if (shortPressDetected) { // Button press detected
shortPressDetected = false; // Reset the flag
break; // Proceed to dispensing
}
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dispensing...");
// Dispensing process
unsigned long dispenseTime = (volumeToDispenseMl / pumpFlowRate) * 1000; // Time in milliseconds
unsigned long startTime = millis();
float dispensedVolume = 0;
float paidAmount = 0;
digitalWrite(relayPin, LOW);
while (millis() - startTime < dispenseTime) {
unsigned long elapsed = millis() - startTime;
dispensedVolume = (elapsed / 1000.0) * pumpFlowRate; // Volume dispensed so far
paidAmount = (dispensedVolume / 1000.0) * ratePerLiter; // Amount paid for dispensed volume
// Update display
lcd.setCursor(0, 0);
lcd.print(" Ksh: ");
lcd.print((int)paidAmount);
lcd.print("/");
lcd.print((int)price);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("Disp: ");
lcd.print((int)dispensedVolume);
lcd.print("/");
lcd.print((int)volumeToDispenseMl);
lcd.print(" ml");
delay(200); // Refresh rate for display
}
digitalWrite(relayPin, HIGH);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dispense complete");
lcd.setCursor(0, 1);
lcd.print("Price: Ksh ");
lcd.print(price);
delay(3000);
return;
} else if (key == '*') { // Cancel input
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cancelled");
delay(2000);
return;
} else if (key == 'D') { // Decimal point
priceStr += '.';
lcd.setCursor(0, 1);
lcd.print(priceStr);
} else { // Append digit
priceStr += key;
lcd.setCursor(0, 1);
lcd.print(priceStr);
}
}
}
}
void viewPendingOrders() {
lcd.clear();
lcd.setCursor(0, 0);
SdFile file;
// Open the pending orders file for reading
if (!file.open(pendingOrdersFile.c_str(), O_RDONLY)) {
lcd.print("No Pending Orders");
delay(2000);
return;
}
// Temporary variables for navigation
char currentLine[100]; // Buffer to hold the current line
int currentIndex = 0;
int totalOrders = 0;
// Count total orders in the file
while (file.fgets(currentLine, sizeof(currentLine))) {
totalOrders++;
}
file.close();
// Reopen the file to navigate
if (!file.open(pendingOrdersFile.c_str(), O_RDONLY)) {
lcd.print("Error Reading File");
delay(2000);
return;
}
while (true) {
// Reset the file pointer to the start
file.rewind();
// Navigate to the desired line
for (int i = 0; i <= currentIndex && file.fgets(currentLine, sizeof(currentLine)); i++) {
// Just loop until the desired line
}
// Parse the current line
String line = String(currentLine);
line.trim(); // Remove extra spaces or newlines
int separatorIndex = line.indexOf(',');
if (separatorIndex != -1) {
String transactionID = line.substring(0, separatorIndex);
String amountPaid = line.substring(separatorIndex + 1, line.indexOf(',', separatorIndex + 1));
// Display the transaction details on the LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Txn: ");
lcd.print(transactionID);
lcd.setCursor(0, 1);
lcd.print("Amt: ");
lcd.print(amountPaid);
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid Data");
}
// Handle navigation and exit
char key = keypad.getKey();
if (key == 'A' && currentIndex > 0) {
currentIndex--; // Scroll up
} else if (key == 'B' && currentIndex < totalOrders - 1) {
currentIndex++; // Scroll down
} else if (key == '*') {
break; // Exit on *
}
delay(100); // Simple debounce
}
file.close();
lcd.clear();
lcd.print("Exiting Orders...");
delay(1000);
}
void changeAdminPIN() {
while (true) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter New PIN:");
String newPIN = "";
while (newPIN.length() < 4) {
char key = keypad.getKey();
if (key >= '0' && key <= '9') {
newPIN += key;
lcd.setCursor(newPIN.length() - 1, 1);
lcd.print("*");
} else if (key == '*') {
return;
}
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Re-enter New PIN:");
String reenteredPIN = "";
while (reenteredPIN.length() < 4) {
char key = keypad.getKey();
if (key >= '0' && key <= '9') {
reenteredPIN += key;
lcd.setCursor(reenteredPIN.length() - 1, 1);
lcd.print("*");
} else if (key == '*') {
return;
}
}
if (newPIN == reenteredPIN) {
adminPIN = newPIN;
//writeStringToEEPROM(EEPROM_PIN_ADDR, adminPIN);
writeConfigToSD(ratePerLiter, pumpFlowRate, adminPIN); // Save to SD card
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("PIN Changed");
delay(2000);
return;
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pins do not match");
delay(2000);
// Go back to the start of the loop to re-enter PIN
}
}
}
void setPricePerLiter() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Price per L:");
String priceStr = "";
while (true) {
char key = keypad.getKey();
if (key) {
if (key == '#') {
ratePerLiter = priceStr.toFloat();
//writeFloatToEEPROM(EEPROM_RATE_ADDR, ratePerLiter);
writeConfigToSD(ratePerLiter, pumpFlowRate, adminPIN); // Save new PIN to SD card
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Price Updated");
delay(2000);
return;
} else if (key == '*') {
return;
} else {
priceStr += key;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Price per L:");
lcd.setCursor(0, 1);
lcd.print(priceStr);
}
}
}
}
void measureFlowRate() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter time (s):");
String timeStr = "";
while (true) {
char key = keypad.getKey();
if (key) {
if (key == '#') {
int dispenseTime = timeStr.toInt();
if (dispenseTime < 1 || dispenseTime > 60) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid Time");
delay(2000);
return;
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Ready to dispense");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Press Dispense");
lcd.setCursor(0, 1);
lcd.print("button to start");
// Wait for the dispense button to be pressed
waitForButtonPress();
if (click) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dispensing...");
click = false;
unsigned long startTime = millis();
digitalWrite(relayPin, LOW);
while (millis() - startTime < dispenseTime * 1000) {
unsigned long elapsed = millis() - startTime;
lcd.setCursor(0, 1);
lcd.print("Time: ");
lcd.print(elapsed / 1000);
lcd.print(" s ");
}
digitalWrite(relayPin, HIGH);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dispense complete");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter volume:");
String volumeStr = "";
while (true) {
char key = keypad.getKey();
if (key) {
if (key == '#') {
float volume = volumeStr.toFloat();
if (volume > 0) {
pumpFlowRate = volume / dispenseTime;
//writeFloatToEEPROM(EEPROM_FLOW_ADDR, pumpFlowRate);
writeConfigToSD(ratePerLiter, pumpFlowRate, adminPIN); // Save to SD card
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Flow Rate Set");
delay(2000);
displayFlowRate(); // Display the newly set flow rate
return;
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid Volume");
delay(2000);
return;
}
} else if (key == '*') {
return;
} else if (key == 'D') {
volumeStr += '.';
lcd.setCursor(0, 1);
lcd.print(volumeStr);
} else {
volumeStr += key;
lcd.setCursor(0, 1);
lcd.print(volumeStr);
}
}
}
}
} else if (key == '*') {
return;
} else {
timeStr += key;
lcd.setCursor(0, 1);
lcd.print(timeStr);
}
}
}
}
void displayFlowRate() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Flow Rate:");
lcd.setCursor(0, 1);
lcd.print(pumpFlowRate);
lcd.print(" ml/s");
delay(3000); // Display the flow rate for 3 seconds
}
void detectButtonPress() {
static bool lastButtonState = LOW; // Track previous button state
bool currentButtonState = digitalRead(buttonPin); // Read the button pin
if (currentButtonState == HIGH && lastButtonState == LOW) {
// Button was pressed (rising edge)
shortPressDetected = true; // Set the flag
}
lastButtonState = currentButtonState; // Update the last state
}
void waitForButtonPress() {
Serial.println("Waiting for button press...");
click = false; // Initialize click to false
while (!click) { // Continue looping until click is true
int buttonState = digitalRead(buttonPin);
if (buttonState == LOW && !isPressing) {
pressedTime = millis();
isPressing = true;
Serial.println("Button is pressed"); // Debug statement
}
if (buttonState == HIGH && isPressing) {
isPressing = false;
releasedTime = millis();
long pressDuration = releasedTime - pressedTime;
if (pressDuration < SHORT_PRESS_TIME) {
Serial.println("A short press is detected"); // Debug statement
click = true; // Set click to true when a short press is detected
}
}
}
Serial.println("Button press detected, exiting waitForButtonPress"); // Debug statement
}
bool isButtonPressed() {
static bool isPressing = false;
static unsigned long pressedTime = 0;
int buttonState = digitalRead(buttonPin);
if (buttonState == LOW && !isPressing) {
pressedTime = millis();
isPressing = true;
}
if (buttonState == HIGH && isPressing) {
isPressing = false;
unsigned long releasedTime = millis();
long pressDuration = releasedTime - pressedTime;
if (pressDuration < SHORT_PRESS_TIME) {
return true; // A short press detected
}
}
return false; // No press detected
}
void writeConfigToSD(float ratePerLiter, float pumpFlowRate, String adminPIN) {
char key = 'K'; // Encryption key for XOR
// Convert values to strings for encryption
String rateStr = String(ratePerLiter);
String flowRateStr = String(pumpFlowRate);
// Encrypt each value
String encryptedRate = xorEncryptDecrypt(rateStr, key);
String encryptedFlowRate = xorEncryptDecrypt(flowRateStr, key);
String encryptedPIN = xorEncryptDecrypt(adminPIN, key);
SdFile configFile;
// Open the configuration file for writing
if (configFile.open("CONFIG.TXT", O_WRITE | O_CREAT | O_TRUNC)) {
configFile.println(encryptedRate.c_str());
configFile.println(encryptedFlowRate.c_str());
configFile.println(encryptedPIN.c_str());
Serial.print("Encrypted Rate per Liter: ");
Serial.println(encryptedRate);
Serial.print("Encrypted Pump Flow Rate: ");
Serial.println(encryptedFlowRate);
Serial.print("Encrypted Admin PIN: ");
Serial.println(encryptedPIN);
configFile.close();
Serial.println("Encrypted configuration saved to SD card.");
} else {
Serial.println("Error opening CONFIG.TXT for writing.");
}
}
String xorEncryptDecrypt(String data, char key) {
String result = data;
for (int i = 0; i < data.length(); i++) {
result[i] = data[i] ^ key; // XOR each character with the key
}
return result;
}
void readConfigFromSD() {
char key = 'K'; // Decryption key for XOR
SdFile configFile;
if (SD.exists("CONFIG.TXT")) {
if (configFile.open("CONFIG.TXT", O_READ)) {
Serial.println("CONFIG.TXT found. Reading encrypted data...");
char buffer[64]; // Buffer to hold data from the file
// Read the encrypted rate
configFile.fgets(buffer, sizeof(buffer));
String encryptedRate = String(buffer);
encryptedRate.trim();
// Read the encrypted flow rate
configFile.fgets(buffer, sizeof(buffer));
String encryptedFlowRate = String(buffer);
encryptedFlowRate.trim();
// Read the encrypted PIN
String encryptedPIN = "";
while (configFile.available()) {
char c = configFile.read();
if (c == '\n' || c == '\r') break; // Stop at end of line
encryptedPIN += c;
}
configFile.close();
// Decrypt the values
String decryptedRate = xorEncryptDecrypt(encryptedRate, key);
String decryptedFlowRate = xorEncryptDecrypt(encryptedFlowRate, key);
String decryptedPIN = xorEncryptDecrypt(encryptedPIN, key);
// Convert decrypted values to appropriate types
ratePerLiter = decryptedRate.toFloat();
pumpFlowRate = decryptedFlowRate.toFloat();
adminPIN = decryptedPIN;
// Print decrypted values to Serial for confirmation
Serial.print("Decrypted Rate per Liter: ");
Serial.println(ratePerLiter);
Serial.print("Decrypted Pump Flow Rate: ");
Serial.println(pumpFlowRate);
Serial.print("Decrypted Admin PIN: ");
Serial.println(adminPIN);
} else {
Serial.println("Error opening CONFIG.TXT for reading.");
}
} else {
Serial.println("CONFIG.TXT not found. Creating default file.");
writeConfigToSD(100.0, 23.0, "0000"); // Default values
}
}
void initializeSDCard() {
const int retryDelay = 2000; // Time to wait between retries (in milliseconds)
while (!SD.begin(chipSelect, SPI_FULL_SPEED)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SD init failed!");
lcd.setCursor(0, 1);
lcd.print("Retrying...");
delay(retryDelay); // Wait before retrying
}
}
void logError(String errorMessage) {
File errorFile = SD.open("ERROR_LOG.TXT", FILE_WRITE);
if (errorFile) {
errorFile.println(errorMessage);
errorFile.close();
Serial.println("Error logged: " + errorMessage);
} else {
Serial.println("Error: Unable to write to error log.");
}
}