Need help with code

Have a code written but can't seem to make some functions work. I probably did something wrong. Who's willing to help me out ? I'll draw schematics and post my code here in a minute. Code is about 600 lines and messy so hopefully we can tidy it up and make it work.

---------------------------------------------------------------UPDATE--------------------------------------------------------------------

//“S”: Starts all delays for the solenoids.
//“6”: Moves the cursor up.
//“4”: Moves the cursor down.
//“8”: Toggles the edit mode.
//“EEPROM RESET”: Resets all EEPROM values to zero.
//“DEFAULT”: Sets the Offsets and Intervals to their default values and saves them to the EEPROM.
//--------------------------------------------------------------------------------------------------------------------------------------------------------
// 
//    ______   _                         _____                   _                    _               __        ___      _              _           
//   |  ____| | |                       / ____|                 | |                  | |             /_ |      / _ \    | |            | |          
//   | |__    | |   ___   __      __   | |        ___    _ __   | |_   _ __    ___   | |     __   __  | |     | | | |   | |__     ___  | |_    __ _ 
//   |  __|   | |  / _ \  \ \ /\ / /   | |       / _ \  | '_ \  | __| | '__|  / _ \  | |     \ \ / /  | |     | | | |   | '_ \   / _ \ | __|  / _` |
//   | |      | | | (_) |  \ V  V /    | |____  | (_) | | | | | | |_  | |    | (_) | | |      \ V /   | |  _  | |_| |   | |_) | |  __/ | |_  | (_| |
//   |_|      |_|  \___/    \_/\_/      \_____|  \___/  |_| |_|  \__| |_|     \___/  |_|       \_/    |_| (_)  \___/    |_.__/   \___|  \__|  \__,_|
//                                                                                                                                                  
//                                                                                                                                                                                                                            
//                                                                           
//     SSSSSSSSSSSSSSS      EEEEEEEEEEEEEEEEEEEEEE     FFFFFFFFFFFFFFFFFFFFFF
//   SS:::::::::::::::S     E::::::::::::::::::::E     F::::::::::::::::::::F
//  S:::::SSSSSS::::::S     E::::::::::::::::::::E     F::::::::::::::::::::F
//  S:::::S     SSSSSSS     EE::::::EEEEEEEEE::::E     FF::::::FFFFFFFFF::::F
//  S:::::S                   E:::::E       EEEEEE       F:::::F       FFFFFF
//  S:::::S                   E:::::E                    F:::::F             
//   S::::SSSS                E::::::EEEEEEEEEE          F::::::FFFFFFFFFF   
//    SS::::::SSSSS           E:::::::::::::::E          F:::::::::::::::F   
//      SSS::::::::SS         E:::::::::::::::E          F:::::::::::::::F   
//         SSSSSS::::S        E::::::EEEEEEEEEE          F::::::FFFFFFFFFF   
//              S:::::S       E:::::E                    F:::::F             
//              S:::::S       E:::::E       EEEEEE       F:::::F             
//  SSSSSSS     S:::::S     EE::::::EEEEEEEE:::::E     FF:::::::FF           
//  S::::::SSSSSS:::::S     E::::::::::::::::::::E     F::::::::FF           
//  S:::::::::::::::SS      E::::::::::::::::::::E     F::::::::FF           
//   SSSSSSSSSSSSSSS        EEEEEEEEEEEEEEEEEEEEEE     FFFFFFFFFFF           
//                                                                                            
//-------------------------------------------------------------------------------------------------------------------------------------------------------         
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

// Define the LCD
LiquidCrystal_I2C lcd(0x27, 20, 4);

// Define the relay pins
const int relayPins[] = {22, 23, 24, 25, 26, 27};

// Define the relay states
int relayStates[] = {LOW, LOW, LOW, LOW, LOW, LOW}; // Start with all relays off

// Define the delay times (in milliseconds)
unsigned long Offsets[6];
unsigned long Intervals[6];

unsigned long lastButtonUpTime = 0;
unsigned long lastButtonDownTime = 0;
unsigned long lastButtonSelectTime = 0;
  unsigned long debounceDelay = 500; // Adjust the debounce delay as needed

  unsigned long currentTime = millis();

//Double press mills
unsigned long buttonPressTime = 0;

// Define the button pins
const int buttonUp = 12;
const int buttonDown = 13;
const int buttonSelect = 11;

// Define the cursor position
volatile int cursorPosition = 0;

// Define the edit mode
volatile bool editMode = false;

// Define LCD update
volatile bool updateLCD = true;

// Define the number of solenoids
const int numSolenoids = 6;

// Define the edit item (0 = Offset, 1 = Opening Time)
volatile int editItem = 0;

// Try get updates instantly. Test logic
bool eepromUpdated = false;
//Rest of additional menus and options
bool secretMenuActive = false;
bool simulateOption = false;
bool cleaningOption = false;

// Define a timer class
class Timer {
  private:
    unsigned long startTime;
    unsigned long interval;
    bool running;

  public:
    Timer(unsigned long interval = 0) {
      this->interval = interval;
      running = false;
    }

    void start() {
      startTime = millis();
      running = true;
    }

    void stop() {
      running = false;
    }

    bool isRunning() {
      return running;
    }

    bool event() {
      if (running && millis() - startTime >= interval) {
        startTime = millis(); // reset the start time
        return true;
      }
      return false;
    }
};

// Define a delay class
class Delay {
  private:
    unsigned long startDTime;
    unsigned long delayTime;
    bool running;

  public:
    Delay(unsigned long delayTime = 0) {
      this->delayTime = delayTime;
      running = false;
    }

    void start() {
      startDTime = millis();
      running = true;
    }

    void stop() {
      running = false;
    }

    bool isRunning() {
      return running;
    }

    bool event() {
      if (running && millis() - startDTime >= delayTime) {
        startDTime = millis(); // reset the start time
        return true;
      }
      return false;
    }
};

// Create timers for each relay
Timer timers[6];
// Create timer Delays for each relay
Delay Delays[6];

void setup() {
  // Initialize the LCD
  lcd.init();
  lcd.backlight();
  // Bootloader
  lcd.setCursor(2, 1);
  lcd.print("Bandau Uzsikraut");
  lcd.setCursor(0, 2);
  for (int i = 0; i < 20; ++i) {
    lcd.print(".");
    delay(100);
  }
  delay(500);
  // Clear screen after loading animation
  lcd.clear();
  delay(750);

  // Set the relay pins as output and initialize states
  for (int i = 0; i < 6; i++) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], relayStates[i]); // Ensure all relays start off
  }

  // Set pullup on buttons
  pinMode(buttonUp, INPUT_PULLUP);
  pinMode(buttonDown, INPUT_PULLUP);
  pinMode(buttonSelect, INPUT_PULLUP);


  // Begin serial communication
  Serial.begin(9600);

  // Load the values from the EEPROM
      for (int i = 0; i < numSolenoids; i++) {
        Offsets[i] = EEPROM.read(18 + i * 3) * 10;
        Intervals[i] = EEPROM.read(i * 3) * 10;
        timers[i] = Timer(Intervals[i]); // Initialize the timers
        Delays[i] = Delay(Offsets[i]);   // Initialize the delays
      }

  // Read and print EEPROM values
  printEEPROMValues();

  // Attach interrupts to the buttons
  attachInterrupt(digitalPinToInterrupt(buttonUp), moveCursorUp, RISING);
  attachInterrupt(digitalPinToInterrupt(buttonDown), moveCursorDown, RISING);
  attachInterrupt(digitalPinToInterrupt(buttonSelect), toggleEditMode, RISING);

}

void loop() {
 static unsigned long lastButtonUpTime = 0;
 static unsigned long lastButtonDownTime = 0;
 static unsigned long lastButtonSelectTime = 0;
 unsigned long lastUpdate = 0;
  unsigned long debounceDelay = 250; // Adjust the debounce delay as needed

  unsigned long currentTime = millis();

  // Button Up
  if (currentTime - lastButtonUpTime > debounceDelay && digitalRead(buttonUp) == LOW) {
    moveCursorUp();
    lastButtonUpTime = currentTime;
  }

  // Button Down
  if (currentTime - lastButtonDownTime > debounceDelay && digitalRead(buttonDown) == LOW) {
    moveCursorDown();
    lastButtonDownTime = currentTime;
  }

  // Button Select
  if (currentTime - lastButtonSelectTime > debounceDelay && digitalRead(buttonSelect) == LOW) {
    toggleEditMode();
    lastButtonSelectTime = currentTime;
  }

//Secret menu call and axit
if (digitalRead(buttonUp) == LOW && digitalRead(buttonDown) == LOW) {
    if (buttonPressTime == 0) { // if not already started
      buttonPressTime = millis(); // start timer
    } else if (millis() - buttonPressTime >= 2000) { // if 2 seconds have passed
      secretMenu(); // call your function
      secretMenuActive = true;
      buttonPressTime = 0; // reset timer
      updateLCD = true;
    }
  }


  // Check for serial input
  if (Serial.available()) {
    processSerialCommand();
  }


  // Update relay states
  updateRelayStates();

  // Update the LCD if needed
  if (updateLCD) {
    updateLCDContent();
  }
}

void printEEPROMValues() {
  Serial.println("------------------------------------------------");
  for (int i = 0; i < numSolenoids; i++) {
    Serial.print("Solenoid ");
    Serial.print(i + 1);
    Serial.print(" Opening Time [ ");
    Serial.print(EEPROM.read(i * 3) * 10);
    Serial.print(" ] Offset [ ");
    Serial.print(EEPROM.read(18 + i * 3) * 10);
    Serial.println(" ]");
  }
  Serial.println("------------------------------------------------");
}

void processSerialCommand() {
  String command = Serial.readStringUntil('\n');
  command.trim();

  if (command == "HELP") {
    // Display help information
    displayHelp();
  } else if (command == "S") {
    // Load the values from the EEPROM
      for (int i = 0; i < numSolenoids; i++) {
        Offsets[i] = EEPROM.read(18 + i * 3) * 10;
        Intervals[i] = EEPROM.read(i * 3) * 10;
        timers[i] = Timer(Intervals[i]); // Initialize the timers
        Delays[i] = Delay(Offsets[i]);   // Initialize the delays
      }
  
    // Start all delays for the solenoids
    startAllDelays();
    updateRelayStates();
  } else if (command == "6") {
    moveCursorUp();
  } else if (command == "4") {
    moveCursorDown();
  } else if (command == "8" || (digitalRead(buttonSelect) == LOW )) {
    toggleEditMode();
  } else if (command == "DEFAULT") {
    setDefaultValues();
  } else if (command == "EEPROM RESET") {
    resetEEPROMValues();
  } else if (command == "Print") {
    printEEPROMValues();
  } else if (command == "x") {
    startSimulation();
  }  else if (command == "Menu") {
      secretMenuActive = true;
      secretMenu();
  }
}

void secretMenu() {
  int currentPage = 1; // Track the current page in the secret menu
  int cursorPosition = 0; // Track the cursor position (0 for ON, 1 for OFF)
  unsigned long lastUpdate = 0; // Variable to store the last update time

  lcd.clear();
  lcd.setCursor(2, 0);
  lcd.print("* Secret  Menu *");
  delay(1000);
  Serial.println("Entering Secret Menu");
  lcd.clear();

  while (secretMenuActive) {
    if (currentPage == 1) {
      lcd.setCursor(5, 0);
      lcd.print("Simulation");
      lcd.setCursor(2, 2);
      lcd.print(cursorPosition == 1 ? "<< ON >>  OFF   " : "   ON  << OFF >>");
    } else if (currentPage == 2) {
      lcd.setCursor(6, 0);
      lcd.print("Cleaning");
      lcd.setCursor(2, 2);
      lcd.print(cursorPosition == 1 ? "<< ON >>  OFF   " : "   ON  << OFF >>");
    }

    if (digitalRead(buttonUp) == LOW) {
      if (currentPage == 2) {
        currentPage = 1;
        lcd.clear();
      }
    } else if (digitalRead(buttonDown) == LOW) {
      if (currentPage == 1) {
        currentPage = 2;
        lcd.clear();
      }
    } else if (digitalRead(buttonSelect) == LOW) {
  // Toggle the cursor position
  cursorPosition = !cursorPosition;
  delay(300);

  if (currentPage == 1) {
    if (cursorPosition == 1) {
      simulateOption = true ;
    } else {
      stopSimulation();
      simulateOption = false;
    }
  } else if (currentPage == 2) {
    if (cursorPosition == 1) {
      startCleaning();
      cleaningOption = true;
    } else {
      stopCleaning();
      cleaningOption = false;
    }
  }
}

    if (simulateOption && millis() - lastUpdate >= 2000) {
    startSimulation();
  }

    if (cleaningOption && millis() - lastUpdate >= 5000) {
      startCleaning();
      Serial.println("Cleaning mode active. Press 'OFF' to stop.");
      lastUpdate = millis();
    }

    //Secret menu call and axit
if (digitalRead(buttonUp) == LOW && digitalRead(buttonDown) == LOW) {
    if (buttonPressTime == 0) { // if not already started
      buttonPressTime = millis(); // start timer
    } else if (millis() - buttonPressTime >= 2000) { // if 2 seconds have passed
    Serial.println("Exiting");
      lcd.clear();
      lcd.setCursor(5,1);
      lcd.print("Exiting...");
      delay(1000);
      secretMenuActive = false;
      buttonPressTime = 0; // reset timer
      updateLCD = true;
    } else if (millis() - buttonPressTime >= 3000) { 
      Serial.print("pritruko palaikyt nulinam laikmati knopkem");// if 3 seconds have passed
      buttonPressTime = 0;
    }
  }

  }
}
//----------------------------------------Secret menu end


void startAllDelays() {
  for (int i = 0; i < numSolenoids; i++) {
    Delays[i].start();
  }
  Serial.println("---Values updated for cycle");
}

void updateRelayStates() {
  for (int i = 0; i < numSolenoids; i++) {
    if (Delays[i].event()) {
      Offsets[i] = EEPROM.read(18 + i * 3) * 10;
      Intervals[i] = EEPROM.read(i * 3) * 10;
      timers[i].start();
      relayStates[i] = HIGH;
      digitalWrite(relayPins[i], relayStates[i]);
      Delays[i].stop();
    }

    if (timers[i].event()) {
      relayStates[i] = LOW;
      digitalWrite(relayPins[i], relayStates[i]);
      timers[i].stop();
    }
  }
}
//-------------------------------------------------------------------------------
void startSimulation() {
  static Timer simulationTimer(2000); // Adjust the interval as needed

  if (simulationTimer.event()) {
    // Load the values from the EEPROM
    for (int i = 0; i < numSolenoids; i++) {
      Offsets[i] = EEPROM.read(18 + i * 3) * 10;
      Intervals[i] = EEPROM.read(i * 3) * 10;
    }

    // Start all delays for the solenoids
    startAllDelays();
    updateRelayStates();

    Serial.println("----Simulation completed----");
    updateLCD = true;
  }
}


//--------------------------------------------Trying to get Simulation to work on toggle....

void startCleaning() {
  // Open all relays
  for (int i = 0; i < numSolenoids; i++) {
    relayStates[i] = HIGH;
    digitalWrite(relayPins[i], HIGH);
  }
    updateLCD = true; // Set the flag to update the LCD

}

void stopSimulation() {
  // Add logic to stop the simulation
  Serial.println("-------------Simulation stopped----------------");
      updateLCD = true; // Set the flag to update the LCD
}

void stopCleaning() {
  // Add logic to stop the cleaning
  for (int i = 0; i < numSolenoids; i++) {
    relayStates[i] = LOW;
    digitalWrite(relayPins[i], relayStates[i]);
  }
    updateLCD = true; // Set the flag to update the LCD
    Serial.println("Cleaning mode stopped. Returning to normal state.");
}
//--------------------------------------------------------------------------------

void updateLCDContent() {
  lcd.clear();
  lcd.setCursor(5, 0);
  lcd.print("Solenoid ");
  lcd.print(cursorPosition + 1);
  lcd.setCursor(0, 1);
  lcd.print("Offset : ");
  if (editMode && editItem == 0) {
    lcd.print("<< ");
    lcd.print(Offsets[cursorPosition] / 10);
    lcd.print(" >> %");
  } else {
    lcd.print(Offsets[cursorPosition] / 10);
    lcd.print(" %");
  }
  lcd.setCursor(0, 2);
  lcd.print("Opening: ");
  if (editMode && editItem == 1) {
    lcd.print("<< ");
    lcd.print(Intervals[cursorPosition] / 10);
    lcd.print(" >> cs");
  } else {
    lcd.print(Intervals[cursorPosition] / 10);
    lcd.print(" cs");
  }
  updateLCD = false; // Reset the flag
}

void displayHelp() {
  Serial.println("List of available commands:");
  Serial.println("\"S\": Start all delays for the solenoids.");
  Serial.println("\"6\": Move the cursor up.");
  Serial.println("\"4\": Move the cursor down.");
  Serial.println("\"8\": Toggle the edit mode.");
  Serial.println("\"EEPROM RESET\": Reset all EEPROM values to zero.");
  Serial.println("\"DEFAULT\": Set the Offsets and Intervals to their default values and save them to the EEPROM.");
  Serial.println("\"Print\": Prints a table of current values saved on Solenoids.");
  Serial.println("\"Menu\": Secret menu to Operate Simulation and Cleaning.");
  Serial.println("\"Exit\": Exit Secret Menu");
}

void moveCursorUp() {
  if (editMode) {
    // Increment the value of the selected item
    if (editItem == 0) {
      Offsets[cursorPosition] = min(1500, Offsets[cursorPosition] + 10);
    } else {
      Intervals[cursorPosition] += 10;
    }
  } else {
    // Move the cursor up
    cursorPosition = min(numSolenoids - 1, cursorPosition + 1);
  }
  updateLCD = true; // Set the flag to update the LCD
}

void moveCursorDown() {
  if (editMode) {
    // Decrement the value of the selected item
    if (editItem == 0) {
      Offsets[cursorPosition] = max(0, Offsets[cursorPosition] - 10);
    } else {
      Intervals[cursorPosition] = max(0, Intervals[cursorPosition] - 10);
    }
  } else {
    // Move the cursor down
    delay(1000);
    cursorPosition = max(0, cursorPosition - 1);
  }
  updateLCD = true; // Set the flag to update the LCD
}

void toggleEditMode() {
  if (editMode) {
    // Switch to the next item to edit
    editItem = (editItem + 1) % 2;
    if (editItem == 0) {
      // If we've cycled back to the first item, exit edit mode and display a "Saved" message
      editMode = false;
      displaySavedMessage();
      delay(2000);
      saveValuesToEEPROM();
      eepromUpdated = true;
    }
  } else {
    // Enter edit mode
    editMode = true;
  }
  updateLCD = true; // Set the flag to update the LCD
}

void displaySavedMessage() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("*******************");
  lcd.setCursor(5, 1);
  lcd.print("Solenoid ");
  lcd.print(cursorPosition + 1);
  lcd.setCursor(7, 2);
  lcd.print("Saved!");
  lcd.setCursor(0, 3);
  lcd.print("*******************");
}

void saveValuesToEEPROM() {
  for (int i = 0; i < numSolenoids; i++) {
    EEPROM.update(18 + i * 3, Offsets[i] / 10);
    EEPROM.update(i * 3, Intervals[i] / 10);
  }
  Serial.println("Values saved to EEPROM.");
}

void setDefaultValues() {
  for (int i = 0; i < numSolenoids; i++) {
    if (i == 0 || i == 2 || i == 4) { // Solenoids 1, 3, 5
      Offsets[i] = 0;
    } else {                         // The rest of the solenoids
      Offsets[i] = 500;
    }
    Intervals[i] = 60;
    EEPROM.update(18 + i * 3, Offsets[i] / 10);
    EEPROM.update(i * 3, Intervals[i] / 10);
  }
  Serial.println("Default Values Saved to EEPROM.");
  updateLCD = true; // Set the flag to update the LCD
}

void resetEEPROMValues() {
  // Zero out the EEPROM values
  for (int i = 0; i < numSolenoids * 2 + 1; i++) {
    EEPROM.update(i, 0);
  }
  Serial.println("EEPROM VALUES ZEROED");
  updateLCD = true; // Set the flag to update the LCD
}
                                     [ PINOUT ]

Power:

                               [ RELAY SHIELD ]
     * DC +    = Connected to 14.6 V bench power station to simulate running tractor voltage.
     * DC -     = Connected to the same power stations return as one should.

                                   [ MEGA ]
     * VIN   = Connected to 5V output from relay shield.
     * GND = Connected to ground pin of both Arduino Mega and relay shield.

Buttons:

  • Digital Pin 11 = Connected to button 1 signal pin. (SELECT)
  • Digital Pin 12 = Connected to button 2 signal pin. (DOWN)
  • Digital Pin 13 = Connected to button 3 signal pin. (UP)
  • GND (Ground) = Connected to the common ground pin of the keypad.

Relays:

  • Relay 1 Control Pin = Connected to Pin 22)
  • Relay 1 Control Pin = Connected to Pin 23)
  • Relay 1 Control Pin = Connected to Pin 24)
  • Relay 1 Control Pin = Connected to Pin 25)
  • Relay 1 Control Pin = Connected to Pin 26)
  • Relay 1 Control Pin = Connected to Pin 27)

Cant get the simulation to work. It needs to run through the UpdateRelays sequence once every second or run continuously through the sequence. sequence completes and starts over again.

for budget please give me a quote. my budget is flexible just give me quotes please. Or if you think this is a pretty simple fix and I'm just blind mock me virtually but help me fix it :slight_smile: . this should be a simple fix for someone that is good at coding. this is my first ever

Question is when you will post it :smiley:

... and what's the budget :slight_smile:

What does the setup do and what do You want it to do?

Overall, this code needs to control solenoids based on user input and stored settings. It should include features for displaying information, editing settings, and performing simulations/cleaning routine.

Solenoids should run 1 sequence as per saved values when a momentary switch is pressed. but I have a function S to simulate that and it works. However, I cant get the simulation to work witch in turn I have doubts if it will work with a switch.

---------------------------Update

Right, I have just tested it out with a switch. works fine. Just need to get Simulation mode to work.....

Will post a video for you guys to see

That tells nothing useful.
What does it do now?
What is it supposed to do? You need to document that in detail.

Hi, @kornelijusuk

Is this the start of your project?

Tom.. :grinning: :+1: :coffee: :australia:

Yes it is. Trying to figure out why cant i Simulate operation. Switch operation works ive just tweeked the code to include that and hooked up a random membrane to check.

//-------------------------------------------------------------------------------
void startSimulation() {
  static Timer simulationTimer(2000); // Adjust the interval as needed
   
  if (simulationTimer.event()) {
    // Load the values from the EEPROM
    for (int i = 0; i < numSolenoids; i++) {
      Offsets[i] = EEPROM.read(18 + i * 3) * 10;
      Intervals[i] = EEPROM.read(i * 3) * 10;
    }

    // Start all delays for the solenoids
    startAllDelays();
    updateRelayStates();

    Serial.println("----Simulation completed----");
    updateLCD = true;
  }
}

That's all done once when startSimulation() is performed. Don't you have to call the simulation in loop() on a regular basis?

These changes would at least do something (probably not what you want?):

//-------------------------------------------------------------------------------
void startSimulation() {
  Timer simulationTimer(2000); // Adjust the interval as needed
  Serial.println("Start Simulation");
  simulationTimer.start();
  startCleaning();
  while (!simulationTimer.event()) {
    // Do nothing ...
  }
  stopCleaning();
  stopSimulation();
}

void stopSimulation() {
  // Add logic to stop the simulation
  // Load the values from the EEPROM
  for (int i = 0; i < numSolenoids; i++) {
    Offsets[i] = EEPROM.read(18 + i * 3) * 10;
    Intervals[i] = EEPROM.read(i * 3) * 10;
  }
  // Start all delays for the solenoids
  startAllDelays();
  updateRelayStates();
  Serial.println("----Simulation completed----");
  updateLCD = true;

  Serial.println("-------------Simulation stopped----------------");
  updateLCD = true; // Set the flag to update the LCD
}

You can test the changes on Wokwi;

1 Like

No problem somewhere else. I have just tried to try run the sequance when i am in the Secret menu and nothing works. I do think i need to rewrite the secret menu. Or try and do a diferent style menu.....hmmmmm

Latest code so far. Need to find a better way to handle secret menu as nothing works when secretMenuActive flag is true. when i exit (set to false) everything works fine. Anyone have a better logic for having a Menu for thats accessable only when needed and that still doesnot interfere with main loop ?

//“S”: Starts all delays for the solenoids.
//“6”: Moves the cursor up.
//“4”: Moves the cursor down.
//“8”: Toggles the edit mode.
//“EEPROM RESET”: Resets all EEPROM values to zero.
//“DEFAULT”: Sets the Offsets and Intervals to their default values and saves them to the EEPROM.
//--------------------------------------------------------------------------------------------------------------------------------------------------------
// 
//    ______   _                         _____                   _                    _               __        ___      _              _           
//   |  ____| | |                       / ____|                 | |                  | |             /_ |      / _ \    | |            | |          
//   | |__    | |   ___   __      __   | |        ___    _ __   | |_   _ __    ___   | |     __   __  | |     | | | |   | |__     ___  | |_    __ _ 
//   |  __|   | |  / _ \  \ \ /\ / /   | |       / _ \  | '_ \  | __| | '__|  / _ \  | |     \ \ / /  | |     | | | |   | '_ \   / _ \ | __|  / _` |
//   | |      | | | (_) |  \ V  V /    | |____  | (_) | | | | | | |_  | |    | (_) | | |      \ V /   | |  _  | |_| |   | |_) | |  __/ | |_  | (_| |
//   |_|      |_|  \___/    \_/\_/      \_____|  \___/  |_| |_|  \__| |_|     \___/  |_|       \_/    |_| (_)  \___/    |_.__/   \___|  \__|  \__,_|
//                                                                                                                                                  
//                                                                                                                                                                                                                            
//                                                                           
//     SSSSSSSSSSSSSSS      EEEEEEEEEEEEEEEEEEEEEE     FFFFFFFFFFFFFFFFFFFFFF
//   SS:::::::::::::::S     E::::::::::::::::::::E     F::::::::::::::::::::F
//  S:::::SSSSSS::::::S     E::::::::::::::::::::E     F::::::::::::::::::::F
//  S:::::S     SSSSSSS     EE::::::EEEEEEEEE::::E     FF::::::FFFFFFFFF::::F
//  S:::::S                   E:::::E       EEEEEE       F:::::F       FFFFFF
//  S:::::S                   E:::::E                    F:::::F             
//   S::::SSSS                E::::::EEEEEEEEEE          F::::::FFFFFFFFFF   
//    SS::::::SSSSS           E:::::::::::::::E          F:::::::::::::::F   
//      SSS::::::::SS         E:::::::::::::::E          F:::::::::::::::F   
//         SSSSSS::::S        E::::::EEEEEEEEEE          F::::::FFFFFFFFFF   
//              S:::::S       E:::::E                    F:::::F             
//              S:::::S       E:::::E       EEEEEE       F:::::F             
//  SSSSSSS     S:::::S     EE::::::EEEEEEEE:::::E     FF:::::::FF           
//  S::::::SSSSSS:::::S     E::::::::::::::::::::E     F::::::::FF           
//  S:::::::::::::::SS      E::::::::::::::::::::E     F::::::::FF           
//   SSSSSSSSSSSSSSS        EEEEEEEEEEEEEEEEEEEEEE     FFFFFFFFFFF           
//                                                                                            
//-------------------------------------------------------------------------------------------------------------------------------------------------------         
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

// Define the LCD
LiquidCrystal_I2C lcd(0x27, 20, 4);

// Define the relay pins
const int relayPins[] = {22, 23, 24, 25, 26, 27};

// Define the relay states
int relayStates[] = {LOW, LOW, LOW, LOW, LOW, LOW}; // Start with all relays off

// Define the delay times (in milliseconds)
unsigned long Offsets[6];
unsigned long Intervals[6];

unsigned long lastButtonUpTime = 0;
unsigned long lastButtonDownTime = 0;
unsigned long lastButtonSelectTime = 0;
  unsigned long debounceDelay = 500; // Adjust the debounce delay as needed

  unsigned long currentTime = millis();

//Double press mills
unsigned long buttonPressTime = 0;

// Define the button pins
const int buttonUp = 12;
const int buttonDown = 13;
const int buttonSelect = 11;

//Momentary Switch
const int Switch = 53;

// Define the cursor position
volatile int cursorPosition = 0;

// Define the edit mode
volatile bool editMode = false;

// Define LCD update
volatile bool updateLCD = true;

// Define the number of solenoids
const int numSolenoids = 6;

// Define the edit item (0 = Offset, 1 = Opening Time)
volatile int editItem = 0;

// Try get updates instantly. Test logic
bool eepromUpdated = false;
//Rest of additional menus and options
bool secretMenuActive = false;
bool simulateOption = false;
bool cleaningOption = false;

// Define a timer class
class Timer {
  private:
    unsigned long startTime;
    unsigned long interval;
    bool running;

  public:
    Timer(unsigned long interval = 0) {
      this->interval = interval;
      running = false;
    }

    void start() {
      startTime = millis();
      running = true;
    }

    void stop() {
      running = false;
    }

    bool isRunning() {
      return running;
    }

    bool event() {
      if (running && millis() - startTime >= interval) {
        startTime = millis(); // reset the start time
        return true;
      }
      return false;
    }
};

// Define a delay class
class Delay {
  private:
    unsigned long startDTime;
    unsigned long delayTime;
    bool running;

  public:
    Delay(unsigned long delayTime = 0) {
      this->delayTime = delayTime;
      running = false;
    }

    void start() {
      startDTime = millis();
      running = true;
    }

    void stop() {
      running = false;
    }

    bool isRunning() {
      return running;
    }

    bool event() {
      if (running && millis() - startDTime >= delayTime) {
        startDTime = millis(); // reset the start time
        return true;
      }
      return false;
    }
};

// Create timers for each relay
Timer timers[6];
// Create timer Delays for each relay
Delay Delays[6];

void setup() {
  // Initialize the LCD
  lcd.init();
  lcd.backlight();
  // Bootloader
  lcd.setCursor(2, 1);
  lcd.print("Bandau Uzsikraut");
  lcd.setCursor(0, 2);
  for (int i = 0; i < 20; ++i) {
    lcd.print(".");
    delay(100);
  }
  delay(500);
  // Clear screen after loading animation
  lcd.clear();
  delay(750);

  // Set the relay pins as output and initialize states
  for (int i = 0; i < 6; i++) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], relayStates[i]); // Ensure all relays start off
  }

  // Set pullup on buttons
  pinMode(buttonUp, INPUT_PULLUP);
  pinMode(buttonDown, INPUT_PULLUP);
  pinMode(buttonSelect, INPUT_PULLUP);

  // Set puup for Switch
  pinMode(Switch, INPUT_PULLUP);


  // Begin serial communication
  Serial.begin(9600);

  // Load the values from the EEPROM
      for (int i = 0; i < numSolenoids; i++) {
        Offsets[i] = EEPROM.read(18 + i * 3) * 10;
        Intervals[i] = EEPROM.read(i * 3) * 10;
        timers[i] = Timer(Intervals[i]); // Initialize the timers
        Delays[i] = Delay(Offsets[i]);   // Initialize the delays
      }

  // Read and print EEPROM values
  printEEPROMValues();

  // Attach interrupts to the buttons
  attachInterrupt(digitalPinToInterrupt(buttonUp), moveCursorUp, RISING);
  attachInterrupt(digitalPinToInterrupt(buttonDown), moveCursorDown, RISING);
  attachInterrupt(digitalPinToInterrupt(buttonSelect), toggleEditMode, RISING);
  attachInterrupt(digitalPinToInterrupt(Switch), updateRelayStates, RISING);

}

void loop() {
 static unsigned long lastButtonUpTime = 0;
 static unsigned long lastButtonDownTime = 0;
 static unsigned long lastButtonSelectTime = 0;
 static unsigned long lastSwitchTime = 0;
 unsigned long lastUpdate = 0;
 unsigned long debounceDelay = 250; // Adjust the debounce delay as needed

  unsigned long currentTime = millis();

  // Button Up
  if (currentTime - lastButtonUpTime > debounceDelay && digitalRead(buttonUp) == LOW) {
    moveCursorUp();
    lastButtonUpTime = currentTime;
  }

  // Button Down
  if (currentTime - lastButtonDownTime > debounceDelay && digitalRead(buttonDown) == LOW) {
    moveCursorDown();
    lastButtonDownTime = currentTime;
  }

  // Button Select
  if (currentTime - lastButtonSelectTime > debounceDelay && digitalRead(buttonSelect) == LOW) {
    toggleEditMode();
    lastButtonSelectTime = currentTime;
  }

    // Switch
  if (currentTime - lastSwitchTime > debounceDelay && digitalRead(Switch) == LOW) {
    Serial.println("----------------SWITCH-----------------");
    loadValues();
    startAllDelays();
    updateRelayStates();
    lastButtonSelectTime = currentTime;
  }



//Secret menu call and axit
if (digitalRead(buttonUp) == LOW && digitalRead(buttonDown) == LOW) {
    if (buttonPressTime == 0) { // if not already started
      buttonPressTime = millis(); // start timer
    } else if (millis() - buttonPressTime >= 2000) { // if 2 seconds have passed
      secretMenu(); // call your function
      secretMenuActive = true;
      buttonPressTime = 0; // reset timer
      updateLCD = true;
    }
  }


  // Check for serial input
  if (Serial.available()) {
    processSerialCommand();
  }


  // Update relay states
  updateRelayStates();

  // Start simulation
  if (simulateOption) {
    loadValues();
    startAllDelays();
    updateRelayStates();
    delay(1500);
  }

  // Update the LCD if needed
  if (updateLCD) {
    updateLCDContent();
  }
}

void printEEPROMValues() {
  Serial.println("------------------------------------------------");
  for (int i = 0; i < numSolenoids; i++) {
    Serial.print("Solenoid ");
    Serial.print(i + 1);
    Serial.print(" Opening Time [ ");
    Serial.print(EEPROM.read(i * 3) * 10);
    Serial.print(" ] Offset [ ");
    Serial.print(EEPROM.read(18 + i * 3) * 10);
    Serial.println(" ]");
  }
  Serial.println("------------------------------------------------");
}

void processSerialCommand() {
  String command = Serial.readStringUntil('\n');
  command.trim();

  if (command == "HELP") {
    // Display help information
    displayHelp();
  } else if (command == "S") {
    // Load the values from the EEPROM Start all Delays
    loadValues();
    startAllDelays();
    updateRelayStates(); Serial.println("---UpdatingRelays");
    Serial.println("---------------Sequance Finished------------------");
  } else if (command == "6") {
    moveCursorUp();
  } else if (command == "4") {
    moveCursorDown();
  } else if (command == "8") {
    if(secretMenuActive) {
        cursorPosition = !cursorPosition;
  delay(150);
    } else {
    toggleEditMode();
    }
  } else if (command == "DEFAULT") {
    setDefaultValues();
  } else if (command == "EEPROM RESET") {
    resetEEPROMValues();
  } else if (command == "Print") {
    printEEPROMValues();
  } else if (command == "x") {
    startSimulation();
  }  else if (command == "exit") {
      secretMenuActive = false;
      Serial.println("Exiting secret menu....");
      updateLCD = true;
  }  else if (command == "m" ) {
      secretMenuActive = true;
      secretMenu();
  }
}

void secretMenu() {
  int currentPage = 1; // Track the current page in the secret menu
  int cursorPosition = 0; // Track the cursor position (0 for ON, 1 for OFF)
  unsigned long lastUpdate = 0; // Variable to store the last update time
    

  lcd.clear();
  lcd.setCursor(2, 0);
  lcd.print("* Secret  Menu *");
  delay(1000);
  Serial.println("Entering Secret Menu");
  lcd.clear();

  while (secretMenuActive) {

        if (Serial.available()) {
    processSerialCommand();
  }

    if (currentPage == 1) {
      lcd.setCursor(5, 0);
      lcd.print("Simulation");
      lcd.setCursor(2, 2);
      lcd.print(cursorPosition == 1 ? "<< ON >>  OFF   " : "   ON  << OFF >>");
    } else if (currentPage == 2) {
      lcd.setCursor(6, 0);
      lcd.print("Cleaning");
      lcd.setCursor(2, 2);
      lcd.print(cursorPosition == 1 ? "<< ON >>  OFF   " : "   ON  << OFF >>");
    }

    if (digitalRead(buttonUp) == LOW) {
      if (currentPage == 2) {
        currentPage = 1;
        lcd.clear();
      }
    } else if (digitalRead(buttonDown) == LOW) {
      if (currentPage == 1) {
        currentPage = 2;
        lcd.clear();
      }
    } else if (digitalRead(buttonSelect) == LOW) {
  // Toggle the cursor position
  cursorPosition = !cursorPosition;
  delay(150);

  if (currentPage == 1) {
    if (cursorPosition == 1) {
            simulateOption = true ;
    } else {
      stopSimulation();
      simulateOption = false;
    }
  } else if (currentPage == 2) {
    if (cursorPosition == 1) {
      startCleaning();
      cleaningOption = true;
    } else {
      stopCleaning();
      cleaningOption = false;
    }
  }
}

   // if (simulateOption && millis() - lastUpdate >= 2000) {
   //   digitalWrite(Switch, LOW);
 // }

    if (cleaningOption && millis() - lastUpdate >= 5000) {
      startCleaning();
      Serial.println("Cleaning mode active. Press 'OFF' to stop.");
      lastUpdate = millis();
    }

    //Secret menu call and axit
if (digitalRead(buttonUp) == LOW && digitalRead(buttonDown) == LOW) {
    if (buttonPressTime == 0) { // if not already started
      buttonPressTime = millis(); // start timer
    } else if (millis() - buttonPressTime >= 2000) { // if 2 seconds have passed
    Serial.println("Exiting");
      lcd.clear();
      lcd.setCursor(5,1);
      lcd.print("Exiting...");
      delay(1000);
      secretMenuActive = false;
      buttonPressTime = 0; // reset timer
      updateLCD = true;
    } else if (millis() - buttonPressTime >= 3000) { 
      Serial.print("pritruko palaikyt nulinam laikmati knopkem");// if 3 seconds have passed
      buttonPressTime = 0;
    }
  }

  }
}
//----------------------------------------Secret menu end

//Load values
void loadValues() {

      Serial.println("-Loading Values");

      for (int i = 0; i < numSolenoids; i++) {
        Offsets[i] = EEPROM.read(18 + i * 3) * 10;
        Intervals[i] = EEPROM.read(i * 3) * 10;
        timers[i] = Timer(Intervals[i]); // Initialize the timers
        Delays[i] = Delay(Offsets[i]);   // Initialize the delays
      }

}


void startAllDelays() {
  for (int i = 0; i < numSolenoids; i++) {
    Delays[i].start();
  }
  Serial.println("--Delays started");
}

void updateRelayStates() {
  //Serial.println("---Updating Relays");
  for (int i = 0; i < numSolenoids; i++) {
    if (Delays[i].event()) {
      Offsets[i] = EEPROM.read(18 + i * 3) * 10;
      Intervals[i] = EEPROM.read(i * 3) * 10;
      timers[i].start();
      relayStates[i] = HIGH;
      digitalWrite(relayPins[i], relayStates[i]);
      Delays[i].stop();
    }

    if (timers[i].event()) {
      relayStates[i] = LOW;
      digitalWrite(relayPins[i], relayStates[i]);
      timers[i].stop();
    }
  }
}
//-------------------------------------------------------------------------------
void startSimulation() {

  if (simulateOption) {

    Serial.println("----Starting simulation----");

    loadValues();
    startAllDelays();
    updateRelayStates();
    Serial.println("---Updating relay states");
    Serial.println("----Simulation completed----");
    updateLCD = true;
  } else {
    Serial.println("Stand By mode.....ping every 10 seconds to show code working");
    delay(10000);
  }

}

//--------------------------------------------Trying to get Simulation to work on toggle....

void startCleaning() {
  // Open all relays
  for (int i = 0; i < numSolenoids; i++) {
    relayStates[i] = HIGH;
    digitalWrite(relayPins[i], HIGH);
  }
    updateLCD = true; // Set the flag to update the LCD

}

void stopSimulation() {
  // Add logic to stop the simulation
  digitalWrite(Switch, HIGH);
  Serial.println("-------------Simulation stopped----------------");
      updateLCD = true; // Set the flag to update the LCD
}

void stopCleaning() {
  // Add logic to stop the cleaning
  for (int i = 0; i < numSolenoids; i++) {
    relayStates[i] = LOW;
    digitalWrite(relayPins[i], relayStates[i]);
  }
    updateLCD = true; // Set the flag to update the LCD
    Serial.println("Cleaning mode stopped. Returning to normal state.");
}
//--------------------------------------------------------------------------------

void updateLCDContent() {
  lcd.clear();
  lcd.setCursor(5, 0);
  lcd.print("Solenoid ");
  lcd.print(cursorPosition + 1);
  lcd.setCursor(0, 1);
  lcd.print("Offset : ");
  if (editMode && editItem == 0) {
    lcd.print("<< ");
    lcd.print(Offsets[cursorPosition] / 10);
    lcd.print(" >> %");
  } else {
    lcd.print(Offsets[cursorPosition] / 10);
    lcd.print(" %");
  }
  lcd.setCursor(0, 2);
  lcd.print("Opening: ");
  if (editMode && editItem == 1) {
    lcd.print("<< ");
    lcd.print(Intervals[cursorPosition] / 10);
    lcd.print(" >> cs");
  } else {
    lcd.print(Intervals[cursorPosition] / 10);
    lcd.print(" cs");
  }
  updateLCD = false; // Reset the flag
}

void displayHelp() {
  Serial.println("List of available commands:");
  Serial.println("\"S\": Start all delays for the solenoids.");
  Serial.println("\"6\": Move the cursor up.");
  Serial.println("\"4\": Move the cursor down.");
  Serial.println("\"8\": Toggle the edit mode.");
  Serial.println("\"EEPROM RESET\": Reset all EEPROM values to zero.");
  Serial.println("\"DEFAULT\": Set the Offsets and Intervals to their default values and save them to the EEPROM.");
  Serial.println("\"Print\": Prints a table of current values saved on Solenoids.");
  Serial.println("\"Menu\": Secret menu to Operate Simulation and Cleaning.");
  Serial.println("\"Exit\": Exit Secret Menu");
}

void moveCursorUp() {
  if (editMode) {
    // Increment the value of the selected item
    if (editItem == 0) {
      Offsets[cursorPosition] = min(1500, Offsets[cursorPosition] + 10);
    } else {
      Intervals[cursorPosition] += 10;
    }
  } else {
    // Move the cursor up
    cursorPosition = min(numSolenoids - 1, cursorPosition + 1);
  }
  updateLCD = true; // Set the flag to update the LCD
}

void moveCursorDown() {
  if (editMode) {
    // Decrement the value of the selected item
    if (editItem == 0) {
      Offsets[cursorPosition] = max(0, Offsets[cursorPosition] - 10);
    } else {
      Intervals[cursorPosition] = max(0, Intervals[cursorPosition] - 10);
    }
  } else {
    // Move the cursor down
    delay(1000);
    cursorPosition = max(0, cursorPosition - 1);
  }
  updateLCD = true; // Set the flag to update the LCD
}

void toggleEditMode() {
  if (editMode) {
    // Switch to the next item to edit
    editItem = (editItem + 1) % 2;
    if (editItem == 0) {
      // If we've cycled back to the first item, exit edit mode and display a "Saved" message
      editMode = false;
      displaySavedMessage();
      delay(2000);
      saveValuesToEEPROM();
      eepromUpdated = true;
    }
  } else {
    // Enter edit mode
    editMode = true;
  }
  updateLCD = true; // Set the flag to update the LCD
}

void displaySavedMessage() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("*******************");
  lcd.setCursor(5, 1);
  lcd.print("Solenoid ");
  lcd.print(cursorPosition + 1);
  lcd.setCursor(7, 2);
  lcd.print("Saved!");
  lcd.setCursor(0, 3);
  lcd.print("*******************");
}

void saveValuesToEEPROM() {
  for (int i = 0; i < numSolenoids; i++) {
    EEPROM.update(18 + i * 3, Offsets[i] / 10);
    EEPROM.update(i * 3, Intervals[i] / 10);
  }

  Serial.println("Values saved to EEPROM.");
  printEEPROMValues();
}

void setDefaultValues() {
  for (int i = 0; i < numSolenoids; i++) {
    if (i == 0 || i == 2 || i == 4) { // Solenoids 1, 3, 5
      Offsets[i] = 0;
    } else {                         // The rest of the solenoids
      Offsets[i] = 500;
    }
    Intervals[i] = 60;
    EEPROM.update(18 + i * 3, Offsets[i] / 10);
    EEPROM.update(i * 3, Intervals[i] / 10);
  }
  Serial.println("Default Values Saved to EEPROM.");
  printEEPROMValues();
  updateLCD = true; // Set the flag to update the LCD
}

void resetEEPROMValues() {
  // Zero out the EEPROM values
  for (int i = 0; i < numSolenoids * 2 + 1; i++) {
    EEPROM.update(i, 0);
  }
  Serial.println("EEPROM VALUES ZEROED");
  printEEPROMValues();
  updateLCD = true; // Set the flag to update the LCD
}

I recommend to separate menus from functionality.

enum menuTypes    {STANDARD, SECRET} menuMode;
enum programTypes {NORMAL, SIMULATION} programMode;

void setup() {
  // put your setup code here, to run once:
  // Added some initialization here ;-)
  menuMode = STANDARD;
  programMode = NORMAL;
}

void loop() {
  menus();
  functionality();
  allways();
}

void menus() {
  switch (menuMode) {
    case STANDARD: handleStandardMenu();
      break;
    case SECRET: handleSecretMenu();
      break;
  }
}

void functionality() {
  switch (programMode) {
    case NORMAL: handleNormalCode();
      break;
    case SIMULATION: handleSimulationCode();
      break;
  }
}

void allways() {
  handleSerial();
  handleButtons();
  handleLCD();
}


void handleStandardMenu(){};
void handleSecretMenu(){};
void handleNormalCode(){};
void handleSimulationCode(){};

void handleSerial(){};
void handleButtons(){};
void handleLCD(){};


Make sure none of those parts is blocking the sketch ...

(Edit: Now a compiling version ... :wink: )

  • In the menu functions you only set control variables

  • The "functionality()" uses the control variables to perform the appropriate action.

  • The "allways()" part does everything that is independend from menu/functionality like reading data from serial or printing to lcd.

  • And added some initialization in setup()
    `

1 Like

It might be interesting for you to evaluate the concept of a state machine; see here for a simple example

https://forum.arduino.cc/t/help-with-code-if-possible/1201603/9

Wow looks like i should have started with this. Guess I will have to rewrite the whole code, but first i need to play around with this to see how it behaves ! thank you for this :slight_smile:

Question, is there a way for me to code Libraries and just include them in to my code ? I would love to get rid of some things like time classes that I've done. Could call it DelayClass. And possibly some other features like Saving, Updating, Reseting, Displaying, Loading values could go in to a library called DataHandling or something like that.

wouldn't this make my code cleaner, easier to work and easier to debug ?

In theory, yes. That's the hole point of libraries. It is a fairly simple dance. Google "create arduino library" for how you can take any of your favorite stuff and make your own library.

In practice, maybe somewhat less useful; designing a library that can be used all over the place without modification (which necessity would defeat the purpose) is a challenge. Making it perfect enough so you wouldn't be in there looking for your errors anyway is even more important, and difficult.

In the middle ground, some ppl use the tab capability of the IDE to put large chunks of code off to the side, so to speak, code they don't wanna see or need to scroll past or whatever.

So look into using tabs.

Some, I include myself, just put all the code into one giant tab, the main *.ino for the project, and make use of a decent text editor to fly around in it, or swim. Some code is like clouds, some more like soup. But everything's in there somewhere.

a7

1 Like

See @alto777's post

Just move your class definitions into a separate file (e.g. myclasses.h), save that in the same directory as your main sketch. The IDE will come up with those files in tabs.

Add

#include "myclasses.h" 

to your main sketch...

(And don't forget to write

#pragma once

on top of your .h file. It will make sure it is only included once in a project ...)

1 Like

I was wondering why I never make libraries. I cut and paste from earlier code, and other ppl's for sure, but it's always cut, paste and tweak - I never seem to write a good general bit of code that can be reused without slight modifications.

It also occurred to me that if you do make good and extensive use of your own libraries, your code becomes slightly less portable.

If you come here, for example, you will have to ask us not only to look into your code, but acquire and install your personal libraries what made the code sensible.

I think that might cut down on the attention your problem(s) might get.

a7

That is true, I agree. So thinking forwards I'm better off having a SOUP code in development phase.

Try tidy it up with custom libraries when finished for ease of use for myself, BUT NOT for Public forum troubleshooting.

So Advantages and Disadvantages. Being a n00b I think I will stick with the SOUP :smiley:

1 Like

I was worried at some point that I was not doing things correctly, but a forum heavy happened to say, or words to the effect,

  • I don't look at anyone's code that isn't in a single tab
  • I put everything in one file myself

and

  • I do not consider myself a proper C programmer

So I decided to relax. If I can ever become the kind of programmer she is, improper or not, I may revisit the issue.

Don't hold your breath. :expressionless:

a7

I tried, I am a bit over my head now with this code but somehow....... SOMEHOW everything works as I want. Even the simulation, the menu handling, the lock screen to prevent button pushes when my tractor will be moving.

Here is the latest of my code that I have rewritten, think it still needs a bit of a tidy up there probably is a lot of unnecessary variables but ill get to them ( probably never if it works :smiley: )

#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

// LCD setup
LiquidCrystal_I2C lcd(0x27, 20, 4);
//Lock icon
byte customChar[] = {
  B01110,
  B10001,
  B10001,
  B10001,
  B11111,
  B11011,
  B11011,
  B11111
};

// Define the relay pins
const int numSolenoids = 6;
const int relayPins[numSolenoids] = {22, 23, 24, 25, 26, 27};
int relayStates[numSolenoids] = {LOW, LOW, LOW, LOW, LOW, LOW};
const unsigned long bootloaderDuration = 2000;

// Define the button pins
const int buttonUp = 12;
const int buttonDown = 13;
const int buttonSelect = 11;
const int Switch = 53; // Switch pin

// Global variables for menu handling
unsigned long buttonPressStartTime = 0;
bool inSecretMenuToggle = false;
int currentSolenoid = 0;
bool editMode = false;
int editItem = 0; // 0 for Offset, 1 for Duration
bool virtualSwitchState = false; // false means switch is OFF, true means ON

// Locking Global variables
unsigned long lastSwitchTime = 0;
int switchActivationCount = 0;
bool isLocked = false;
const unsigned long lockInterval = 5000; // 5 seconds
const int activationThreshold = 3; // Minimum activations within 5 seconds

//Secret menu variables
bool secretMenuActive = false;
int secretMenuPage = 1; // 1 for Simulation, 2 for Cleaning
int secretMenuPosition = 0; // 0 for ON, 1 for OFF
bool simulateOption = false;
bool cleaningOption = false;
unsigned long lastSecretMenuUpdate = 0;
unsigned long lastSimulationTime = 0;  //We will see if i can finaly get it work

// Relay timing variables
unsigned long Offsets[numSolenoids];
unsigned long Durations[numSolenoids];
unsigned long maxInterval = 0;

// Define LCD update
volatile bool updateLCD = true;

enum menuTypes { STANDARD, SECRET } menuMode;
enum programTypes { NORMAL, SIMULATION } programMode;

class Timer {
  // ... Timer class definition ...
    private:
    unsigned long startTime;
    unsigned long interval;
    bool running;

  public:
    Timer(unsigned long interval = 0) {
      this->interval = interval;
      running = false;
    }

    void start() {
      startTime = millis();
      running = true;
    }

    void stop() {
      running = false;
    }

    bool isRunning() {
      return running;
    }

    bool event() {
      if (running && millis() - startTime >= interval) {
        startTime = millis(); // reset the start time
        return true;
      }
      return false;
    }
};

class Delay {
  // ... Delay class definition ...
    private:
    unsigned long startDTime;
    unsigned long delayTime;
    bool running;

  public:
    Delay(unsigned long delayTime = 0) {
      this->delayTime = delayTime;
      running = false;
    }

    void start() {
      startDTime = millis();
      running = true;
    }

    void stop() {
      running = false;
    }

    bool isRunning() {
      return running;
    }

    bool event() {
      if (running && millis() - startDTime >= delayTime) {
        startDTime = millis(); // reset the start time
        return true;
      }
      return false;
    }
};

Timer timers[numSolenoids];
Delay delays[numSolenoids];

void setup() {
  // Begin serial communication
  Serial.begin(9600);
  
  
  // Initialize the LCD
  lcd.init();
  lcd.backlight();
  displayBootloader();
  lcd.createChar(0, customChar);

  // Initialize relay pins, button pins and load values from EEPROM
  for (int i = 0; i < numSolenoids; i++) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], relayStates[i]);
  }

  // Set up the switch pin
  pinMode(Switch, INPUT_PULLUP);
  lastSwitchTime = millis();

    // Set pullup on buttons
  pinMode(buttonUp, INPUT_PULLUP);
  pinMode(buttonDown, INPUT_PULLUP);
  pinMode(buttonSelect, INPUT_PULLUP);

  // Calculate, Load and print values from EEPROM
  loadValuesFromEEPROM();
  findMaxSolenoidTime();
  printEEPROMValues();

Serial.println("Changing menuMode to STANDARD");
  menuMode = STANDARD;
Serial.println("Changing programMode to NORMAL");
  programMode = NORMAL;
delay(2000);

}

void loop() {

  handleLocking();
  menus();
  functionality();
  always();
//----------------will try simulate this way
      findMaxSolenoidTime();
      if (menuMode == SECRET && simulateOption) {
        static unsigned long lastToggleTime = 0;
        if (millis() - lastToggleTime >= maxInterval) {
            lastToggleTime = millis();
            virtualSwitchState = !virtualSwitchState; // Toggle the virtual switch state
            simulateSwitchAction(virtualSwitchState);
        }
    }


}

void handleLocking() {
  // Check for switch activation
  if (digitalRead(Switch) == LOW) { // Assuming LOW when operated
    if (millis() - lastSwitchTime > lockInterval) {
      // More than 5 seconds since last activation, reset counter
      switchActivationCount = 0;
    }
    lastSwitchTime = millis(); // Update the last switch activation time
    switchActivationCount++; // Increment activation count

    if (switchActivationCount >= activationThreshold) {
      isLocked = true; // Lock the screen
      Serial.println("**** L O C K E D ****");
    }
  }

  // Unlock the screen if the switch has not been activated for more than 5 seconds
  if (millis() - lastSwitchTime > lockInterval && isLocked) {
    isLocked = false;
          Serial.println("**** U N L O C K E D ****");
          updateLCD= true;
    // Optionally, you can update the standard screen here if needed
    // updateStandardScreen();
  }

  // Display the lock screen if the system is locked
  if (isLocked) {
    lockedScreen(); // Function to handle lock screen display
  }
}

  //---test
void findMaxSolenoidTime() {
    maxInterval = 0;
    for (int i = 0; i < numSolenoids; i++) {
        unsigned long totalTime = Offsets[i] + Durations[i];
        if (totalTime > maxInterval) {
            maxInterval = totalTime;
        }
    }
    return maxInterval;
}
  //---tes end

void menus() {
  // ... Menu handling logic ...
    switch (menuMode) {
    case STANDARD: 
      handleStandardMenu(); 
      break;
    case SECRET: 
      handleSecretMenu(); 
      break;
  }
}

void functionality() {
  // ... Functionality handling logic ...
    if (programMode == NORMAL) {
    //Serial.println("Changing menuMode to SANDARD in functionality"); <<---THE (expletive deleted) CULPRIT ALL ALONG.....
    //menuMode = STANDARD;
    handleNormalCode();
  } else     if (programMode == SIMULATION) {
        handleSimulationCode();
     }
  }

void always() {
  handleSerial();
  handleButtons();
  handleLCD();
  
  // Check for simultaneous button press
  if (digitalRead(buttonUp) == LOW && digitalRead(buttonDown) == LOW) {
    if (!inSecretMenuToggle) {
      // Start timing the button press
      buttonPressStartTime = millis();
      inSecretMenuToggle = true;
    } else if (millis() - buttonPressStartTime > 1000) { // 1 second hold
      // Toggle between STANDARD and SECRET menus
      menuMode = (menuMode == STANDARD) ? SECRET : STANDARD;
      inSecretMenuToggle = false; // Reset the toggle state
    }
  } else {
    inSecretMenuToggle = false;
  }

   // Lock and Unlock Mechanism

}

void handleStandardMenu() {

  if(updateLCD){
    updateLCDContent(); 
  }

}

void handleSecretMenu() {
    if (!secretMenuActive) {
        secretMenuActive = true;
        lcd.clear();
        lcd.setCursor(2, 0);
        lcd.print("* Secret  Menu *");
        delay(1000);
        menuMode = SECRET;
        updateLCD = true;

    }
}

void handleNormalCode() {
  // ... Normal operation logic ...
    static bool switchPressed = false;
  
  if (digitalRead(Switch) == LOW) {
    if (!switchPressed) { // Trigger only on the edge
      switchPressed = true;
      startRelaySequence();
    }
  } else {
    switchPressed = false;
  }

  updateRelays();
}

//-------------------new functrion to try virtual switch
void simulateSwitchAction(bool state) {
    if (state) {
        // Logic to handle switch ON
            startRelaySequence();
    } else {
        // Logic to handle switch OFF
            
    }

    updateRelays();
}

//--------------------------

void handleSimulationCode() {
  // ... Normal operation logic ...
    static bool switchPressed = false;
  
  if (digitalRead(Switch) == LOW) {
    if (!switchPressed) { // Trigger only on the edge
      switchPressed = true;
      startRelaySequence();
    }
  } else {
    switchPressed = false;
  }

  updateRelays();
}

void startCleaning() {
  // Open all relays
  for (int i = 0; i < numSolenoids; i++) {
    relayStates[i] = HIGH;
    digitalWrite(relayPins[i], HIGH);
  }
    //updateLCD = true; // Set the flag to update the LCD

}

void stopCleaning() {
  // Add logic to stop the cleaning
  for (int i = 0; i < numSolenoids; i++) {
    relayStates[i] = LOW;
    digitalWrite(relayPins[i], relayStates[i]);
  }
    cleaningOption = false;
    //updateLCD = true; // Set the flag to update the LCD
    //Serial.println("Cleaning mode stopped. Returning to normal state.");
}

void handleSerial() {
  // ... Serial communication handling ...
      if (Serial.available() > 0) {
        String command = Serial.readStringUntil('\n');
        command.trim(); // Remove any whitespace

         if (command == "menu") {
            // Enter Secret Menu
            handleSecretMenu();
            secretMenuActive = true;
            updateLCD = true;
        } else if (command == "exit") {
            // Exit Secret Menu
            menuMode = STANDARD;
            secretMenuActive = false;
            updateLCD = true;
        }   else if (command == "DEFAULT") {
            setDefaultValues();
        }   else if (command == "ZERO") {
            resetEEPROMValues();
        } else if (command == "EEPROM RESET") {
            resetEEPROMValues();
        } else if (command == "Print") {
            printEEPROMValues();
        } else         if (command == "Info") {
            printBuildInfo();
        }// New command to set solenoid values
        else if (command.indexOf('/') != -1) { // Check if the command contains '/'
            int firstSlashIndex = command.indexOf('/');
            int lastSlashIndex = command.lastIndexOf('/');

            if (firstSlashIndex != lastSlashIndex) {
                int solenoid = command.substring(0, firstSlashIndex).toInt();
                int offset = command.substring(firstSlashIndex + 1, lastSlashIndex).toInt() * 10;
                int opening = command.substring(lastSlashIndex + 1).toInt() *10 ;

                if (solenoid > 0 && solenoid <= numSolenoids) {
                    Offsets[solenoid - 1] = offset;
                    Durations[solenoid - 1] = opening;
                    saveValuesToEEPROM();
                    Serial.println("Solenoid " + String(solenoid) + " updated: Offset = " + String(offset) + ", Opening = " + String(opening));
                    updateLCD = true;
                } else {
                    Serial.println("Invalid solenoid number.");
                }
            } else {
                Serial.println("Invalid command format.");
            }
        }
    }
}

void handleButtons() {
    //Serial.print("Current menuMode in handleButtons: ");
    //Serial.println(menuMode == SECRET ? "SECRET" : "STANDARD");    FOR DEBUGING
    //delay(2000);    
    if (isLocked) {
        return;
    }

    // Check if we are in the secret menu
    if (menuMode == SECRET) {
        // Handle buttons specifically for SECRET menu
        if (digitalRead(buttonSelect) == LOW) {
          //Serial.println("SECRET Menu: Select Pressed");
            // Logic for Select button in SECRET menu
            secretMenuPosition = !secretMenuPosition; // Toggle ON/OFF option
            updateLCD = true;
            delay(300); // debounce delay
        } else if (digitalRead(buttonUp) == LOW) {
            // Logic for UP button in SECRET menu
            secretMenuPage = max(1, secretMenuPage - 1); // Decrease page number, minimum is 1
            updateLCD = true;
            delay(200); // debounce delay
        } else if (digitalRead(buttonDown) == LOW) {
            // Logic for DOWN button in SECRET menu
            secretMenuPage = min(2, secretMenuPage + 1); // Increase page number, maximum is 2 (or more if you have more pages)
            updateLCD = true;
            delay(200); // debounce delay
        }

            // Additional logic based on secretMenuPage and secretMenuPosition
            if (secretMenuPage == 1) {
                simulateOption = (secretMenuPosition == 1);
                if (simulateOption) {
                    programMode = SIMULATION;
                } else {
                    programMode = NORMAL;
                }
            } else if (secretMenuPage == 2) {
                cleaningOption = (secretMenuPosition == 1);
                
             if (cleaningOption) {
                startCleaning();
            } else {
                stopCleaning();
            }
             
            }
      

    } else if (menuMode == STANDARD) {
        // Handle buttons for STANDARD menu
        if (digitalRead(buttonUp) == LOW) {
          //Serial.println("Standard Menu: Up Pressed");
            if (editMode) {
                if (editItem == 0) 
                Offsets[currentSolenoid] = (Offsets[currentSolenoid] + 10) % 1010; // Roll over at 100
                else if (editItem == 1) 
                Durations[currentSolenoid] = (Durations[currentSolenoid] + 10) % 1010; // Roll over at 100
            } else {
                currentSolenoid = (currentSolenoid + 1) % numSolenoids;
            }
            updateLCD = true;
            delay(200); // debounce delay
        }

        if (digitalRead(buttonDown) == LOW) {
          //Serial.println("Standard Menu: Down Pressed");
            if (editMode) {
                if (editItem == 0) { // Offset
                    if (Offsets[currentSolenoid] > 0) {
                        Offsets[currentSolenoid] -= 10;
                    } else {
                        Offsets[currentSolenoid] = 1000; // Roll over to 100
                    }
                } else if (editItem == 1) { // Opening
                    if (Durations[currentSolenoid] > 0) {
                        Durations[currentSolenoid] -= 10;
                    } else {
                        Durations[currentSolenoid] = 1000; // Roll over to 100
                } 
                }
            }
                else  {
                currentSolenoid = (currentSolenoid - 1 + numSolenoids) % numSolenoids;
            }
            updateLCD = true;
            delay(200); // debounce delay
        }
        
            

        if (digitalRead(buttonSelect) == LOW) {
          //Serial.println("Standard Menu: Select Pressed");
            if (editMode) {
                editItem = (editItem + 1) % 2;
                if (editItem == 0) {
                    saveValuesToEEPROM();
                    displaySavedMessage();
                    delay(1000); // Display saved message briefly
                    editMode = false;
                }
            } else {
                editMode = true;
            }
            updateLCD = true;
            delay(300); // debounce delay
        }
      }
}

void handleLCD() {
  // ... LCD update handling ...
    if (updateLCD) {
    updateLCDContent();
    updateLCD = false;  // Reset the flag after updating
  }
}

void updateLCDContent() {
  // ... LCD content update logic ... 
      lcd.clear();
  //--------------------------------------if secret menu
  if (menuMode == SECRET) {
      if (millis() - lastSecretMenuUpdate > 500) { // Update every 500 ms
        lcd.clear();
        if (secretMenuPage == 1) {
            lcd.setCursor(5, 0);
            lcd.print("Simulation");
            lcd.setCursor(2, 2);
            lcd.print(secretMenuPosition == 1 ? "<< ON >>  OFF   " : "   ON  << OFF >>");
        } else if (secretMenuPage == 2) {
            lcd.setCursor(6, 0);
            lcd.print("Cleaning");
            lcd.setCursor(2, 2);
            lcd.print(secretMenuPosition == 1 ? "<< ON >>  OFF   " : "   ON  << OFF >>");
        }
        lastSecretMenuUpdate = millis();
    }
  }

  //----------------------------------------------------

  //--------------------------------------if normal menu
  if (menuMode == STANDARD) {
  lcd.clear();
  lcd.setCursor(5, 0);
  lcd.print("Solenoid ");
  lcd.print(currentSolenoid + 1);
  lcd.setCursor(0, 1);
  lcd.print("Offset : ");
  if (editMode && editItem == 0) {
    lcd.print("<< ");
    lcd.print(Offsets[currentSolenoid] / 10);
    lcd.print(" >> %");
  } else {
    lcd.print(Offsets[currentSolenoid] / 10);
    lcd.print(" %");
  }
  lcd.setCursor(0, 2);
  lcd.print("Opening: ");
  if (editMode && editItem == 1) {
    lcd.print("<< ");
    lcd.print(Durations[currentSolenoid] / 10);
    lcd.print(" >> cs");
  } else {
    lcd.print(Durations[currentSolenoid] / 10);
    lcd.print(" cs");
  }
  updateLCD = false; // Reset the flag
}
}

void displaySavedMessage() {
  // ... Display saved message logic ...
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("*******************");
  lcd.setCursor(5, 1);
  lcd.print("Solenoid ");
  lcd.print(currentSolenoid + 1);
  lcd.setCursor(7, 2);
  lcd.print("Saved!");
  lcd.setCursor(0, 3);
  lcd.print("*******************");
}

void lockedScreen() {
  // Mesage to apear on LCD when Switch activated to prevent button presses
        lcd.setCursor(0, 0);
        lcd.print("********************");
        lcd.setCursor(0, 1);
        lcd.print("*System Operational*");
        lcd.setCursor(0, 2);
        lcd.print("*     Locked  ");
        lcd.write(0);
        lcd.print("    *");
        lcd.setCursor(0, 3);
        lcd.print("********************"); 
}

void saveValuesToEEPROM() {
  // ... EEPROM value saving logic ...
    for (int i = 0; i < numSolenoids; i++) {
    EEPROM.update(18 + i * 3, Offsets[i] / 10);
    EEPROM.update(i * 3, Durations[i] / 10);
  }

  Serial.println("Values saved to EEPROM.");
  printEEPROMValues();
}

void loadValuesFromEEPROM() {
  // ... EEPROM value loading logic ...

      Serial.println("-Loading Values");

      for (int i = 0; i < numSolenoids; i++) {
        Offsets[i] = EEPROM.read(18 + i * 3) * 10;
        Durations[i] = EEPROM.read(i * 3) * 10;
        timers[i] = Timer(Durations[i]); // Initialize the timers
        delays[i] = Delay(Offsets[i]);   // Initialize the delays
      }
}

void startRelaySequence() {
  // ... Start relay sequence logic ...
    for (int i = 0; i < numSolenoids; i++) {
    delays[i] = Delay(Offsets[i]);
    delays[i].start();
    timers[i] = Timer(Durations[i]);
  }
}

void updateRelays() {
  // ... Update relays logic ...
    for (int i = 0; i < numSolenoids; i++) {
    if (delays[i].event()) {
      relayStates[i] = HIGH;
      digitalWrite(relayPins[i], relayStates[i]);
      delays[i].stop();
      timers[i].start();
    }

    if (timers[i].event()) {
      relayStates[i] = LOW;
      digitalWrite(relayPins[i], relayStates[i]);
      timers[i].stop();
    }
	}
}

void printEEPROMValues() {
  Serial.println("------------------------------------------------");
  for (int i = 0; i < numSolenoids; i++) {
    Serial.print("Solenoid ");
    Serial.print(i + 1);
    Serial.print(" Opening Time [ ");
    Serial.print(EEPROM.read(i * 3) * 10);
    Serial.print(" ] Offset [ ");
    Serial.print(EEPROM.read(18 + i * 3) * 10);
    Serial.println(" ]");
  }
  Serial.println("------------------------------------------------");
  Serial.print("Max Interval is : "); 
  Serial.print(maxInterval / 10); 
  Serial.print(" cs = ");
  Serial.print(maxInterval);
  Serial.println(" ms");
  Serial.println("------------------------------------------------");
}

void setDefaultValues() {
  for (int i = 0; i < numSolenoids; i++) {
    if (i == 0 || i == 2 || i == 4) { // Solenoids 1, 3, 5
      Offsets[i] = 0;
    } else {                         // The rest of the solenoids
      Offsets[i] = 500;
    }
    Durations[i] = 60;
    EEPROM.update(18 + i * 3, Offsets[i] / 10);
    EEPROM.update(i * 3, Durations[i] / 10);
  }
  Serial.println("Default Values Saved to EEPROM.");
  updateLCD = true; // Set the flag to update the LCD
}

void resetEEPROMValues() {
  // Zero out the EEPROM values
  for (int i = 0; i < numSolenoids * 2 + 1; i++) {
    EEPROM.update(i, 0);
  }
  Serial.println("EEPROM VALUES ZEROED");
  updateLCD = true; // Set the flag to update the LCD
}

void printBuildInfo() {
    Serial.println(" -------------------------------------------------");
    Serial.println("|              Build Information                  |");
    Serial.println(" -------------------------------------------------");
    Serial.println("| Creator:              Kornelijus                |");
    Serial.println("| Date Code Amended:    2023-12-30                |");  
    Serial.println("| Board:                Arduino Mega 2560 Rev3E   |");
    Serial.println("| Board Serial Number:  4423631373535190F130      |");
    Serial.println("| Build Version      :  v2.0.1 beta               |"); 
    Serial.println("| Sketch uses        :  14332 bytes (5%)          |"); 
    Serial.println("| Global variables   :  1736 bytes (21%)          |");   
    Serial.println(" -------------------------------------------------");
}

void displayBootloader() {
    const int loadingBarLength = 20; // Length of the loading bar (depends on your LCD)
    unsigned long startTime = millis();
    unsigned long endTime = startTime + bootloaderDuration;

    lcd.clear();
    lcd.setCursor(6, 1);
    lcd.print("Loading!");

    while (millis() < endTime) {
        unsigned long currentTime = millis();
        int progress = map(currentTime - startTime, 0, bootloaderDuration, 0, loadingBarLength);
        lcd.setCursor(0, 2);
        lcd.print("|");
        for (int i = 0; i < progress; i++) {
            lcd.print("=");
        }
        for (int i = progress; i < loadingBarLength; i++) {
            lcd.print("");
        }
        lcd.print("|");
        delay(100);
    }
}

This is a Website I used to create custom char
This is a Great topic and Speadsheet too, didn't really use it but have got it if anyone wants it.

Special thanks to @alto777 @ec2021 @sterretje !

Without your help I think I would've chucked it in the hedge. This Forum is amazing !

Now all that is left is to try and CAD a box for my Arduino Mega, 16 Relay Shield, 20x4LCD and slot for the ribbon of the 3 button matrix to go out on the lid bellow LCD.

AND THAT WILL BE A HUMONGOUS CHALANGE !!! Obviously this is a 2024 Project now, let me know if I should start a thread on my journey to CAD enclosure development :smiley: :smiley: :smiley:

Happy Christmas and a Happy New Year !!!

2 Likes