#include <LiquidCrystal_I2C.h>
//Joystick
const int x_pin = A0;
const int y_pin = A1;
const int button_pin = 2;
//Relay
const int relay1_pin = 3;
const int relay2_pin = 4;
const int relay3_pin = 5;
// Joystick calibration
struct Joystick {
const unsigned int x_min = 0;
const unsigned int x_mid = 502;
const unsigned int x_max = 1023;
const unsigned int y_min = 0;
const unsigned int y_mid = 515;
const unsigned int y_max = 1023;
};
// Delays
const int button_debounce_delay = 500;
const int cursor_move_delay = 200;
// Display characters
const byte full[8] = {
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111
};
const byte empty_left[8] = {
0b11111,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b11111
};
const byte empty_right[8] = {
0b11111,
0b00001,
0b00001,
0b00001,
0b00001,
0b00001,
0b11111
};
const byte empty_middle[8] = {
0b11111,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b11111,
};
const byte left_arrow[8] = {
0b00000,
0b00100,
0b01000,
0b11111,
0b01000,
0b00100,
0b00000
};
const byte right_arrow[8] = {
0b00000,
0b00100,
0b00010,
0b11111,
0b00010,
0b00100,
0b00000
};
const byte up_arrow[8] = {
0b00100,
0b01110,
0b10101,
0b00100,
0b00100,
0b00100,
0b00000
};
const byte down_arrow[8] = {
0b00100,
0b00100,
0b00100,
0b10101,
0b01110,
0b00100,
0b00000
};
/*const byte press[8] = {
0b00000,
0b01100,
0b10010,
0b10010,
0b01100,
0b00000,
0b00000
};*/
// State management
struct State {
int cursorPosition = 1;
int barAmount = 0;
int topItemIndex = 0;
int infoScreenNum = 0;
bool buttonPressed = false;
int fillTime = 5000;
struct {
String last_added = "";
bool cola = false;
int cola_amount = 0;
bool fanta = false;
int fanta_amount = 0;
bool sprite = false;
int sprite_amount = 0;
bool mix = false;
}drinks;
void reset() {
infoScreenNum = 0;
barAmount = 0;
drinks = {};
cursorPosition = 1;
topItemIndex = 0;
}
};
// Menu items
const int drinks_size = 4;
const int amounts_size = 3;
int drinks_mix_size = 3;
const String drinks[] = {"Cola", "Fanta", "Sprite", "Mix"};
const String amounts[] = {"150 ml", "250 ml", "300 ml"};
const String headers[] = {"Vyberte Napoj:", "Vyberte Mnozstvi:", "Napoj se pripravuje", "Napoj je pripraven"};
const String drinks_mix[] = {"Cola", "Fanta", "Sprite", "Ok"};
LiquidCrystal_I2C lcd(0x27, 20, 4); //dopsaaat
Joystick joystick;
State state;
void setup()
{
Serial.begin(9600);
pinMode(x_pin, INPUT);
pinMode(y_pin, INPUT);
pinMode(button_pin, INPUT_PULLUP); //dopsaat
pinMode(relay1_pin, OUTPUT);
pinMode(relay2_pin, OUTPUT);
pinMode(relay3_pin, OUTPUT);
digitalWrite(relay1_pin, HIGH);
digitalWrite(relay2_pin, HIGH);
digitalWrite(relay3_pin, HIGH);
lcd.init();
lcd.backlight();
lcd.createChar(0, empty_left); // Create custom character in LCD memory
lcd.createChar(1, empty_right);
lcd.createChar(2, empty_middle);
lcd.createChar(3, left_arrow);
lcd.createChar(4, right_arrow);
lcd.createChar(5, full);
lcd.createChar(6, up_arrow);
lcd.createChar(7, down_arrow);
//lcd.createChar(8, press);
updateDisplay(drinks, drinks_size, headers[0]);
}
int mapValue(int value, int min_num, int mid_num, int max_num)
{
int mapped;
if(value < mid_num)
{
mapped = map(value, min_num, mid_num, 0, 511);
}
else
{
mapped = map(value, mid_num, max_num, 512, 1023);
}
return mapped;
}
String getJoystickPosition()
{
int x = analogRead(x_pin);
int y = analogRead(y_pin);
int x_mapped = mapValue(x, joystick.x_min, joystick.x_mid, joystick.x_max);
int y_mapped = mapValue(y, joystick.y_min, joystick.y_mid, joystick.y_max);
if(x_mapped == 0)
{
return "left";
}
else if(x_mapped == 1023)
{
return "right";
}
if(y_mapped == 0)
{
return "top";
}
else if(y_mapped == 1023)
{
return "bottom";
}
return "center";
}
void updateDisplay(const String items[], const int item_count, const String header)
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(header);
for(int i = 0; i < 3 && (state.topItemIndex + i) < item_count; i++) {
lcd.setCursor(1, i + 1);
lcd.print(items[state.topItemIndex + i]);
}
lcd.setCursor(0, state.cursorPosition);
lcd.print(">");
}
void showHelp()
{
lcd.clear();
lcd.setCursor(6, 0);
lcd.print("Napoveda");
lcd.setCursor(0, 1);
lcd.print("Listovani: ");
lcd.write(byte(6));
lcd.print(" ");
lcd.write(byte(7));
lcd.setCursor(0, 2);
lcd.print("Zmena mnozstvi: ");
lcd.write(byte(3));
lcd.print(" ");
lcd.write(byte(4));
lcd.setCursor(0, 3);
lcd.print("Zpet: ");
lcd.write(byte(3));
lcd.setCursor(11, 3);
lcd.print("Vybrat: o");
}
void moveHelpCursor(String position)
{
if(position == "top" || position == "bottom" || position == "left" || position == "right")
{
state.infoScreenNum = 0;
state.cursorPosition = 1;
state.topItemIndex = 0;
updateDisplay(drinks_mix, drinks_mix_size, headers[0]);
delay(button_debounce_delay);
}
}
void moveCursor(String position, const String items[], const int item_count, const String header)
{
bool updated = false;
if(position == "top" && (state.cursorPosition > 1 || state.topItemIndex > 0)) {
if(state.cursorPosition > 1) {
state.cursorPosition--;
}
else
{
state.topItemIndex--;
}
updated = true;
}
if(position == "bottom" && (state.topItemIndex + state.cursorPosition) < item_count)
{
if(state.cursorPosition < 3) {
state.cursorPosition++;
}
else if((state.topItemIndex + 3) < item_count) {
state.topItemIndex++;
}
updated = true;
}
if(position == "left")
{
if(state.infoScreenNum != 0)
{
state.reset();
drinks_mix_size = 3;
updateDisplay(drinks, drinks_size, headers[0]);
delay(cursor_move_delay);
}
}
if(position == "right")
{
if(state.infoScreenNum == 0)
{
state.infoScreenNum = 6;
showHelp();
delay(button_debounce_delay);
}
}
if(updated)
{
updateDisplay(items, item_count, header);
delay(cursor_move_delay);
}
}
int showProcessBar(String text, int delay_time, int i)
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(text);
int delay_per_step = state.fillTime / 100;
if(state.drinks.mix)
{
delay_time = delay_time + i - 1;
for(i; i <= delay_time; i++) {
int filled_chars = (i * 16) / 100;
lcd.setCursor(2, 1);
for(int j = 0; j < 16; j++) {
if(j < filled_chars) {
lcd.write(byte(5));
}
else
{
if(j == 0)
{
lcd.write(byte(0));
}
else if(j == 15)
{
lcd.write(byte(1));
}
else
{
lcd.write(byte(2));
}
}
}
lcd.setCursor(8, 2);
lcd.print(i);
lcd.print("%");
delay(delay_per_step);
}
}
else
{
for(i; i <= 100; i++) {
int filled_chars = (i * 16) / 100;
lcd.setCursor(2, 1);
for(int j = 0; j < 16; j++) {
if(j < filled_chars) {
lcd.write(byte(5));
}
else
{
if(j == 0)
{
lcd.write(byte(0));
}
else if(j == 15)
{
lcd.write(byte(1));
}
else
{
lcd.write(byte(2));
}
}
}
lcd.setCursor(8, 2);
lcd.print(i);
lcd.print("%");
delay(delay_per_step);
}
}
return i;
}
void pump()
{
int i = 0;
if(state.drinks.mix)
{
if(state.drinks.cola)
{
digitalWrite(relay1_pin, LOW);
i = showProcessBar(headers[2], state.drinks.cola_amount, i);
digitalWrite(relay1_pin, HIGH);
}
if(state.drinks.fanta)
{
digitalWrite(relay2_pin, LOW);
i = showProcessBar(headers[2], state.drinks.fanta_amount, i);
digitalWrite(relay2_pin, HIGH);
}
if(state.drinks.sprite)
{
digitalWrite(relay3_pin, LOW);
i = showProcessBar(headers[2], state.drinks.sprite_amount, i);
digitalWrite(relay3_pin, HIGH);
}
}
else if(state.drinks.cola)
{
Serial.println("cola");
digitalWrite(relay1_pin, LOW);
i = showProcessBar(headers[2], state.fillTime, 0);
//delay(state.fillTime);
digitalWrite(relay1_pin, HIGH);
}
else if(state.drinks.fanta)
{
Serial.println("fanta");
digitalWrite(relay2_pin, LOW);
i = showProcessBar(headers[2], state.fillTime, 0);
digitalWrite(relay2_pin, HIGH);
}
else if(state.drinks.sprite)
{
Serial.println("sprite");
digitalWrite(relay3_pin, LOW);
i = showProcessBar(headers[2], state.fillTime, 0);
digitalWrite(relay3_pin, HIGH);
}
}
void doneDisplay(String text)
{
lcd.clear();
lcd.setCursor(1, 1);
lcd.print(text);
delay(2000);
}
void showAmount(String text)
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.write(byte(3));
lcd.print(" ");
lcd.print(text);
lcd.print(" ");
lcd.write(byte(4));
lcd.setCursor(0, 1);
for(int i = 0; i < 20; i++)
{
if(i*5 >= (100 - (state.drinks.cola_amount + state.drinks.fanta_amount + state.drinks.sprite_amount)))
{
lcd.write(byte(5));
}
else
{
if(i == 0)
{
lcd.write(byte(0));
}
else if(i == 19)
{
lcd.write(byte(1));
}
else
{
lcd.write(byte(2));
}
}
}
lcd.setCursor(0, 2);
lcd.print("0%");
if((100 - (state.drinks.cola_amount + state.drinks.fanta_amount + state.drinks.sprite_amount)) > 95)
{
lcd.setCursor(16, 2);
}
else if((100 - (state.drinks.cola_amount + state.drinks.fanta_amount + state.drinks.sprite_amount)) < 10)
{
lcd.setCursor(18, 2);
}
else
{
lcd.setCursor(17, 2);
}
lcd.print(100 - (state.drinks.cola_amount + state.drinks.fanta_amount + state.drinks.sprite_amount));
lcd.print("%");
lcd.setCursor(0, 3);
lcd.print(">Ok");
lcd.setCursor(16, 3);
lcd.print("Zpet");
}
void updateCursorAmount()
{
if(state.cursorPosition == 1)
{
lcd.setCursor(0, 3);
lcd.print(">");
lcd.setCursor(15, 3);
lcd.print(" ");
}
else if(state.cursorPosition == 2)
{
lcd.setCursor(15, 3);
lcd.print(">");
lcd.setCursor(0, 3);
lcd.print(" ");
}
}
void updateAmountDisplay(int amount)
{
lcd.setCursor(0, 1);
for(int i = 0; i < 20; i++) {
if(i*5 < amount || i*5 >= (100 - (state.drinks.cola_amount + state.drinks.fanta_amount + state.drinks.sprite_amount)))
{
lcd.write(byte(5));
}
else
{
if(i == 0)
{
lcd.write(byte(0));
}
else if(i == 19)
{
lcd.write(byte(1));
}
else
{
lcd.write(byte(2));
}
}
}
lcd.setCursor(2, 2);
lcd.print(" ");
lcd.setCursor(0, 2);
lcd.print(amount);
lcd.print("%");
updateCursorAmount();
}
void moveAmount(String position)
{
bool updated = false;
if(position == "top") {
if(state.cursorPosition > 1) {
state.cursorPosition--;
updated = true;
}
}
if(position == "bottom")
{
if(state.cursorPosition < 2) {
state.cursorPosition++;
updated = true;
}
}
if(position == "left")
{
if(state.barAmount > 0) {
state.barAmount -= 5;
updated = true;
}
}
if(position == "right")
{
if(state.barAmount < (100 - (state.drinks.cola_amount + state.drinks.fanta_amount + state.drinks.sprite_amount))) {
state.barAmount += 5;
updated = true;
}
}
if(updated)
{
updateAmountDisplay(state.barAmount);
delay(cursor_move_delay);
}
}
void processSelection() {
if(!digitalRead(button_pin) && !state.buttonPressed)
{
state.buttonPressed = true;
int selected_item = state.topItemIndex + state.cursorPosition - 1;
switch (state.infoScreenNum) {
case 0:
switch(selected_item) {
case 0: state.drinks.cola = true;
break;
case 1: state.drinks.fanta = true;
break;
case 2: state.drinks.sprite = true;
break;
case 3: state.drinks.mix = true;
break;
}
state.infoScreenNum++;
state.cursorPosition = 1;
state.topItemIndex = 0;
updateDisplay(amounts, amounts_size, headers[1]);
break;
case 1:
if(selected_item == 0)
{
state.fillTime = 1800;
}
else if(selected_item == 1)
{
state.fillTime = 4000;
}
else if(selected_item == 2)
{
state.fillTime = 4800;
}
if(state.drinks.mix)
{
state.infoScreenNum = 4;
state.cursorPosition = 1;
state.topItemIndex = 0;
updateDisplay(drinks_mix, drinks_mix_size, headers[0]);
}
else
{
state.infoScreenNum++;
}
break;
case 4:
if(selected_item != 3)
{
switch(selected_item) {
case 0: state.drinks.cola = true;
state.drinks.last_added = "cola";
break;
case 1: state.drinks.fanta = true;
state.drinks.last_added = "fanta";
break;
case 2: state.drinks.sprite = true;
state.drinks.last_added = "sprite";
break;
}
state.infoScreenNum++;
state.cursorPosition = 1;
state.topItemIndex = 0;
showAmount("Vyberte mnozstvi");
}
else
{
state.infoScreenNum = 2;
}
break;
case 5:
if(state.cursorPosition == 1)
{
if(state.drinks.last_added.equals("cola"))
{
state.drinks.cola_amount += state.barAmount;
}
else if(state.drinks.last_added.equals("fanta"))
{
state.drinks.fanta_amount += state.barAmount;
}
else if(state.drinks.last_added.equals("sprite"))
{
state.drinks.sprite_amount += state.barAmount;
}
if(state.drinks.cola_amount + state.drinks.fanta_amount + state.drinks.sprite_amount == 100)
{
drinks_mix_size = 4;
}
}
state.barAmount = 0;
state.infoScreenNum--;
state.cursorPosition = 1;
state.topItemIndex = 0;
updateDisplay(drinks_mix, drinks_mix_size, headers[0]);
break;
case 6:
state.infoScreenNum = 0;
state.cursorPosition = 1;
state.topItemIndex = 0;
updateDisplay(drinks_mix, drinks_mix_size, headers[0]);
break;
}
}
}
void handleDisplayStates()
{
switch(state.infoScreenNum) {
case 0:
moveCursor(getJoystickPosition(), drinks, drinks_size, headers[0]);
break;
case 1:
moveCursor(getJoystickPosition(), amounts, amounts_size, headers[1]);
break;
case 2:
//Turn the pump on
pump();
state.infoScreenNum++;
case 3:
doneDisplay(headers[3]);
drinks_mix_size = 3;
state.reset();
updateDisplay(drinks, drinks_size, headers[0]);
break;
case 4:
moveCursor(getJoystickPosition(), drinks_mix, drinks_mix_size, headers[0]);
break;
case 5:
moveAmount(getJoystickPosition());
break;
case 6:
moveHelpCursor(getJoystickPosition());
break;
}
}
void loop() {
handleDisplayStates();
processSelection();
if (state.buttonPressed && digitalRead(button_pin)) {
delay(button_debounce_delay);
state.buttonPressed = false;
}
}