// https://wokwi.com/projects/410224559941422081
// For https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/118
// Example of combining five non-blocking codes, cut-and-pasted together:
/*
  1) https://wokwi.com/projects/410122241449166849 -- Mobatools Reference2 Example by @MicroBahner
  2) https://wokwi.com/projects/366664987128970241 -- SeveralThingsAtTheSameTime by @Robin2
  3) https://wokwi.com/projects/410037772389117953 -- Some Fastleds by @DaveX
  4) https://wokwi.com/projects/410296132612048897 -- Loop Timer https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/122?u=davex
  5) https://wokwi.com/projects/409963275966630913 -- Serial Command Parser by @nickgammon
*/
////////// Example Reference for MoToStepper - attaching a bipolar stepper with step/dir and enable ////////////
// https://github.com/MicroBahner/MobaTools/blob/master/examples/_Stepper/Stepper_Reference_2/Stepper_Reference_2.ino
// https://wokwi.com/projects/410122241449166849
// for https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754/6


/* An Example with 3 steppers ( 2 unipolar, 1 bipolar ).
    A reference run is started in setup.  2 limit switches are required for this.
    This example does not run on ESP8266
*/


//==============   Declarations

//======================================================
//================= Declarations First program
//======================================================

#include <MobaTools.h>
MoToTimer Pause;
MoToStepper Step_X(4096);                    // X-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Y(4096);                    // Y-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Z(200, STEPDIR);            // Z-Achse, bipolarer Schrittmotor mit Treiber wie A4988, DRV8825 oder vergleichbare
const byte pinRef[] = { A0, A1 };            // pinRef_X, pinRef_Y
const byte nbrOfButtons = sizeof(pinRef);    // Anzahl der angeschlossenen Taster
enum {X_AXIS = 0, Y_AXIS = 1};

//======================================================
//================= Declarations Second program
//======================================================
//Several Things At The Same Time
// -----LIBRARIES

#include <Servo.h>

// ----CONSTANTS (won't change)

const int onBoardLedPin =  14;      // the pin numbers for the LEDs
const int led_A_Pin = 15;
const int led_B_Pin = 16;
const int buttonLed_Pin = 17;

const int buttonPin = 20; // the pin number for the button

const int servoPin = 19; // the pin number for the servo signal

const int onBoardLedInterval = 500; // number of millisecs between blinks
const int led_A_Interval = 2500;
const int led_B_Interval = 4500;

const int blinkDuration = 500; // number of millisecs that Led's are on - all three leds use this

const int buttonInterval = 300; // number of millisecs between button readings

const int servoMinDegrees = 20; // the limits to servo movement
const int servoMaxDegrees = 150;

//------- VARIABLES (will change)

byte onBoardLedState = LOW;             // used to record whether the LEDs are on or off
byte led_A_State = LOW;           //   LOW = off
byte led_B_State = LOW;
byte buttonLed_State = LOW;

Servo myservo;  // create servo object to control a servo

int servoPosition = 90;     // the current angle of the servo - starting at 90.
int servoSlowInterval = 80; // millisecs between servo moves
int servoFastInterval = 10;
int servoInterval = servoSlowInterval; // initial millisecs between servo moves
int servoDegrees = 2;       // amount servo moves at each step
//    will be changed to negative value for movement in the other direction

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousOnBoardLedMillis = 0;   // will store last time the LED was updated
unsigned long previousLed_A_Millis = 0;
unsigned long previousLed_B_Millis = 0;

unsigned long previousButtonMillis = 0; // time when button press last checked

unsigned long previousServoMillis = 0; // the time when the servo was last moved

//======================================================
//==== Declarations  Third program
//======================================================
//
// fastled DaveX from https://wokwi.com/projects/410037772389117953
//
#include "FastLED.h"

const int DaveXButton = 21;
const int DaveXLED = 18;
#define NUM_LEDS1 26
#define DATA_PIN1 35
CRGB array_LED_sconce [NUM_LEDS1];
uint32_t interval = 1000, timeout;
// subset of ledstrip to color
int ledBlink[] = {4, 12, 20, 0, 8, 16, 24};

int fastledState;
CHSV daveXringColor = CHSV(70, 255, 255);

//======================================================
//================= Fourth program Declarations
//======================================================

extern volatile unsigned long timer0_millis; // might be faster than millis()

//======================================================
//================= Fifth program Declarations
//======================================================

// the possible states of the state-machine
typedef enum {  NONE, GOT_R, GOT_S, GOT_G } statesSerial;

// current state-machine state
statesSerial state = NONE;
// current partial number
unsigned int currentValue;


//======================================================
//================= Interaction program Declarations
//======================================================

unsigned long rpm = 20, speed = 100, garrulous = 0;
bool dirtyInteractions = false; // flag for handling interactions
long X_oscillationSpeed = 200;
long Y_oscillationSpeed = 150;

const char help[] PROGMEM = 
  ""
  "// Commands:\n"
  "// Rnnnn sets the Z-axis RPM\n"
  "// Snnn set the X and Y speeds proportionally\n"
  "// G0 disables the loop speed testing\n"
  "// G1 (or nonzero) enables the testing.\n"
  "[Rnnn][Snnn][Gnnn]?\n"
;
// per https://forum.arduino.cc/t/printing-from-progmem-in-simplest-fashion/525923/5
#define CF(x) ((const __FlashStringHelper *)x)

//======================================================
//================= Arduino minimal program requirements
//======================================================

void setup() {
  Serial.begin(115200);
  Serial.println("Starting a Combination of Four Programs");
  setupGammonSerial();
  setupMoba();
  setupSTATST();
  setupDaveX();
  Serial.print(CF(help));
}

void loop() {
  loopGammonSerial();
  loopMoba();
  loopSTATST();
  loopDaveX();
  LoopCounter(); // the function runs as a task, the optimizer will inline the code.
  handleInteractions();
}

//======================================================
//======== renamed functions from component programs
//======================================================

void setupMoba() {
  Serial.println("MobaTools Reference 2 Example  https://wokwi.com/projects/410123341513466881");  // so we know what sketch is running

  for (byte i = 0; i < nbrOfButtons; i++)  {
    pinMode(pinRef[i], INPUT_PULLUP);        // gedrückt = LOW
  }
  Step_X.attach( 12, 11, 10, 9 );            // IN1, IN2, IN3, IN4
  Step_Y.attach( 5, 4, 3, 2 );               // IN1, IN2, IN3, IN4

  Step_Z.attach( 7, 8 );                     // STEPpin, DIRpin
  Step_Z.setSpeed( 800 );                    // = 80 U/Min (motorspezifisch)
  Step_Z.setRampLen( 50 );                   // Beschleunigung (motorspezifisch)
  Step_Z.write(360);                         // Winkel 360 Grad drehen
  // Pause.setTime( 2500 );                     // Dreh- und Pausenzeit
}

void loopMoba() {
  enum {SEEK_ZERO, MOVE_FORWARD, MOVE_BACWARD};
  static byte step_X = SEEK_ZERO;          // Schrittkettenstatus X-Achse
  static byte step_Y = SEEK_ZERO;          // Schrittkettenstatus Y-Achse
  static int16_t angle = 360;

  //-- X-Achse
  switch (step_X) {
    case SEEK_ZERO:
      if (seekZeropoint(Step_X, X_AXIS)) {
        step_X = MOVE_FORWARD;
      }
      break;
    case MOVE_FORWARD:
      if ( !Step_X.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        //        Step_X.setSpeed( 200 );              // = 20 U/Min (motorspezifisch)
        Step_X.setSpeed( X_oscillationSpeed );              // = 20 U/Min (motorspezifisch)
        Step_X.setRampLen( 100 );            // Beschleunigung (motorspezifisch)
        Step_X.writeSteps(4096);
        step_X = MOVE_BACWARD;
      }
      break;
    case MOVE_BACWARD:
      if ( !Step_X.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_X.writeSteps(-4096);
        step_X = MOVE_FORWARD;
      }
      break;
  }
  //-- Y-Achse
  switch (step_Y) {
    case SEEK_ZERO:
      if (seekZeropoint(Step_Y, Y_AXIS)) {
        step_Y = MOVE_FORWARD;
      }
      break;
    case MOVE_FORWARD:
      if ( !Step_Y.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        //        Step_Y.setSpeed( 150 );              // = 20 U/Min (motorspezifisch)
        Step_Y.setSpeed( Y_oscillationSpeed );              // = 20 U/Min (motorspezifisch)
        Step_Y.setRampLen( 100 );            // Beschleunigung (motorspezifisch)
        Step_Y.writeSteps(4096);             // Bewegung starten
        step_Y = MOVE_BACWARD;
      }
      break;
    case MOVE_BACWARD:
      if ( !Step_Y.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_Y.writeSteps(-4096);            // Bewegung starten
        step_Y = MOVE_FORWARD;
      }
      break;
  }
  //-- Z-Achse
  if ( !Step_Z.moving() ) {                  // warten bis die Bewegung abgeschlossen ist
    if ( !Pause.running() ) {                // warten bis Ablauf der Zeit
      Pause.setTime( 5000 );                 // Zeit für Bewegung und Pause
      angle *= -1;                          // andere Richtung
      Step_Z.write(angle);                  // Bewegung starten
    }
  }

}

bool seekZeropoint(MoToStepper &Step, byte axis) {     // keine Kopie, sondern eine Referenz des Schrittmotorobjektes
  enum {REFLEAVE, REFREACH, REACHZERO};
  static byte step[] = {REFLEAVE, REFLEAVE};       // Schrittkettenstatus

  switch (step[axis]) {
    case REFLEAVE:
      Step.setSpeed( 50 );                   // = 5 U/Min (motorspezifisch) Schleichfahrt
      Step.setRampLen( 5 );                  // Beschleunigung (motorspezifisch)
      Step.rotate(1);                        // vom Referentpunkt runterbewegen
      if (digitalRead(pinRef[axis])) {      // Referenzsensor nicht (mehr) betätigt
        Step.doSteps(100);                   // mit etwas Entfernung anhalten
        step[axis]++;
      }
      break;
    case REFREACH:
      if ( !Step.moving() ) {                // warten bis die Bewegung abgeschlossen ist
        Step.rotate(-1);                     // Richtung Referentpunkt bewegen
        step[axis]++;
      }
      break;
    case REACHZERO:
      if (!digitalRead(pinRef[axis])) {     // Ref_X betätigt
        Step.setZero(100);                   // setze Nullpunkt 100 Schritte von Referenzpunkt entfernt
        Step.writeSteps(0);                  // zum Nullpunkt bewegen
        step[axis]++;
      }
      break;
    default:
      step[axis] = REFLEAVE;
      return true;
  }
  return false;
}

//======================================================
//================= Second program functions
//======================================================

//###########################################################

// SeveralThingsAtTheSameTimeRev1.ino
// from:
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/1
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/2
// implemented on a Wokwi simulation per
// https://forum.arduino.cc/t/circuit-with-1-button-and-3-leds/1134897/3
// Wokwi: https://wokwi.com/projects/366664987128970241

// Note that the Adafruit series at https://learn.adafruit.com/multi-tasking-the-arduino-part-1/all-together-now
// expands on this to do two steppers using classes
// An implementation of the class-based Adafruit one in Wokwi is at
// https://wokwi.com/projects/397172393690134529

// An expansion of the BlinkWithoutDelay concept to illustrate how a script
//  can appear to do several things at the same time

// this sketch does the following
//    it blinks the onboard LED (as in the blinkWithoutDelay sketch)
//    it blinks two external LEDs (LedA and LedB) that are connected to pins 12 and 11.
//    it turns another Led (buttonLed connected to pin 10) on or off whenever a button
//       connected to pin 7 is pressed
//    it sweeps a servo (connected to pin 5) back and forth at different speeds

//  One leg of each LED should be connected to the relevant pin and the other leg should be connected to a
//   resistor of 470 ohms or more and the other end of the resistor to the Arduino GND.
//   If the LED doesn't light its probably connected the wrong way round.

//  On my Uno and Mega the "button" is just a piece of wire inserted into pin 7.
//   Touching the end of the wire with a moist finger is sufficient to cause the switching action
//   Of course a proper press-on-release-off button switch could also be used!

//  The Arduino is not capable of supplying enough 5v power to operate a servo
//    The servo should have it's own power supply and the power supply Ground should
//      be connected to the Arduino Ground.

// The sketch is written to illustrate a few different programming features.
//    The use of many functions with short pieces of code.
//       Short pieces of code are much easier to follow and debug
//    The use of variables to record the state of something (e.g. onBoardLedState) as a means to
//       enable the different functions to determine what to do.
//    The use of millis() to manage the timing of activities
//    The definition of all numbers used by the program at the top of the sketch where
//       they can easily be found if they need to be changed

//=======


//========

void setupSTATST() {

  //  Serial.begin(9600);
  Serial.println("Starting SeveralThingsAtTheSameTimeRev1.ino");  // so we know what sketch is running

  // set the Led pins as output:
  pinMode(onBoardLedPin, OUTPUT);
  pinMode(led_A_Pin, OUTPUT);
  pinMode(led_B_Pin, OUTPUT);
  pinMode(buttonLed_Pin, OUTPUT);

  // set the button pin as input with a pullup resistor to ensure it defaults to HIGH
  pinMode(buttonPin, INPUT_PULLUP);

  myservo.write(servoPosition); // sets the initial position
  myservo.attach(servoPin);

}

//=======

void loopSTATST() {

  // Notice that none of the action happens in loop() apart from reading millis()
  //   it just calls the functions that have the action code

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
  //   use the same time for all LED flashes to keep them synchronized

  readButton();               // call the functions that do the work
  updateOnBoardLedState();
  updateLed_A_State();
  updateLed_B_State();
  switchLeds();
  servoSweep();

}

//========

void updateOnBoardLedState() {

  if (onBoardLedState == LOW) {
    // if the Led is off, we must wait for the interval to expire before turning it on
    if (currentMillis - previousOnBoardLedMillis >= onBoardLedInterval) {
      // time is up, so change the state to HIGH
      onBoardLedState = HIGH;
      // and save the time when we made the change
      previousOnBoardLedMillis += onBoardLedInterval;
      // NOTE: The previous line could alternatively be
      //              previousOnBoardLedMillis = currentMillis
      //        which is the style used in the BlinkWithoutDelay example sketch
      //        Adding on the interval is a better way to ensure that succesive periods are identical

    }
  }
  else {  // i.e. if onBoardLedState is HIGH

    // if the Led is on, we must wait for the duration to expire before turning it off
    if (currentMillis - previousOnBoardLedMillis >= blinkDuration) {
      // time is up, so change the state to LOW
      onBoardLedState = LOW;
      // and save the time when we made the change
      previousOnBoardLedMillis += blinkDuration;
    }
  }
}

//=======

void updateLed_A_State() {

  if (led_A_State == LOW) {
    if (currentMillis - previousLed_A_Millis >= led_A_Interval) {
      led_A_State = HIGH;
      previousLed_A_Millis += led_A_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_A_Millis >= blinkDuration) {
      led_A_State = LOW;
      previousLed_A_Millis += blinkDuration;
    }
  }
}

//=======

void updateLed_B_State() {

  if (led_B_State == LOW) {
    if (currentMillis - previousLed_B_Millis >= led_B_Interval) {
      led_B_State = HIGH;
      previousLed_B_Millis += led_B_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_B_Millis >= blinkDuration) {
      led_B_State = LOW;
      previousLed_B_Millis += blinkDuration;
    }
  }
}

//========

void switchLeds() {
  // this is the code that actually switches the LEDs on and off

  digitalWrite(onBoardLedPin, onBoardLedState);
  digitalWrite(led_A_Pin, led_A_State);
  digitalWrite(led_B_Pin, led_B_State);
  digitalWrite(buttonLed_Pin, buttonLed_State);
}

//=======

void readButton() {

  // this only reads the button state after the button interval has elapsed
  //  this avoids multiple flashes if the button bounces
  // every time the button is pressed it changes buttonLed_State causing the Led to go on or off
  // Notice that there is no need to synchronize this use of millis() with the flashing Leds

  if (millis() - previousButtonMillis >= buttonInterval) {

    if (digitalRead(buttonPin) == LOW) {
      buttonLed_State = ! buttonLed_State; // this changes it to LOW if it was HIGH
      //   and to HIGH if it was LOW
      previousButtonMillis += buttonInterval;
    }
  }

}

//========

void servoSweep() {

  // this is similar to the servo sweep example except that it uses millis() rather than delay()

  // nothing happens unless the interval has expired
  // the value of currentMillis was set in loop()

  if (currentMillis - previousServoMillis >= servoInterval) {
    // its time for another move
    previousServoMillis += servoInterval;

    servoPosition = servoPosition + servoDegrees; // servoDegrees might be negative

    if (servoPosition <= servoMinDegrees) {
      // when the servo gets to its minimum position change the interval to change the speed
      if (servoInterval == servoSlowInterval) {
        servoInterval = servoFastInterval;
      }
      else {
        servoInterval = servoSlowInterval;
      }
    }
    if ((servoPosition >= servoMaxDegrees) || (servoPosition <= servoMinDegrees))  {
      // if the servo is at either extreme change the sign of the degrees to make it move the other way
      servoDegrees = - servoDegrees; // reverse direction
      // and update the position to ensure it is within range
      servoPosition = servoPosition + servoDegrees;
    }
    // make the servo move to the next position
    myservo.write(servoPosition);
    // and record the time when the move happened
  }
}

//=====END


//======================================================
//================= Third program functions
//======================================================
// Non-blocking Fastled animation with a button-led function
//
// https://wokwi.com/projects/410037772389117953
// https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/89?u=davex

void setupDaveX()
{
  FastLED.addLeds<WS2812B, DATA_PIN1, GRB>(array_LED_sconce, NUM_LEDS1);
  FastLED.setBrightness(255);
  // Serial.begin(115200);
  pinMode(DaveXLED, OUTPUT);
  pinMode(DaveXButton, INPUT_PULLUP);
  Serial.println("DaveX FastLEDs https://wokwi.com/projects/410037772389117953");  // so we know what sketch is running

}

void loopDaveX()
{
  wiperNB();
  somethingElse();
}

void somethingElse() {
  auto now = millis();
  const typeof(now) interval = 100;
  static typeof(now) last = -interval;
  if (now - last >= interval) {
    last += interval;
    digitalWrite(DaveXLED, !digitalRead(DaveXButton));
  }
}

void wiperNB() {
  // size 7 gives 0-6 up 7 blank, 8-14 down, 15 blank
  const int rangeLen = sizeof(ledBlink) / sizeof(ledBlink[0]);
  const int maxState = 2 * rangeLen + 1;
  if (millis() - timeout > interval) { // wait for interval
    timeout += interval; // restart the "lap" timer
    //  Serial.print(fastledState); Serial.print("/");
    //  Serial.print(maxState); Serial.print(" ");
    switch (fastledState) {
      case 0 ... rangeLen-1: // wiping up
        ledOnProcessing(ledBlink[fastledState]);
        break;
      case rangeLen:
        allLedsOff();
        break;
      case rangeLen+1 ... maxState-1:  // wiping down
        {
          const int LastLedIndex = rangeLen - 1;
          int upwardStep = fastledState - (rangeLen + 1);
          int ledToChange = LastLedIndex - upwardStep;
          ledOnProcessing(ledBlink[ledToChange]);
        }
        break;
      case maxState:
        allLedsOff();
        break;
      default:
        Serial.print(fastledState);
    }
    ++fastledState; // prepare for next step
    if (fastledState > maxState) { // move to next cycle
      fastledState = 0;
    }
    FastLED.show();
  }
}

void ledOnProcessing(int fastledState) {
  array_LED_sconce[fastledState] = daveXringColor;
}

void allLedsOff() {
  fill_solid(array_LED_sconce, NUM_LEDS1, CHSV(0, 0, 0));
}


void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count; // only this function sees this
  static bool lastBit10Set; // only this function sees this
  word millis16 = timer0_millis;

  count++; // adds 1 to count after any use in an expression, here it just adds 1.

  bool currentBit10Set = millis16 & 0x0400; // leverage integral to bool implicit promotion
  if (currentBit10Set != lastBit10Set && garrulous) // 1 second
  {
    //    Serial.print( millis16 ); // 16-bit binary into decimal text, many micros
    //    Serial.write('\t');
    Serial.print( count ); // 32-bit binary into decimal text, load of cycles!
    Serial.println(" loop/sec");
    count = 0; // don't forget to reset the counter
    lastBit10Set = currentBit10Set; //  changes 0<==>1
  }
}


//======================================================
//================= Fifth program functions
//======================================================
// Non-blocking Serial command parsing
//
// Code from https://gammon.com.au/serial

// Example state machine reading serial input
// Author: Nick Gammon
// Date: 17 December 2011


//======================================================
//================= Fifth program functions
//======================================================

// Declarations above^^^

void setupGammonSerial ()
{
  // Serial.begin (115200);
  state = NONE;
  Serial.println("Serial commands Rnnnn Snnnn Gnnnn.");
}  // end of setup

void processRPM (const unsigned int value)
{
  // do something with RPM
  Serial.print ("Z RPM = ");
  Serial.println (value);
  rpm = value;
  dirtyInteractions = true;
} // end of processRPM

void processSpeed (const unsigned int value)
{
  // do something with speed
  Serial.print ("XY Speed = ");
  Serial.println (value);
  speed = value;
  dirtyInteractions = true;
} // end of processSpeed

void processGear (const unsigned int value)
{
  // do something with gear
  Serial.print ("Garrulous/Verbose = ");
  Serial.println (value);
  garrulous = value;
  dirtyInteractions = true;
} // end of processGear

void handlePreviousState ()
{
  switch (state)
  {
    case GOT_R:
      processRPM (currentValue);
      break;
    case GOT_S:
      processSpeed (currentValue);
      break;
    case GOT_G:
      processGear (currentValue);
      break;
  }  // end of switch

  currentValue = 0;
}  // end of handlePreviousState

void processIncomingByte (const byte c)
{
  if (isdigit (c))
  {
    currentValue *= 10;
    currentValue += c - '0';
  }  // end of digit
  else
  {

    // The end of the number signals a state change
    handlePreviousState ();

    // set the new state, if we recognize it
    switch (c)
    {
      case 'R':
        state = GOT_R;
        break;
      case 'S':
        state = GOT_S;
        break;
      case 'G':
        state = GOT_G;
        break;
      default:
        state = NONE;
        break;
    }  // end of switch on incoming byte
  } // end of not digit

} // end of processIncomingByte

void loopGammonSerial ()
{
  while (Serial.available ())
    processIncomingByte (Serial.read ());

  // do other stuff in loop as required

}  // end of loop

//======================================================
//================= Interaction functions
//======================================================

void handleInteractions() {
  // These use the values from the serialGammon parsing
  // to set the speeds of the steppers and the garruloousness
  // and verbosity of the program


  if (dirtyInteractions) {
    // Mobatools takes RPM in tenths of RPM.
    Step_Z.setSpeed( rpm * 10 );
    X_oscillationSpeed = speed * 10;
    Y_oscillationSpeed = speed * 5;
    //immediate
    Step_X.setSpeed(X_oscillationSpeed);
    Step_Y.setSpeed(Y_oscillationSpeed);
    dirtyInteractions = false;
    Serial.println("[Rnn][Snnn][Gnnn]?");
  }
}
A4988
Reference Y
Reference X
Stepper X and Stepper Y and Stepper Z
Unused Button