RGB WiFi enabled lamp (Part 2)

As promised in my previous entry, I’ll go into some details related to how I implemented effects on my RGB Lamp:

All the code is available at my github repo or the mirror repo on my self-hosted gitea instance

The idea is to have mqtt messages triggering different behavior in my code, the design consists in triggering a periodic function to change LED RGB values depending on the message topic and payload

I’ll try to explain with code chunks, the lamp has 3 operation modes: Solid, Fade and Rainbow

Solid

This was the very first transition implemented, simple color setting depending on a 32 bit RGB value (8 bits per color)

  • function mqttDataCb is called with topic /mqtt/lights/solid
  • payload is assigned to a boolean and other mode booleans (fade and rainbow) are set to false. RGB combination is assigned to all LEDs
void mqttDataCb(uint32_t *args, const char *topic, uint32_t topic_len,
                const char *data, uint32_t data_len) {
  char *topicBuf = (char *)os_zalloc(topic_len + 1),
       *dataBuf = (char *)os_zalloc(data_len + 1);

  MQTT_Client *client = (MQTT_Client *)args;

  os_memcpy(topicBuf, topic, topic_len);
  topicBuf[topic_len] = 0;

  os_memcpy(dataBuf, data, data_len);
  dataBuf[data_len] = 0;

  INFO_MSG("Receive topic: %s, data: %s length: %d \r\n", topicBuf, dataBuf,
           data_len);
  int isRed = strcmp(topicBuf, "/mqtt/lights/red");
  int isGreen = strcmp(topicBuf, "/mqtt/lights/green");
  int isBlue = strcmp(topicBuf, "/mqtt/lights/blue");
  int isFade = strcmp(topicBuf, "/mqtt/lights/fade");
  int isSolid = strcmp(topicBuf, "/mqtt/lights/solid");
  int isRainbow = strcmp(topicBuf, "/mqtt/lights/rainbow");
  INFO_MSG("topic comparison result: R %d, G %d, B %d, fade %d, solid%d\r\n",
           isRed, isGreen, isBlue, isFade, isSolid);
  if (isRed == 0) {
    received_red = dataBuf[0];
  } else if (isGreen == 0) {
    received_green = dataBuf[0];
  } else if (isBlue == 0) {
    received_blue = dataBuf[0];
  } else if (isFade == 0) {
    fade = dataBuf[0];
    solid = false;
    rainbow = false;
  } else if (isSolid == 0) {
    solid = dataBuf[0];
    fade = false;
    rainbow = false;
  } else if (isRainbow == 0) {
    rainbow = dataBuf[0];
    solid = false;
    fade = false;
  }

  os_free(topicBuf);
  os_free(dataBuf);

  if (solid) {
    os_timer_disarm(&fadeTimer); //Disable timer for periodic function

    INFO_MSG("Updated color is R:%d G:%d B:%d \r\n", received_red,
             received_green, received_blue);
    int i;
    for (i = 0; i < NUMPIXELS; i++) {
      // Color takes RGB values, from 0,0,0 up to 255,255,255
      setPixelColor(&ledstrip, i,
                    color_rgb(received_red, received_green, received_blue));
      system_soft_wdt_feed();
    }
    show(&ledstrip);
  } else if (fade) {
   ... //Redacted for convenience
  }else if(rainbow){
   ... //Redacted for convenience
  }
}
  • Nothing else to do here, the Solid mode is simple and doesn’t need a periodic function to change anything, which is why the timer to trigger said function is just disabled (in case the previous mode depended on it) with os_timer_disarm(&fade_timer) (more on this available in Espressif’s nonos SDK)

Fade

I call the first one Fade because of the soft transitions between colors…

  • function mqttDataCb is called with topic /mqtt/lights/fade
  • after the payload value is assigned to a boolean, the periodic function timer is disarmed, some initialization happens: all LEDs are initialized to color blue, and a state machine is re-initialized (to start from a known state) This is the state machine declaration:
enum fade_state{
  to_purple,
  to_red,
  to_orange,
  to_green,
  to_teal,
  to_blue
};

enum fade_state FADESTATE = to_purple; //state machine initial state

This is the missing chunk in function mqttDataCb:

} else if (fade) {
  fade_red = 0; //reset fade values
  fade_green = 0;
  fade_blue = 255;
  FADESTATE = to_purple;
  // MQTT_Publish(client,"/mqtt/lights/solid",0,1,2,0);
  for (uint8_t i = 0; i < NUMPIXELS; i++) {
    // Color takes RGB values, from 0,0,0 up to 255,255,255
    setPixelColor(&ledstrip, i, color_rgb(fade_red, fade_green, fade_blue));
    system_soft_wdt_feed();
  }
  show(&ledstrip);

  //initialize timer
  os_timer_disarm(&fadeTimer);
  os_timer_setfn(&fadeTimer, (os_timer_func_t *)fadeFunction, NULL);
  os_timer_arm(&fadeTimer, FADE_DELAY, 0);
}else if(rainbow){
  • This function basically kickstarts the timer that calls fadeFunction, which in turn disarms the timer, checks which mode of operation is active and, in this case, cycles through the super fancy state machine (implemented as a simple switch statement), it just saturates or depletes a color to create the color transitions:
static void ICACHE_FLASH_ATTR fadeFunction(void *arg) {
  os_timer_disarm(&fadeTimer);
  if (fade) {
    switch (FADESTATE) { //Fade state machine
    case to_purple:
      if (fade_red < 255) { // purple
        fade_red += 51;
      } else {
        FADESTATE = to_red;
      }
      break;
    case to_red:
      if (fade_blue > 0) { // red
        fade_blue -= 51;
      } else {
        FADESTATE = to_orange;
      }
      break;
    case to_orange:
      if (fade_green < 255) { // orange
        fade_green += 51;
      } else {
        FADESTATE = to_green;
      }
      break;
    case to_green:
      if (fade_red > 0) { // green
        fade_red -= 51;
      } else {
        FADESTATE = to_teal;
      }
      break;
    case to_teal:
      if (fade_blue < 255) { // teal
        fade_blue += 51;
      } else {
        FADESTATE = to_blue;
      }
      break;
    case to_blue:
      if (fade_green > 0) { // blue
        fade_green -= 51;
      } else {
        FADESTATE = to_purple;
      }
      break;
    default:
      break;
    }
    
    for (uint8_t i = 0; i < NUMPIXELS; i++) {
      setPixelColor(&ledstrip, i, color_rgb(fade_red, fade_green, fade_blue));
      system_soft_wdt_feed();
    }
    show(&ledstrip);

  }else if(rainbow){

  ... // Redacted for convenience

  }
  os_timer_setfn(&fadeTimer, (os_timer_func_t *)fadeFunction, NULL);
  os_timer_arm(&fadeTimer, FADE_DELAY, 0);
}

Rainbow

This is the most flashy (pun intended) mode of operation, it took what I call LED remapping (basically messing around with pointers), the soldering method shown in Thingiverse makes it hard to use simple mathematical operations to set the color LEDs periodically, initially I thought that using something like LED_index % 9 in a switch case would be enough but that doesn’t work for the snake like pattern I have, perhaps a diagram can illustrate what I mean:

Essentially what I wanted was to have LEDs 0, 17, 18, 35, 36, 53, 54, 71 be “grouped” together and have the same color. Same applies to all others… So I created some structs that group the LEDs like this and had to manually point their elements to the corresponding LED numbers:

This are the corresponding chunks of code (not sure if function ledstrip_remap() is the best way to deal with the remapping, I’m pretty sure there should be a way to define this at compile time, food for thought):

#define NUMPIXELS   72
 COLOR_COMBINATIONS      6
 LINES_IN_STRIP  9
 LEDS_PER_LINE   8

typedef union{
    struct{ //LSB to MSB
        uint8_t red:8;
        uint8_t green:8;
        uint8_t blue:8;
    }b;
    uint32_t w;
}color;

typedef struct leds{
    color LED_ARRAY[NUMPIXELS]; // led complete array
}LEDS;

typedef struct ledline{
    color *LED_LINE_ELEMENTS[LEDS_PER_LINE]; // leds in line
}LEDLINE;

typedef struct ledstrip{
    LEDLINE LED_LINES_IN_STRIP[LINES_IN_STRIP]; // led lines in strip
}LEDSTRIP;


LEDS all_leds;
LEDSTRIP myledstrip;
color rainbow_array[COLOR_COMBINATIONS];


void ledstrip_remap() { // it is important to rework this function if the total
                        // number of leds in your strip is not 72 (arranged in 9
                        // lines x 8 elements each)
  // 1st ring
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[0];
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[17];
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[18];
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[35];
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[36];
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[53];
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[54];
  myledstrip.LED_LINES_IN_STRIP[0].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[71];

  // 2nd ring
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[1];
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[16];
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[19];
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[34];
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[37];
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[52];
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[55];
  myledstrip.LED_LINES_IN_STRIP[1].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[70];

  // 3rd ring
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[2];
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[15];
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[20];
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[33];
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[38];
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[51];
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[56];
  myledstrip.LED_LINES_IN_STRIP[2].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[69];

  // 4th ring
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[3];
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[14];
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[21];
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[32];
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[39];
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[50];
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[57];
  myledstrip.LED_LINES_IN_STRIP[3].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[68];

  // 5th ring
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[4];
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[13];
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[22];
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[31];
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[40];
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[49];
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[58];
  myledstrip.LED_LINES_IN_STRIP[4].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[67];

  // 6th ring
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[5];
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[12];
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[23];
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[30];
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[41];
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[48];
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[59];
  myledstrip.LED_LINES_IN_STRIP[5].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[66];

  // 7th ring
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[6];
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[11];
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[24];
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[29];
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[42];
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[47];
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[60];
  myledstrip.LED_LINES_IN_STRIP[6].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[65];

  // 8th ring
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[7];
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[10];
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[25];
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[28];
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[43];
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[46];
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[61];
  myledstrip.LED_LINES_IN_STRIP[7].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[64];

  // 9th ring
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[0] =
      &all_leds.LED_ARRAY[8];
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[1] =
      &all_leds.LED_ARRAY[9];
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[2] =
      &all_leds.LED_ARRAY[26];
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[3] =
      &all_leds.LED_ARRAY[27];
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[4] =
      &all_leds.LED_ARRAY[44];
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[5] =
      &all_leds.LED_ARRAY[45];
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[6] =
      &all_leds.LED_ARRAY[62];
  myledstrip.LED_LINES_IN_STRIP[8].LED_LINE_ELEMENTS[7] =
      &all_leds.LED_ARRAY[63];
}

void set_rainbow_element(uint8_t element_num, uint8_t red, uint8_t green, uint8_t blue){
    rainbow_array[element_num].b.red = red;
    rainbow_array[element_num].b.green = blue;
    rainbow_array[element_num].b.blue = green;
}

void init_rainbow_array() { // it is important to rework this function if the
                            // desired color combination array doesn't have 6
                            // elements
  set_rainbow_element(0, 0, 0, 255);   // blue
  set_rainbow_element(1, 255, 0, 255); // purple
  set_rainbow_element(2, 255, 0, 0);   // red
  set_rainbow_element(3, 255, 255, 0); // orange
  set_rainbow_element(4, 0, 255, 0);   // green
  set_rainbow_element(5, 0, 255, 255); // teal
}

void rotate_rainbow_array(){
    color temp = rainbow_array[0];
    for (uint8_t i = 0; i < COLOR_COMBINATIONS -1; i++) {
        rainbow_array[i] = rainbow_array[i+1];
    }
    rainbow_array[COLOR_COMBINATIONS-1] = temp;
}

void set_strip_line_color(uint8_t line_num, color assigned_color) {
  for (uint8_t i = 0; i < LEDS_PER_LINE; i++) {
    myledstrip.LED_LINES_IN_STRIP[line_num].LED_LINE_ELEMENTS[i]->b.red =
        assigned_color.b.red;
    myledstrip.LED_LINES_IN_STRIP[line_num].LED_LINE_ELEMENTS[i]->b.green =
        assigned_color.b.green;
    myledstrip.LED_LINES_IN_STRIP[line_num].LED_LINE_ELEMENTS[i]->b.blue =
        assigned_color.b.blue;
  }
}

Then the missing chunk of code in function fadeFunction is simpler:

  }else if(rainbow){
    rotate_rainbow_array();
    for (uint8_t i = 0; i < LINES_IN_STRIP; i++) {
      if (i < COLOR_COMBINATIONS) {
        set_strip_line_color(i, rainbow_array[i]);
      } else {
        set_strip_line_color(i, rainbow_array[i - COLOR_COMBINATIONS]);
      }
    }

    for (uint8_t i = 0; i < NUMPIXELS; i++) {
      // setPixelColor takes RGB values, from 0,0,0 up to 255,255,255, 
      // this function is part of the Adafruit_NeoPixel library
      setPixelColor(&ledstrip, i,
                    color_rgb(all_leds.LED_ARRAY[i].b.red,
                              all_leds.LED_ARRAY[i].b.green,
                              all_leds.LED_ARRAY[i].b.blue));
      system_soft_wdt_feed();
    }
    show(&ledstrip);
  }

This is my first attempt at explaining code in a blog post, hopefully the idea got across, at least writing this helps me organize my thoughts


Day 15 of my 2021’s

Join 100DaysToOffload!

By lopeztel

Mexican, wannabe maker and overall linux distro enthusiast. I enjoy playing around with electronics, running, painting and reading. I started blogging as therapy and got more invested recently.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.