// https://wokwi.com/projects/410224559941422081
// For https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/118
// Example of combining four 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) Loop Timer https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/122?u=davex
*/
////////// 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);

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

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

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

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

}

//======================================================
//======== 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.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.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) // 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 
  }
}
A4988
Reference Y
Reference X
Stepper X and Stepper Y and Stepper Z
Unused Button