// Work in progress
// This moves a set of steppers towards an random vector of locations.
//  https://wokwi.com/projects/413104758423535617
//  for https://forum.arduino.cc/t/controlling-32-or-more-stepper-motors/1280070/104
//
// built from
// https://wokwi.com/projects/390741120778917889
// for https://forum.arduino.cc/t/controlling-many-stepper-motors-beginner/1228155/16
//
// v1: V1_simultaneousSteps.ino.txt tab -- move all steppers at same rate
// v2: Move steppers at independent rates to reach new target at the same time.
// v3: Use Bresenham algorithm to sync axes, so you could accelerate and keep in step.
//
// Pasted together from bits of these:
// https://wokwi.com/projects/356666059490147329 -- non-blocking FastLED
// https://wokwi.com/projects/390478474763185153 -- 6axis RAMPS GCODE interpreter
// https://forum.arduino.cc/t/changing-button-states-from-momentary-to-latch/993706/14 -- handling vectors of parts with structs
//
// Serial plotter shows decreasing error as steppers approach new targets

#include <FastLED.h>

#define NUM_LEDS  32
const int FASTLED_PIN  = 2;
const int SpeedPin = A15;
const int PatternPin = A14;
const int verbose = 4;

// FastLED variables
CRGB leds[NUM_LEDS];
uint8_t patternCounter = 0;
bool isRunning = false;
bool changePattern = false;

void checkTimer();
int reportInterval = 100; // ms

// FIREglobals:
const uint8_t cooling = 55;
const uint8_t sparking = 120;
bool gReverseDirection = false;

//  timing coefficients for fast dummy axis
uint32_t RetargetInterval = 1000; //ms
const int msPerStep = 3;  // if you want acceleration, vary this step rate and the others will follow
// Bresenham fast dummy axis delta
int32_t dx = RetargetInterval / msPerStep; // ghost axis dx for Bresenham Algorithm synchronization

struct Steppers {
  int8_t StepPin;
  int8_t EnablePin;
  int8_t DirPin;
  int8_t MinPin;
  int8_t MaxPin;
  long position;
  long target;
  //  uint32_t lastStep; //
  //  uint32_t interval; // ms between steps
  int32_t dy; // bresenham dy  (ghost axis dx=constant is faster than everything)
  int32_t D; // bresenham Decision sum

} steppers[] = { //
  {22, 3, 4, 5}, //
  {23, 3, 4, 5}, //
  {24, 3, 4, 5}, //
  {25, 3, 4, 5}, //
  {26, 3, 4, 5}, //
  {27, 3, 4, 5}, //
  {28, 3, 4, 5}, //
  {29, 3, 4, 5}, //
  {30, 3, 4, 5}, //
  {31, 3, 4, 5}, //
  {32, 3, 4, 5}, //
  {33, 3, 4, 5}, //
  {34, 3, 4, 5}, //
  {35, 3, 4, 5}, //
  {36, 3, 4, 5}, //
  {37, 3, 4, 5}, //
  {38, 3, 4, 5}, //
  {39, 3, 4, 5}, //
  {40, 3, 4, 5}, //
  {41, 3, 4, 5}, //
  {42, 3, 4, 5}, //
  {43, 3, 4, 5}, //
  {44, 3, 4, 5}, //
  {45, 3, 4, 5}, //
  {46, 3, 4, 5}, //
  {47, 3, 4, 5}, //
  {48, 3, 4, 5}, //
  {49, 3, 4, 5}, //
  {50, 3, 4, 5}, //
  {51, 3, 4, 5}, //
  {52, 3, 4, 5}, //
  {53, 3, 4, 5}, //
};


void updateRetargetInterval(uint32_t newInterval) {
  if (newInterval != RetargetInterval) {
    RetargetInterval = newInterval;
    dx =  RetargetInterval / msPerStep; // set speed to reach the target by the end of the interval
    Serial.print("RetargetInterval=");
    Serial.println(RetargetInterval);

  }
}

uint32_t targetInterval = RetargetInterval;
uint32_t lastLedChange = -targetInterval;


void setup(void) {
  Serial.begin(115200);  // open coms

  FastLED.addLeds<WS2812B, FASTLED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(100);

  for (auto &stepper : steppers) {
    pinMode(stepper.StepPin, OUTPUT);
    pinMode(stepper.DirPin, OUTPUT);
    pinMode(stepper.EnablePin, OUTPUT);
    pinMode(stepper.MinPin, INPUT_PULLUP);
    digitalWrite(stepper.EnablePin, LOW);
  }
  lastLedChange = millis() - targetInterval; // backdate change
  updateRetargetInterval(1000);
}

void loop(void) {
  uint32_t now = millis();
  if (now - lastLedChange >= targetInterval) {
    lastLedChange = now;
    switch (map(analogRead(PatternPin), 0, 1024, 0, 2)) {
      case 0:
        rainbowLoop(1, 17);
        break;
      case 1:
        fireLoop();
        break;
    }

    FastLED.show();
    updateRetargetInterval(map(analogRead(SpeedPin), 0, 1023, 2000, 500));
    targetInterval = RetargetInterval;
  }

  stepperSyncWithLeds(now);
  report();
}

void stepperSyncWithLeds(uint32_t now) {
  static uint32_t last = 0;
  if (now - last < msPerStep) return;
  last = now;
  int ii = 0;
  for (auto &stepper : steppers) {
    long target = leds[ii][0]; // red channel
    if (stepper.target != target) { // new target, set slope
      stepper.target = target;
      long distance_to_go = stepper.target - stepper.position;
      stepper.dy = abs(distance_to_go);
      stepper.D = 2 * stepper.dy - dx;
      Serial.print("NEWTARGET:");
      Serial.print(stepper.dy);Serial.print("/"); Serial.print(dx);
      Serial.print(" ");
      Serial.print(stepper.StepPin); Serial.print(" ");
      Serial.print(stepper.StepPin-ii);
      Serial.print(" #");
      Serial.print(ii); Serial.print(" t:");Serial.print(target); Serial.println();
    }

    if (stepper.position != stepper.target && stepper.D > 0 ) { // time to step
      stepper.D -= 2L * dx; // decrement the Decision rule by the slope effect of one dy
      //      stepper.lastStep = now;
      if (stepper.position < stepper.target) {
        digitalWrite(stepper.DirPin, LOW);
        ++stepper.position;
      } else {
        digitalWrite(stepper.DirPin, HIGH);
        --stepper.position;
      }
      delayMicroseconds(50);
      digitalWrite(stepper.StepPin, HIGH);
      digitalWrite(stepper.StepPin, LOW);
    }
    stepper.D += 2L * stepper.dy; // increment y Decision rule by the slope effect of one of dx
    ++ii; // next stepper
  }
}


void fireLoop() {
  static byte heat[NUM_LEDS];

  // Step 1.  Cool down every cell a little
  for ( int i = 0; i < NUM_LEDS; i++) {
    heat[i] = qsub8( heat[i],  random8(0, ((cooling * 10) / NUM_LEDS) + 2));
  }

  // Step 2.  Heat from each cell drifts 'up' and diffuses a little
  for ( int k = NUM_LEDS - 1; k >= 2; k--) {
    heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
  }

  // Step 3.  Randomly ignite new 'sparks' of heat near the bottom
  if ( random8() < sparking ) {
    int y = random8(3);
    heat[y] = qadd8( heat[y], random8(160, 255) );
  }

  // Step 4.  Map from heat cells to LED colors
  for ( int j = 0; j < NUM_LEDS; j++) {
    CRGB color = HeatColor( heat[j]);
    int pixelnumber;
    if ( gReverseDirection ) {
      pixelnumber = (NUM_LEDS - 1) - j;
    } else {
      pixelnumber = j;
    }
    leds[pixelnumber] = color;
  }
}

void rainbowLoop(uint8_t thisSpeed, uint8_t deltaHue) {     // The fill_rainbow call doesn't support brightness levels.

  uint8_t thisHue = beatsin8(thisSpeed, 0, 255);              // A simple rainbow wave.
  //uint8_t thisHue = beat8(thisSpeed,255);                     // A simple rainbow march.

  fill_rainbow(leds, NUM_LEDS, thisHue, deltaHue);            // Use FastLED's fill_rainbow routine.

}


void report(void) {
  const uint32_t interval = 100;
  static uint32_t last = -interval;
  if (millis() - last < interval) return;
  last += interval;
  if(0){
  for ( auto &stepper : steppers) {
    Serial.print(stepper.target - stepper.position);
    Serial.print(", ");
  }
  Serial.println();
  }
}


void writeStepperJson() {
  // write Wokwi json fragment for the diagram.json file
  char buff[180];
  Serial.println(",");
  for (int ii = 1; ii < 33 ; ++ii) {
    snprintf(buff, 180, R"vogon(     [ "mega:%d", "drv%d:STEP", "green", [  ] ],)vogon", ii + 21, ii);
    Serial.println(buff);
    snprintf(buff, 180, R"vogon(     [ "mega:%d", "drv%d:DIR", "green", [ ] ],)vogon", 4, ii);
    Serial.println(buff);
    snprintf(buff, 180, R"vogon(     [ "mega:%d", "drv%d:ENABLE", "green", [ ] ],)vogon", 3, ii);
    Serial.println(buff);
  }
}
A4988
A4988
A4988
Speed
Pattern
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988
A4988