// Include libraries and user-defined header files
#include <LiquidCrystal_I2C.h> // Library for I2C LCD
#include "GenericQueue.h" // User-defined generic queue implementation
#include "ADebouncer.h" // User-defined debouncing library
// Initialize the I2C LCD object with the specified address and dimensions
LiquidCrystal_I2C myLCD(0x27, 20, 4);
// Define pin numbers for LEDs
#define COOKING_LED_PIN 12
#define READY_LED_PIN 11
#define NEW_LED_PIN 10
// Define debounce period and blink period
#define DEBOUNCE_PERIOD 10
#define BLINK_PERIOD 500
// Define the number of input pins and their corresponding pin numbers
#define NUMBER_OF_INPUTS 4
const int inputPin[NUMBER_OF_INPUTS] = {9, 8, 7, 6};
// Create debouncer objects for each input pin
ADebouncer debouncer[NUMBER_OF_INPUTS];
class Timer {
private:
uint32_t _duration; // Stores the duration of the timer
uint32_t _startTime; // Stores the start time of the timer
uint32_t _elapsed; // Stores the elapsed time of the timer
bool _state; // Represents the state of the timer (e.g., running or expired)
bool _enable; // Indicates whether the timer is enabled
public:
bool &state; // Reference to the state of the timer
uint32_t &elapsed; // Reference to the elapsed time of the timer
uint32_t &duration; // Reference to the duration of the timer
// Constructor initializes the references to the state, elapsed, and duration
Timer(): state(_state), elapsed(_elapsed), duration(_duration) {}
// Set the duration of the timer
void timeDelay(uint32_t duration) {
_duration = duration;
}
// Activate or deactivate the timer based on the 'enable' parameter
// Returns the state of the timer
bool timerOn(bool enable) {
if (enable) {
uint32_t currTime = millis();
if (!_enable) {
_startTime = currTime;
}
if (!_state) {
_elapsed = currTime - _startTime;
if (_elapsed >= _duration) {
_elapsed = _duration;
state = true;
}
}
} else {
_state = false;
}
_enable = enable;
return state;
}
};
// Class representing a menu with a name and duration
class Menu {
public:
String name; // Name of the menu
uint32_t duration; // Duration of the menu in milliseconds
public:
// Default constructor
Menu() {
name = ""; // Initialize name to an empty string
duration = 1000UL; // Initialize duration to 1000 milliseconds
}
// Parameterized constructor
Menu(String const& name, uint32_t duration) {
this->name = name; // Set the name to the provided value
this->duration = duration; // Set the duration to the provided value
}
};
Menu myMenu[] = { Menu("Potato", 5000UL)
, Menu("Sandwich", 4000UL)
, Menu("Burger", 3000UL)
, Menu("Chicken", 2000UL)
};
// Define the maximum queue size
const int maxQueue = 36;
// Create a generic queue of Menu objects with the specified maximum size
GenericQueue<Menu> order(maxQueue);
// Define the initial size of the queue
const int initialAddQ = 4;
// Define an array of menu names
const String menuList[4] = {"OInQ.: ", "NextQ: ", "CurrQ: ", "Cooki: "};
// Initialize an array to hold LCD display content
String lcdBuffer[4];
// Define timers for cooking, blinking, and LCD update intervals
Timer cookingTimer;
Timer blinkTimer;
Timer intervalLCD;
// Variable to hold remaining time for cooking
float remainingTime;
// Variable to store the state of blinking
bool blinkState;
// Function prototypes
void Cooking();
void RaiseOrder();
void QueueChanged(Menu m, QueueEventArgs e);
// Setup function
void setup() {
Serial.begin(115200); // Start serial communication
myLCD.init(); // Initialize the LCD
myLCD.backlight(); // Turn on the backlight
// Set input pins to INPUT_PULLUP and initialize debouncers
for (int index = 0; index < sizeof(inputPin) / sizeof(int); index++) {
pinMode(inputPin[index], INPUT_PULLUP);
debouncer[index].mode(INSTANT, DEBOUNCE_PERIOD, HIGH);
}
pinMode(COOKING_LED_PIN, OUTPUT); // Set COOKING_LED_PIN as output
pinMode(LED_BUILTIN, OUTPUT); // Set LED_BUILTIN as output
order.onStateChanged(QueueChanged); // Set the event handler for queue state changes
blinkTimer.timeDelay(BLINK_PERIOD); // Set the time delay for blinking
intervalLCD.timeDelay(100); // Set the time delay for LCD update
ReadQueue(); // Read the queue
Cooking(); // Start the cooking process
LCDDisplay(); // Update the LCD display
delay(1000); // Delay for 1 second
}
void loop() {
// Control blinking based on timer
blinkTimer.timerOn(!blinkTimer.state);
if (blinkTimer.state) blinkState = !blinkState;
// Perform order processing
RaiseOrder();
// Update LCD Buffer by reading the queue
ReadQueue();
// Perform cooking-related tasks
Cooking();
// Update LCD display at regular intervals
if (intervalLCD.timerOn(!intervalLCD.state)) {
LCDDisplay();
}
// Control the built-in LED based on blink state
digitalWrite(LED_BUILTIN, blinkState);
}
// Process incoming orders based on button presses
void RaiseOrder() {
for (int index = 0 ; index < sizeof(inputPin) / sizeof(int) ; index++) {
// Debounce button input
debouncer[index].debounce(!digitalRead(inputPin[index]));
// Check for rising edge (button press) and enqueue the corresponding item
if (debouncer[index].rising()) {
order.enqueue(myMenu[index]);
}
}
}
// Simulate cooking process based on incoming orders
void Cooking() {
int count = order.count;
// If there are pending orders
if (!order.isEmpty() ) {
// Control cooking LED based on blink state
digitalWrite(COOKING_LED_PIN, blinkState);
// Get the current order from the queue
Menu currOrder = order.peek();
// Start the cooking timer for the current order
cookingTimer.timeDelay(currOrder.duration);
cookingTimer.timerOn(true);
// If cooking timer is active
if (cookingTimer.state) {
// Turn off the cooking timer and dequeue the completed order
cookingTimer.timerOn(false);
order.dequeue();
}
// Calculate the remaining time for the current order
remainingTime = (cookingTimer.duration - cookingTimer.elapsed) * 0.001;
}
// If no pending orders
else {
remainingTime = 0.0;
// Turn off the cooking LED
digitalWrite(COOKING_LED_PIN, 0);
}
}
// Update LCD display with current and next orders from the queue
void ReadQueue() {
Menu currOrder;
Menu nexOrder;
int count = order.count;
// Get Current Queue
if (count > 0) {
currOrder = order.peek();
}
// Get Next Queue
if (count > 1) {
nexOrder = order[1];
}
// Prepare LCD buffer with order information
lcdBuffer[0] = PadMid(String(count), "(MQ" + String(order.count) + ")", 13, ' '); // Total order count
lcdBuffer[1] = PadMid(nexOrder.name, (order.count > 1 ? " " + String(nexOrder.duration * 0.001, 1) + "s" : "-"), 13, ' '); // Next order name and duration
lcdBuffer[2] = PadMid(currOrder.name, (order.count > 0 ? " " + String(currOrder.duration * 0.001, 1) + "s" : "-"), 13, ' '); // Current order name and duration
lcdBuffer[3] = PadMid(order.count > 0 ? (blinkState == 1 ? currOrder.name + " " : "") : " ", order.count > 0 ? String(remainingTime, 1) + "s" : "-", 13, ' '); // Display remaining time if an order is being processed
}
// Handle queue state changes and log events
void QueueChanged(QueueEventArgs e, Menu m) {
switch (e.state) {
case ENQUEUE:
// Notify new order added
// newTimer.TimerOff(true);
Serial.println("enqueue: " + String(m.name) + " New Order!");
break;
case DEQUEUE:
// Notify order ready for serving
// readyTimer.TimerOff(true);
Serial.println("dequeue: " + String(m.name) + " Ready to serve!");
break;
};
}
// Display menu and buffer information on the LCD
void LCDDisplay() {
String strLCD = "";
for (int index = 0; index < 4; index++) {
myLCD.setCursor(0, index);
strLCD = menuList[index]; // Store menu information
strLCD += lcdBuffer[index]; // Append buffer information
myLCD.print(strLCD); // Display combined information
}
}
// Returns a string by padding characters in the middle of two strings
String PadMid(String leftString, String rightString, int length, char paddingChar) {
int lenPad = length - leftString.length() - rightString.length(); // Calculate padding length
for (int index = 0; index < lenPad; index++) {
leftString += paddingChar; // Add padding characters to left string
}
return (leftString + rightString); // Return combined string with padding
}