// #include <EEPROM.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>
// EEPROM settings
const int EEPROM_SIZE = 1024; // Total EEPROM size in bytes
const int DATA_SIZE = sizeof(int); // Size of the data type (int) to be stored
const int SIGNATURE_SIZE = sizeof(long); // Size of the signature
const int SLOT_SIZE = DATA_SIZE + SIGNATURE_SIZE; // Total slot size (value + signature)
const int NUM_SLOTS = (EEPROM_SIZE / SLOT_SIZE) - 1; // Number of slots for wear leveling
int totalCycleCounter = 0; // The cycle counter variable
int startAddress = 0; // Tracks the current EEPROM slot index
const long SIGNATURE = 0xDEADBEEF; // A unique signature to identify valid slots
LiquidCrystal_I2C lcd(0x27, 20, 4); // Create an Instance for LCD
// Bluetooth Serial Communication
SoftwareSerial BTSerial(10, 11); // RX, TX
const byte dumpCycles = 10; // Define number of allowed dump cycles
// Pin definitions
const byte sensorpin = 6;
const byte up_relay1pin = 8; // Up Solenoid Relay 1
const byte down_relay2pin = 12; // Down Solenoid Relay 2
const byte dump_relay3pin = 17; // Dump Solenoid Relay 3
const byte buzzerpin = 9;
const byte button1 = 2; // Mode selection Button
const byte button2 = 5; // Raises the cylinder (activates the Up relay on Pin 8).
const byte button3 = 4; // Lowers the cylinder (activates the Down relay on Pin 12).
const byte button4 = 3; // Activates the Dump relay (Pin 17) and Down relay (Pin 12) for 1 second, then both go low.
const byte eepromAddress = 0; // EEPROM address to start writing from, you can change this depending on your needs
bool prev4State = LOW; // Variable to hold button 4 previous state
int cycleCounter = 0; // Variable to hold cycle counter
const byte maxCycles = 20;
int selectedMenu = 1;
unsigned long debounceDelay = 250;
unsigned long debounce1Delay = 450; // Debounce delay for button 1
unsigned long lastBtUpdateTime = millis(); // Hold last time update was send to bluetooth
unsigned long btUpdateTime = 5000; // Update time to send current status to bluetooth if not updated previously
// Variables to hold last button press time for dobounce delay
volatile unsigned long lastDebounceTime1 = 0;
unsigned long lastDebounceTime4 = 0;
enum MenuState {
MENU_FAST_AUTO,
MENU_HALF_DROP,
MENU_MANUAL,
MENU_UNDEFINED
};
enum SubState {
SUB_STATE_1,
SUB_STATE_2,
NO_STATE
};
SubState currentSubState = SUB_STATE_1;
SubState lastSentSubState = NO_STATE; // To track the last sent substate
MenuState currentMenuState = MENU_MANUAL;
// Variables for button states and initially set them LOW
volatile bool button1State = LOW; // Variable to track button 1 state
bool button2State, button3State, button4State = LOW;
byte menuNumber = 1; // This menu number will be send to Application
bool btCommand2, btCommand3, btCommand4 = LOW; // Variable to hold bluetooth commands
void setup() {
Serial.begin(9600);
BTSerial.begin(9600); // Initialize Bluetooth Serial
// Load the last saved value from EEPROM using wear leveling
if (!loadValueFromEEPROM()) {
// If no valid value was found, initialize the counter to 0
totalCycleCounter = 0;
startAddress = 1; // Start at slot 1 since slot 0 is reserved
}
lcd.init();
lcd.setCursor(0, 0);
lcd.backlight();
lcd.display();
// Set pin Modes of Button as iNPUT with PULL-UP
pinMode(button1, INPUT_PULLUP); // Set button pin as input with Pull-UP
pinMode(button2, INPUT_PULLUP); // Set button pin as input with Pull-UP
pinMode(button3, INPUT_PULLUP); // Set button pin as input with Pull-UP
pinMode(button4, INPUT_PULLUP); // Set button pin as input with Pull-UP
pinMode(sensorpin, INPUT_PULLUP);
// Set Pin modes of Solenoid as output
pinMode(down_relay2pin, OUTPUT);
pinMode(dump_relay3pin, OUTPUT);
pinMode(up_relay1pin, OUTPUT);
pinMode(buzzerpin, OUTPUT);
resetCounters();
displayMessage(0, 3, "Total:", HIGH); // Print this message on dispaly and clear it as well
displayMessage(8, 0, "Manual ", LOW); // Display current menu on screen adn dont' clear
sendDataToPhone(); // send Data to phone if connected
}
void resetCounters() {
cycleCounter = 0;
digitalWrite(down_relay2pin, LOW);
digitalWrite(dump_relay3pin, LOW);
digitalWrite(up_relay1pin, LOW);
}
void saveTotalCycleCounter() {
// Store the value with wear leveling if it has changed
storeValueIfChanged(totalCycleCounter);
}
void loop() {
unsigned long currentTime = millis();
// Read physical button states and bluetooth Commands
checkBluetoothCommands(); // Check for bluetooth
readButtonStates();
if (button1State == HIGH) { // Check for button 1 press change menu state
button1State = LOW; // Reset button 1 state flag
if (currentMenuState == MENU_FAST_AUTO) {
Serial.println("Change Menu to Manual");
currentMenuState = MENU_MANUAL;
displayMessage(8, 0, "Manual ", LOW); // Display current menu on screen adn dont' clear (Add white space after name if we get raw data previously)
menuNumber = 1; // Set menu number to 2 to be send to application
sendDataToPhone();
} else {
Serial.println("Change Menu to Auto");
currentMenuState = MENU_FAST_AUTO;
menuNumber = 2;
}
lastSentSubState = SUB_STATE_2; // change last sub state
}
// Menu selection logic
if (currentMenuState == MENU_FAST_AUTO && currentSubState == SUB_STATE_2 && button4State == HIGH && cycleCounter < dumpCycles) {
// if (cycleCounter >= dumpCycles) { // Check if cycle counter is greater then 10 reset it and change back to substate 1
// cycleCounter = 0; // Reset the counter
// // currentSubState = SUB_STATE_1; // Switch to Sub Menu 1
// // Optionally, reset other relevant states or counters as needed
// // This function resets cycleCounter and totalCycleCounter, adjust as necessary
// }
// ... [Add Fast Auto state functionality] // Raise the cylinder if sensorpin is LOW and dump_relay3pin (dump) is LOW
if (digitalRead(sensorpin) == HIGH) { // if the sensor pin is not triggered, the Up relay (Pin 8) is activated
digitalWrite(up_relay1pin, HIGH); // Activate solenoid1 to raise
} else {
digitalWrite(up_relay1pin, LOW); // Deactivate solenoid1
}
// If the sensor pin is triggered, the Dump relay (Pin 17) and Down relay (Pin 12) are activated for 1 second, and the system returns to Substate 1.
if (digitalRead(sensorpin) == LOW) {
digitalWrite(dump_relay3pin, HIGH); // Activate solenoid2 dumping cylinder
digitalWrite(down_relay2pin, HIGH);
cycleCounter++;
totalCycleCounter++;
saveTotalCycleCounter();
sendDataToPhone(); // Send updated data to phone
customDelay(1000);
sendDataToPhone(); // Send updated data to phone
digitalWrite(dump_relay3pin, LOW);
digitalWrite(down_relay2pin, LOW); // Deactivate solenoid2
while (digitalRead(sensorpin) == LOW) { // wait until sensor is triggered and get back to normal state
;
}
}
}
// Menu functionality
switch (currentMenuState) {
case MENU_MANUAL:
currentSubState = SUB_STATE_1; // Change substate back to substate 1
if (button2State == HIGH && digitalRead(sensorpin) == HIGH && digitalRead(dump_relay3pin) == LOW) { // Button press raises cylinder
digitalWrite(up_relay1pin, HIGH);
} else if (button2State == LOW) { // Button released Stop raising cylinder
digitalWrite(up_relay1pin, LOW);
}
if (button3State == HIGH) { // Lower cylinder with button press
digitalWrite(down_relay2pin, HIGH);
} else {
digitalWrite(down_relay2pin, LOW);
}
if (button4State == HIGH) { // button press dumps cylinder Activates the Dump relay (Pin 17) and Down relay (Pin 12) for 1 second
digitalWrite(dump_relay3pin, HIGH); // Activate Dump Relay
digitalWrite(down_relay2pin, HIGH); // Activate Down Relay
// Mark and count cylinder dump
cycleCounter++; // Increase Cycle Count
totalCycleCounter++;
saveTotalCycleCounter(); // Save the counter to eeprom
sendDataToPhone(); // Send updated data to phone
customDelay(1000);
sendDataToPhone(); // Send updated data to phone
button4State = LOW; // reset button 4 state
digitalWrite(dump_relay3pin, LOW); // Turn off Dump relay
digitalWrite(down_relay2pin, LOW); // Turn off Down Relay
}
break;
case MENU_FAST_AUTO: // Switch-case logic for MENU_FAST_AUTO
if (button4State == HIGH) { // Toggle the substate on button 4 press
if (currentSubState == SUB_STATE_1) {
cycleCounter = 0; // Reset the counter
currentSubState = SUB_STATE_2;
prev4State = HIGH; // set button 4 previous state as high on substate 2
menuNumber = 3; // Set menu number to 4 to be send to application
sendDataToPhone();
} else if (prev4State == LOW) { // Change state only when button 4 is released
currentSubState = SUB_STATE_1;
button4State = LOW; // Reset button 4 state
}
}
switch (currentSubState) { // Check current sub state and take action
case SUB_STATE_1:
if (lastSentSubState != SUB_STATE_1) { // If last substate is not substate 1 update it and turn All relay LOW
displayMessage(8, 0, "AUTO STANDBY", LOW); // Display current menu on screen adn dont' clear
lastSentSubState = SUB_STATE_1; // Set last substate as substate 1
// Execute AUTO STANDBY specif
digitalWrite(dump_relay3pin, LOW); // Activate solenoid2 to dump
digitalWrite(down_relay2pin, LOW);
digitalWrite(up_relay1pin, LOW);
menuNumber = 2; // Set menu number to 3 to be send to application
sendDataToPhone();
// ... [Your specific logic for this sub-state]
}
break;
case SUB_STATE_2: // Cases for substate 2
lastSentSubState = SUB_STATE_2; // change last sub state
displayMessage(8, 0, "AUTO CYCLING", LOW); // Display current menu on screen adn dont' clear
break; // Clear the LCD display at set intervals
}
}
if ((millis() - lastBtUpdateTime) > btUpdateTime) { // If enough time has passed send an auto update message to device
Serial.println("Send Regular Update to Phone");
sendDataToPhone(); // Send an update to phone
}
}
void sendDataToPhone() {
lastBtUpdateTime = millis(); // note current time
BTSerial.print(menuNumber); // Get current menu number and update it on display
BTSerial.print(";");
BTSerial.print(totalCycleCounter);
BTSerial.print(";");
BTSerial.print(cycleCounter);
BTSerial.println(";"); // Update the last sent menu state to MENU_IDLE
// Update cycle and total cycle counters on LCD
displayMessage(0, 2, cycleCounter);
displayMessage(6, 3, totalCycleCounter);
}
void customDelay(unsigned long waitTime) {
unsigned long cTime = millis();
while ((millis() - cTime) < waitTime) {
checkBluetoothCommands(); // Check for bluetooth commands
readButtonStates(); // Read button states
}
}
void readButtonStates() {
if (digitalRead(button1) == LOW && ((millis() - lastDebounceTime1) > debounce1Delay)) { // Check if button 1 pressed
button1State = HIGH; // Set button 1 state as HIGH
lastDebounceTime1 = millis(); // Note current time when button is pressed
}
if (btCommand2 == LOW) // Check if bluetooth command was not received previously
if (digitalRead(button2) == LOW) // Check if button 2 pressed
button2State = HIGH; // Set button 2 state as HIGH
else if (digitalRead(button2) == HIGH) // Check if button 2 Released
button2State = LOW; // Reset button 2 state back to low
if (btCommand3 == LOW) // Check if bluetooth command was not received previously
if (digitalRead(button3) == LOW) // Check if button 3 pressed
button3State = HIGH; // Set button 3 state as HIGH
else if (digitalRead(button3) == HIGH) // Check if button 3 Released
button3State = LOW; // Reset Button 3 state back to low
if (btCommand4 == LOW) // Check if bluetooth command was not received previously
if ((digitalRead(button4) == LOW) && ((millis() - lastDebounceTime4) > debounceDelay)) { // Check if button 1 pressed
button4State = HIGH; // Set button 4 state as HIGH
lastDebounceTime4 = millis(); // Note current time when button is pressed
} else if (digitalRead(button4) == HIGH) { // Check if button 4 is released and we are in substate 2
if (currentSubState == SUB_STATE_2) {
button4State = LOW; // Reset button 4 state
cycleCounter = 0;
prev4State = LOW;
currentSubState = SUB_STATE_1; // Get back to substate 1
}
}
}
void checkBluetoothCommands() {
while (BTSerial.available()) {
char btChar = BTSerial.read();
Serial.print("Received Bluetooth command:");
Serial.println(btChar);
// Process single-character commands for Bluetooth control
// Process button state commands
switch (btChar) {
case 'A': button1State = HIGH; break;
case 'C':
button2State = HIGH;
btCommand2 = HIGH;
break;
case 'D':
button2State = LOW;
btCommand2 = LOW;
break;
case 'E':
button3State = HIGH;
btCommand3 = HIGH;
break;
case 'F':
button3State = LOW;
btCommand3 = LOW;
break;
case 'G':
button4State = HIGH;
btCommand4 = HIGH;
break;
case 'H':
button4State = LOW;
btCommand4 = LOW;
break;
}
}
}
// Function to load the last valid value from EEPROM using wear leveling
bool loadValueFromEEPROM() {
for (int i = NUM_SLOTS; i >= 1; i--) {
long signature;
EEPROM.get(i * SLOT_SIZE, signature);
if (signature == SIGNATURE) {
// If the signature matches, read the counter value
EEPROM.get(i * SLOT_SIZE + SIGNATURE_SIZE, totalCycleCounter);
startAddress = i;
return true;
}
}
// No valid value was found
return false;
}
// Function to store the value in EEPROM using wear leveling
void storeValueIfChanged(int value) {
// Read the value at the current slot
int storedValue;
long signature;
EEPROM.get(startAddress * SLOT_SIZE, signature);
EEPROM.get(startAddress * SLOT_SIZE + SIGNATURE_SIZE, storedValue);
// If the value has changed or the signature is invalid, write the new value
if (signature != SIGNATURE || storedValue != value) {
// Store the signature and the new value at the current slot
EEPROM.put(startAddress * SLOT_SIZE, SIGNATURE);
EEPROM.put(startAddress * SLOT_SIZE + SIGNATURE_SIZE, value);
// Update startAddress to point to the next slot
startAddress++;
if (startAddress > NUM_SLOTS) {
startAddress = 1; // Wrap around to the first slot (avoiding address 0)
}
Serial.println("Value updated in EEPROM with wear leveling.");
}
}
void displayMessage(int x, int y, const String& msg, bool clearScreen) {
// This function will receive cursor x, y position and message to display and a flag to clear previous display or not
if (clearScreen) { // If clear screen flag is high clear lcd
lcd.clear();
delay(500); // Wait for 500 seconds
}
lcd.setCursor(x, y); // Set cursor at x, y position
lcd.print(msg); // Print msg on lcd
}
void displayMessage(int x, int y, int value) { // This function will receive cursor x, y position and integer value to display
lcd.setCursor(x, y); // Set cursor at x, y position
lcd.print(value); // Print msg on lcd
lcd.print(" "); // Remove any extra value
}