Analog LED Strip, Rainbow not working

Hey guys,

I got this code running on my old setup with blynk. Now I switched to MQTT with Node-RED and so on.

If I start any effect (only 3 effects are possible with analog LEDs xD) except the rainbow it is running as expected. A few miliseconds after starting the rainbow my Wemos D1 Mini R2 freezes :frowning:

Is there a better way to cycle smoothly through the colours then I did?

The rainbow effect is at the bottom. The LED_effect Timer is only active if a switch in Node-RED is switched to "on" (true). And the LED_effect time checks the variable "effect" and starts the choosen effect.

#include <pfodBufferedStream.h>
#include <loopTimer.h>
#include <millisDelay.h>
#include <SPI.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <WiFiManager.h>
#include <PubSubClient.h>
#include <SimpleTimer.h>

#define BAUD_RATE 115200
const size_t bufSize = 130;
uint8_t buf[bufSize];
pfodBufferedStream bufferedStream(BAUD_RATE, buf, bufSize, false);

const char* mqttServer = "BrokerIP";
const int mqttPort = 1883;
const char* mqttUser = "USER";
const char* mqttPassword = "PASSWORD";

WiFiClient espClient;
PubSubClient client(espClient);

char msg[50];
unsigned long now, time_now = 0;
unsigned long lastReconnectAttempt = 0;
unsigned long period = 50;

// LED Stuff
void LED_effect();
void Rainbow();
void OneHourLater(byte, byte, byte, unsigned long);
void clearStrip();
int Rpin = 5;
int Gpin = 14;
int Bpin = 12;
int r, g, b;
int red, green, blue;
byte effect = 1;
bool stopEffect;
bool LEDswitch = false;
bool firststartRB = true;
bool debug = false;

// Timer
SimpleTimer timer;
int LED_effect_timerID;

void pdelay(unsigned long);

millisDelay mqttDelay;

void setup() {
  Serial.begin(BAUD_RATE);
  Serial.println();
  for (int i = 5; i > 0; i--) {
    Serial.println(i);
    delay(500);
  }
  Serial.println();

  bufferedStream.connect(&Serial);

  pinMode(Rpin, OUTPUT);
  pinMode(Gpin, OUTPUT);
  pinMode(Bpin, OUTPUT);

  LED_effect_timerID = timer.setInterval(1 * 1000L, LED_effect);

  WiFiManager wifiManager;

  //wifiManager.resetSettings();

  wifiManager.autoConnect("ESP8266");

  bufferedStream.println("Connected.");

  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);

  while (!client.connected()) {
    bufferedStream.println("Connecting to MQTT...");
    if (client.connect("ESP-Buero2", mqttUser, mqttPassword )) {

      bufferedStream.println("connected");
    } else {
      bufferedStream.print("failed with state ");
      bufferedStream.print(client.state());
      delay(2000);
    }
  }
  client.publish("esp/bueroB/status", "connected");
  client.publish("esp/bueroB/status", "first time");
  client.publish("esp/bueroB/led", "false");
  client.publish("esp/bueroB/effect", "effect1");
  client.publish("esp/bueroB/period", String(period).c_str(), true);
  client.subscribe("esp/bueroB/led");
  timer.disable(LED_effect_timerID);
}

void callback(char* topic, byte* payload, unsigned int length) {
  bufferedStream.print("Message arrived ["); bufferedStream.print(topic); bufferedStream.print("] ");
  char msg[length + 1];
  for (int i = 0; i < length; i++) {
    msg[i] = (char)payload[i];
  }
  msg[length] = '\0';
  bufferedStream.println(msg);
  String LEDmsg = msg;

  if (LEDmsg.indexOf('a') >= 20) {
    int R_1 = LEDmsg.indexOf(':') + 1;
    int R_2 = LEDmsg.indexOf(',');
    int G_1 = LEDmsg.indexOf(':', R_1) + 1;
    int G_2 = LEDmsg.indexOf(',', R_2 + 1);
    int B_1 = LEDmsg.indexOf(':', G_1) + 1;
    int B_2 = LEDmsg.indexOf(',', G_2 + 1);
    red = LEDmsg.substring(R_1, R_2).toInt();
    green = LEDmsg.substring(G_1, G_2).toInt();
    blue = LEDmsg.substring(B_1, B_2).toInt();
    bufferedStream.print("Red: "); bufferedStream.print(red);
    bufferedStream.print(" - Green: "); bufferedStream.print(green);
    bufferedStream.print(" - Blue: "); bufferedStream.println(blue);

  } else if (LEDmsg.indexOf('e') == 0) {
    if (effect != LEDmsg.substring(6).toInt()) {
      effect = LEDmsg.substring(6).toInt();
    }
    if (LEDswitch) {
      timer.enable(LED_effect_timerID);
    }
  } else if (strcmp(msg, "false") == 0) {
    LEDswitch = false;
    stopEffect = true;
    clearStrip();
  } else if (strcmp(msg, "true") == 0) {
    LEDswitch = true;
    if (effect > 2) {
      effect = 1;
    }
    timer.enable(LED_effect_timerID);
  } else {
    period = LEDmsg.toInt();
    bufferedStream.print("New Delay: "); bufferedStream.println(period);
  }
}

boolean reconnect() {
  if (client.connect("ESP-Buero2", mqttUser, mqttPassword )) {
    client.publish("esp/bueroB/status", "connected");
    client.subscribe("esp/bueroB/led");
  }
  return client.connected();
}

void mqttConnection() {
  if (!client.connected()) {
    mqttDelay.start(5000);
    client.publish("esp/bueroB/status", "disconnected");
    if (mqttDelay.justFinished()) {
      if (reconnect()) {
        mqttDelay.stop();
      }
    }
  } else {
    client.loop();
  }
}

void pdelay(unsigned long milli)
{
  unsigned long end_time = millis() + milli;
  while (millis() < end_time) {
    yield();
  }
}

void loop() {
  timer.run();
  bufferedStream.available();
  mqttConnection();

  if (debug) {
    loopTimer.check(&bufferedStream);
  }
}

/********************* LED Stuff ***********************/
void LED_effect() {
  bufferedStream.print("Effect: "); bufferedStream.println(effect);
  if (effect == 1) {
    setAll(red, green, blue);
  } else if (effect == 2) {
    stopEffect = false;
    Rainbow();
  } else if (effect == 3) {
    OneHourLater(random(255), random(255), random(255), period * 15);
    OneHourLater(0, 0, 0, period);
  } else if (effect == 4) {
    OneHourLater(255, 0, 0, period * 15);
    OneHourLater(0, 0, 0, period * 15);
  }
}

void setAll(byte r, byte g, byte b) {
  analogWrite(Rpin, r);
  analogWrite(Gpin, g);
  analogWrite(Bpin, b);
}

void clearStrip() {
  timer.disable(LED_effect_timerID);
  setAll(0, 0, 0);
  bufferedStream.println("Clear Strip");
}

void Rainbow() {
  if (firststartRB) {
    bufferedStream.println("RainbowLoop started");
    firststartRB = false;
    for (int k = 0; k <= 255; k++) {
      r = k; //rot nimmt ZU
      setAll(r, g, b);
      pdelay(period);
      if (effect != 2) {
        stopEffect = true;
        break;
      }
    }
  }
  if (!stopEffect) {
    for (int k = 0; k <= 255; k++) {
      g = k; //grün nimmt ZU
      setAll(r, g, b);
      pdelay(period);
      if (effect != 2) {
        stopEffect = true;
        break;
      }
    }
  }
  if (!stopEffect) {
    for (int k = 255; k >= 0; k--) {
      r = k; //rot nimmt AB
      setAll(r, g, b);
      pdelay(period);
      if (effect != 2) {
        stopEffect = true;
        break;
      }
    }
  }
  if (!stopEffect) {
    for (int k = 0; k <= 255; k++) {
      b = k; //blau nimmt ZU
      setAll(r, g, b);
      pdelay(period);
      if (effect != 2) {
        stopEffect = true;
        break;
      }
    }
  }
  if (!stopEffect) {
    for (int k = 255; k >= 0; k--) {
      g = k; //grün nimmt AB
      setAll(r, g, b);
      pdelay(period);
      if (effect != 2) {
        stopEffect = true;
        break;
      }
    }
  }
  if (!stopEffect) {
    for (int k = 0; k <= 255; k++) {
      r = k; //rot nimmt ZU
      setAll(r, g, b);
      pdelay(period);
      if (effect != 2) {
        stopEffect = true;
        break;
      }
    }
  }
  if (!stopEffect) {
    for (int k = 255; k >= 0; k--) {
      b = k; //blau nimmt AB
      setAll(r, g, b);
      pdelay(period);
      if (effect != 2) {
        stopEffect = true;
        break;
      }
    }
  }
  if (stopEffect) {
    bufferedStream.println("RainbowLoop stopped");
    r, g, b = 0;
    clearStrip();
    firststartRB = true;
  }
}

void OneHourLater(byte r, byte g, byte b, unsigned long sDelay) {
  setAll(r, g, b);
  pdelay(sDelay);
}

Your rainbow code is written in "blocking style" with delays inside loops. You have not used the standard Arduino delay() function, but used your own delay function which calls yield(). That should in theory prevent watchdog events, but from your description, it does sound like you are getting those. What do you see on serial monitor when the "freeze" happens?

I would re-write the rainbow code in non-blocking style, using millis() for timing and a single global variable e.g. "hue" for the current rainbow colour. hue could start at 0 and increment to up to 767 before returning to zero. When hue is between 0 and 255, set red = 255 - hue and green = hue. When hue is between 256 and 511, set green = 511 - hue and blue = hue - 256. When hue is between 512 and 767, set blue = 767 - hue and red = hue - 512. This should make your code far shorter and simpler, and no need for pdelay(), allowing loop() to finish quickly.

PaulRB:
Your rainbow code is written in "blocking style" with delays inside loops. You have not used the standard Arduino delay() function, but used your own delay function which calls yield(). That should in theory prevent watchdog events, but from your description, it does sound like you are getting those. What do you see on serial monitor when the "freeze" happens?

I would re-write the rainbow code in non-blocking style, using millis() for timing and a single global variable e.g. "hue" for the current rainbow colour. hue could start at 0 and increment to up to 767 before returning to zero. When hue is between 0 and 255, set red = 255 - hue and green = hue. When hue is between 256 and 511, set green = 511 - hue and blue = hue - 256. When hue is between 512 and 767, set blue = 767 - hue and red = hue - 512. This should make your code far shorter and simpler, and no need for pdelay(), allowing loop() to finish quickly.

In my LED_effect void I have this line

bufferedStream.print("Effect: "); bufferedStream.println(effect);

which only writes the "E" into serial Monitor.

isn't that

void pdelay(unsigned long milli)
{
  unsigned long end_time = millis() + milli;
  while (millis() < end_time) {
    yield();
  }
}

an unblocking delay?

Thank you for your input with the 767 instead of 3x 255 :slight_smile: Will try that out.

EDIT: I also thought about the hue number. It should be able to handle those settings. So I guess it isn't possible with the 767. The number is much higher. And so there would be more and more if statements that won't make it less complicated.

EDIT2: Now I started first test without the whole colours I want, but just for testing. And again. Every Letter is posted by itself in the Serial and not blockwise.

void Rainbow() {
  bufferedStream.println("RainbowLoop started");
  for (int hue = 0; hue <= 767; hue++) {
    if (hue >= 0 && hue <= 255) {
      red = hue;
    } else if (hue >= 256 && hue <= 511) {
      green = hue - 255;
    } else if (hue >= 512 && hue <= 767) {
      blue = hue - 512;
    }
    
    pdelay(period); //unblocking delay
    
    bufferedStream.print("Red: "); bufferedStream.print(red);
    bufferedStream.print(" - Green: "); bufferedStream.print(green);
    bufferedStream.print(" - Blue: "); bufferedStream.println(blue);
  }
}

EDIT3:

Ok the problem with the freezes should rely on the bufferedStream. Thought this would speed up and not freeze :confused: If I go back to for debugging to normal Serial.print nothing is freezing :slight_smile:

But now I figured out an other problem that wasn't there with blynk.
If turn off the LEDs in Node-RED the arduino isn't getting this value of the switch because it is looping through the LEDs. In blynk I was able to interrupt it by setting a variable like "stopEffect = true". But the Arduino isn't getting any value from MQTT while it is inside the loop.
Do you have any Idea to fix this?

Untitled.png

isn't that
....
an unblocking delay?

Nope. Like I said before, that's blocking. In fact, that may be exactly what delay() does on esp8266.

Do you have any Idea to fix this?

Yes, see reply#1. Your rainbow code is still blocking: loops with delays in them. Simpler now, with the hue 0..767 idea, but still 100% blocking code. Have you seen the "blink without delay" example sketch?

Yes I read more than one non blocking code sketches for arduino.

maybe I understand something wrong :frowning:

The Rainbow effect should go through the colors realy slowely by an amount of time I can setup on a MQTT message.

If I just do it like this:

if (delayRunning && ((millis() - delayStart) >= DELAY_TIME)) {
    delayStart += DELAY_TIME; // this prevents drift in the delays

isn't it the case that the next for loop increment is already starting while the unblocking delay is skipped?

I'm not sure what the "delayRunning" is for, but apart from that, that is how you write non-blocking code. DELAY_TIME is what you would control through your MQTT messages.

You mentioned a for loop again. You must not use one for hue. For-loops are not actually banned in non-blocking code style. They can be used as long as they execute very quickly. So you cannot use them for slow fades/cycling between colours.

ahhhh... I was forced to think about to fix my problem with the for loop... thank you, I think I got what you mean :wink:

the delayRunning is a variable in the example of instructables for enabling/disabling the delay. not needed in my case but maybe in other situations.

will try to fix my problem with your input now and will report back <3

Thanks a lot so far.

Thx @PaulRB
Seems to work like a charm in the serial monitor right now :slight_smile:

The code is way smaller now then before with the for loops.

Here is what I got so far:

void Rainbow() {
  if ((millis() - delayStart) >= DELAY_TIME) {
    delayStart += DELAY_TIME; // this prevents drift in the delays
    setAll(red, green, blue);
    hue++;
    
    if (hue >= 0 && hue <= 255) {
      red = hue;
    } else if (hue > 255 && hue <= 510) {
      green = hue - 255; // grün nimmt zu
    } else if (hue > 510 && hue <= 765) {
      red = 765 - hue; // rot nimmt ab
    } else if (hue > 765 && hue <= 1020) {
      blue = hue - 765; // blau nimmt zu
    } else if (hue > 1020 && hue <= 1275) {
      green = 1275 - hue; // grün nimmt ab
    } else if (hue > 1275 && hue <= 1530) {
      red = hue - 1275; // rot nimmt zu
    } else if (hue > 1530 && hue <= 1785) {
      blue = 1785 - hue; // blau nimmt ab
    }

    if (hue == 1785) {
      hue = 255; // neustart bei grün nimmt zu
    }
    Serial.print("Red: "); Serial.print(red);
    Serial.print(" - Green: "); Serial.print(green);
    Serial.print(" - Blue: "); Serial.print(blue);
    Serial.print(" - Hue: "); Serial.println(hue);
  }
}

Last Lines with Serial.print will be deleted in the finished version of course (or maybe with a debug variable, if it is set to true, then they will be shown).

It does feel like you have got the idea now. Good work, you have taken an important step forward in your Arduino experience level.

I'm not sure what is happening with "hue" in your code. It's going up to 1785, then back to 255? This does not make sense to me. It should go to 767 then back to 0 as I explained earlier. And your rainbow code is still longer and more complex than it should be. If you still need help tomorrow, I can make some suggestions.

PaulRB:
I'm not sure what is happening with "hue" in your code. It's going up to 1785, then back to 255? This does not make sense to me. It should go to 767 then back to 0 as I explained earlier. And your rainbow code is still longer and more complex than it should be. If you still need help tomorrow, I can make some suggestions.

Hey PaulRB,

sry that I didn't answer latest response from you. Thank you so far. Got a bit trouble with my node-red and started from scratch there. My experience got better and better with it as well and that results in new thinking about coding my node-red flows :slight_smile: Now that everything is nearly done with it I can do my arduino stuff again.

Ok for the answer with my rainbow code. I have such a high number because I have those cases:

0-255 => red increases
256-510 => green increases / red 255
511-765 => red decreases / green 255
766-1020 => blue increases / green 255
1021-1275 => green decreases / blue 255
1276-1530 => red increases / blue 255
1531-1785 => blue decreases / red 255

So I have every colormix that is possible with avoiding white color (r,g,b 255).
The last "if" is the check if the loop is done all the way through. Then it doesn't need to begin with red because red is already set. On the first start it will start with black and increases red to 255.
If you got something where I can free up some lines and raise my experience with arduino again... Here I am :slight_smile: Thank you so far!

I can see what you are trying to do. I think your longer route through the colour spectrum than I suggested isn't actually visiting any more colours. Colour perceived by the eye is the ratio of red:green:blue. Your route visits more combinations of brightness and saturation, but no more hues, unless I am wrong.

But this isn't that important. The important thing is that your re-wrote your code using non-blocking techniques and got it working!

PaulRB:
I can see what you are trying to do. I think your longer route through the colour spectrum than I suggested isn't actually visiting any more colours. Colour perceived by the eye is the ratio of red:green:blue. Your route visits more combinations of brightness and saturation, but no more hues, unless I am wrong.

But this isn't that important. The important thing is that your re-wrote your code using non-blocking techniques and got it working!

it works like a charm now :slight_smile: it is reeeeeeealy fast in responsiveness!

PaulRB:
I can see what you are trying to do. I think your longer route through the colour spectrum than I suggested isn't actually visiting any more colours. Colour perceived by the eye is the ratio of red:green:blue. Your route visits more combinations of brightness and saturation, but no more hues, unless I am wrong.

But this isn't that important. The important thing is that your re-wrote your code using non-blocking techniques and got it working!

Haha PaulRB :slight_smile:
You were right... my code looked a bit too much for what I wanted to achieve.

I found a solution with the HSV setting in FastLED, now it looks like this:

void Rainbow() {
  if ((millis() - delayStart) >= DELAY_TIME) {
    delayStart += DELAY_TIME;

    setColorHSV(hue, 255, 255);
    hue++;
  }
}

The part with the RGB Settings has to be adjusted as well, it is now using HSV ofc.

void setColorHSV(byte h, byte s, byte v) {
  CHSV color = CHSV(h, s, v);

  fill_solid(leds, NUM_LEDS, color);
  FastLED.show();
}

That is much cleaner, smaller, lighter and I think more performance efficient than my previous attemp.

The hue,sat,brightness value must be uint8_t then if hue is greater then 255 it will start again at 0 without any codesnipped :slight_smile: