Language: Arduino C++; Arduino Uno

This code is licensed under MIT--Some rights reserved.
© Damien Boisvert (AlphaGameDeveloper) 2023

/*
  Low power car.  Code by Damien Boisvert.
  
  This program is licensed under MIT.

Copyright (c) 2023 Damien Boisvert (AlphaGameDeveloper)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

// settings
const int meters = 1;
const int metertime = 12000;
// --------
const int trigbtn = 5;
const int buzzer = 6;
const int motor = 8;
const int startDelayTime = 5;
bool icanhasstarted = false;


void buzz() {
  digitalWrite(buzzer, HIGH);
  delay(200);
  digitalWrite(buzzer, LOW);
}
// setup() is called once: when the board powers on.
// This function contains code that initalizes the
// board and sets pins via 'pinMode' to be an INPUT
// or an output.  It also sets up a serial connection to 
// the computer so that I can send the 'go' signal to the
// board.
void setup() {
  Serial.begin(9600); // Connect to the computer via 9600 baud Serial
  pinMode(trigbtn, INPUT);
  pinMode(motor, OUTPUT);
  pinMode(buzzer, OUTPUT);
  // Serial.println send a short message to the connected
  // computer.  This is used for debugging and lets me know
  // what the board is thinking.
  Serial.println("Board has been initalized!");
  // Display what pin the motor is listening on
  Serial.print("Motor pin is set to : ");
  Serial.println(motor);
  Serial.print("Current time is set to: ");
  Serial.println(metertime);
  // Inform the user that the board is waiting for a Serial signal.
  Serial.println("Currently waiting for Serial IO signal!");
  
}

// This function is depricated.  It serves no real
// use, but it is still in the code to preserve data
// integrity.  No need to mention it, as it is for testing
// only. (debug)
void debug(String message) {
  Serial.print("debug: ");
  Serial.println(message);
}

// This function waits for the message to start from the
// computer.  This proves to be more reliable than a blind
// delay, but it does require a serial connection to the computer
// (at least when it starts).  A button based system would be better,
// and is in another (unused) version of the software.  This may be
// merged later.
void serialHang() {
  // set a variable 'hanging', to hold the holding status
  bool hanging = true;
  // This is a loop that runs while code is hanging
  while (hanging == true) {
    if (Serial.available() > 0) {
    // If there is incoming serial data
    String receivedMessage = ""; // Initialize an empty string to store the received message

    // Read the incoming characters one by one until a newline character is received
    while (Serial.available() > 0) {
      // Serial.read() is to actually wait for the prompt to be completed.
      // This is basic Serial/IoT/HTTP standard, and otherwise you would get
      // something like: User enters 'help'; Arduino gets 'he', 'l', 'p'. Serial
      // is complex, because it is not a easy binary 1 or 0; it is analog.
      char incomingChar = Serial.read();
      if (incomingChar == '\n') {
        // Exit the loop when a newline character is received,
        // as it is the end of the message.
        break; 
      }
      // Append the character to the received message
      receivedMessage += incomingChar; 
    }
    
    // This code will run AFTER the serial signal is recieved, so
    // all code after this (or this function), is only called if
    // the user wants the machine to start.  The below line tells
    // the user what the board recieved via Serial IO, but sending
    // a blank message containing nothing (an empty string '\n') would
    // work, too.
    Serial.print("Recieved data: ");
    Serial.println(receivedMessage);
    // Tells the user that the machine will start, and to 'hold on to your butts',
    // because AMAZING STUFF will happen!!!!!
    Serial.println("Hold on to your butt, boss!");

    Serial.println("Holding for 10 seconds... Cut to stop it!");
    Serial.println("Be sure to disconnect Serial IO!");
    Serial.print("Waiting...");
    // Wait the 10 second delay, and display the time remaining via Serial IO.
    // Example: Waiting... 1... 2... 3... 4..., etc.
    for (int i = 1; i < 11; i++) {
      Serial.print(i);
      Serial.print("... ");
      delay(1000); // <-- Wait one second between numbers, otherwise it would all go at once!
    }
    Serial.println("\nSTARTING!");
    // 'return' stops the function.  It is used to stop the hanging loop, and let
    // setup() or loop() (The Arduino standardized main entrypoint functions) take
    // back control of the board.  However, it does not return data, as it is a void
    // function that does not return anything.  It returns 'undefined'.  (End of function.)
    return;
    }
  }
}

// Button hang, which, like serialHang, will stop the code
// until the button is pressed.  This is much better than
// using Serial.INPUT because it makes me not have to use the
// computer, instead.  (TL;DR: No blue wire unless programming)
void buttonHang() {
  // set a boolean variable (bool=true/false) variable that tracks
  // if the button is pressed.  The code will hang until the 'triggered'
  // variable is set to true, at which point the hang will stop when the
  // button is pressed. :)
  bool triggered = false;
  int waitTimeOverride = 60000;
  int millisWaited = 0;
  int checkDelay = 50;
  int overrideWarningTime = 5;
  while (!triggered) { //repeat while triggered ^^ is false
    if (digitalRead(trigbtn) == HIGH) { // check if the button was pressed
      Serial.println("Button recieved: OK!")
      triggered = true;
      // breaks the loop, as loop only continues whilst
      // the variable 'triggered' is set to false, but
      // the above code sets it to true.  The loop will
      // not repeat at the next interval, causing code
      // execution to continue.
    }
    else {
      delay(checkDelay);
      millisWaited += checkDelay;
      if (millisWaited >= waitTimeOverride) {
        Serial.println("OK you lazy bozos ima start now.")
        // 5(overridewarning) second warning
        for (int i = 0; i < overrideWarningTime; i++) {
          // give a warning before i start.  set with:
          // int overrideWarningTime = ____.
          buzz();
          delay(800);
          Serial.print("GOING TO EXPLODE IN ")
          Serial.println(i+1);
        }
        break;
      }
    }
  }
  // This code is executed when the button is pressed, only.
  Serial.println("Hold on to your butts!");
  Serial.println("PinMode LAYOUT:");
  Serial.println("PIN...DEVICE");
  Serial.print(motor);
  Serial.println(", MOTOR");
  Serial.print(trigbtn);
  Serial.println(", TRIGGER BUTTON");
  Serial.print(buzzer);
  Serial.println(", BUZZER");
  Serial.print("\n\nWaiting ");
  Serial.print(startDelayTime);
  Serial.println("seconds to start!");
  for (int i = 0; i < startDelayTime; i++) {
    int a = i+1; // get human-readable time, as it starts at zero.
    Serial.print(a);
    Serial.print("... ")
    if (a < 4) { // 3 or less, buzz!
      buzz();
      delay(800);
    }
    else {
      delay(1000);
    }
  }
  Serial.println("\nTimer OK, starting car!")
  

  
}

// Loop() contains all main program code... It runs on repeat forever, which is great in
// almost every situation aside from this implementation... We want loop() to run once,
// like setup() but not repeat.  What I do to stop the repeating, is that if you look near
// the start of this file, you will find a boolean (true/false) named 'icanhasstarted'.  btw, 
// grammar should not matter in code. :)  Anyway, it is set to false by default, but when loop()
// finishes, it is set to true, as we don't want loop() to run again.  When loop() starts, it
// checks for the 'icanhasstarted' variable to be true, and if it is, the only possibility is that
// loop() has already run, and so it does nothing, and only calls 'return' to exit the function.
// P.S. calling return to exit the function doesn't stop it from running again, so it starts and stops
// ---- immediately, forever!
void loop() {
  // No-loop protection.
  if (icanhasstarted == true) {
    return; // dont run twice!
  }

  serialHang(); // Wait for user's OK before starting the car.


  // Actual code that makes the car move.  It turns on the motors and turns
  // it off after a delay.  This is repeated for each meter.
  for (int i = 0; i < meters; i++) {
    // this is the only place where debug is called.
    debug("new meter!");
    digitalWrite(motor, HIGH); // turn on the motor
    delay(metertime); // wait the time needed to go one meter
    digitalWrite(motor, LOW); // turn off the motor while calculating
    debug("meter completed... waiting!"); // log that a meter is done
    delay(250); // wait for motor to be completely stopped.
  }
  icanhasstarted = true; // make sure that loop() does not call again
}