Physical mood light color and brightness selector based on LCD touchscreen (with demo video)


  • Mod

    Alright. I have a project to tell you about. If there was an award for the most needlessly laborious project in the MySensors contest, I think I would have a good shot but since there is no such award I'll just post it here for fun 🙂

    I don't remember where I got the idea from, but the basic idea is a physical color selector for a mood light. A LCD touch display can be mounted inside a regular switch/dimmer wall enclosure. The light level and the color of the mood light is set by touching the display. Physical light switches are conveniently located throughout the house and anyone can use them - you don't need a smartphone with an app.

    So I ordered a 3.2" lcd touch screen. It arrived from China, and I realized I had bought a screen with a 16-bit bus. 16 bit bus means I need to have 16 data pins to send data to the screen. And that is for the data bus only. More pins are needed (RSET, LCD_CS, WR and RS). With an Arduino Mega this would not be a problem, but I only have Pro Minis.

    I figured out how to connect the screen. If I used ALL available pins on the Pro Mini (including the serial RX/TX, so no debugging 😱 ) I could make it show what I wanted. The breadboard setup is really a mess:
    0_1458382025681_breadboard.jpg

    So now I have an Arduino connected to a screen, that can show anything pre-defined. But there is no room for a radio or anything else to use for telling the Arduino what to show on the screen. Or is there? I realized that the LCD_CS pin on the screen can be set to high always. So I connected that to Vcc instead of using one of my precious i/o pins. I now have 1 pin available ❗

    The Arduino Software Serial functions can use any pin for serial communication. 1 available pin means one-way communication. I hooked up another Pro Mini (the flash storage was getting pretty crowded on the first Pro Mini anyway so it was nice to start fresh). Using the Software Serial function I could now send characters from the second Pro mini (called "Touch Dimmer) to the first Pro Mini (called "screen driver"). I can't get any information back, but that's ok.

    Next step was to define a set of commands that the Touch Dimmer Arduino could send to the Screen Driver Arduino. As of now, they look like this:

    fillScr(r,g,b) // Fills the entire screen with the r,g,b color
    drawHSL        // Draws a HSL color picker on the screen
    

    So by sending the text

    fillScr(FF,00,00)
    

    to the Screen Driver, I will fill the entire screen with red. By sending

    drawHSL
    

    the Screen Driver will draw a HSL color palette on the screen like this:
    0_1458381416800_hsl_whitetop.png

    I connected the touch sensor to the Touch Dimmer Arduino and set it up so that when the screen is touched, a color is sent to the mood light.

    For the mood light I used a slightly modified version of @AWI's great mood light from this thread.

    As can be seen in this video, selecting color and brightness by pressing on the screen works pretty OK.
    LCD touchscreen mood light color selector – 01:05
    — Mikael Falkvidd

    The sketches I use:
    Screen Driver

    #include <UTFT.h>
    #define NUM_WHITE_PIXELS 20
    #define RED 0
    #define GREEN 1
    #define BLUE 2
    #define SCALE 1
    //unsigned int hsl[240 / SCALE];
    extern unsigned int hsl[0x2170];
    //#define DEBUG
    // Declare which fonts we will be using
    //extern uint8_t SmallFont[];
    
    UTFT screen(ILI9327, A5, A4, A6, A3);
    #include <SoftwareSerial.h>
    
    SoftwareSerial mySerial(A2, A7); // RX, TX. TX is not used in this sketch.
    const byte PRIMES[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71}; // Used to hash the commands
    String message;
    
    
    void setup()
    {
      // Setup the LCD
      screen.InitLCD();
      //  screen.setFont(SmallFont);
      screen.clrScr();
      screen.setBrightness(255);
      mySerial.begin(57600);
    #if defined (DEBUG)
      Serial.begin(115200);
      Serial.println("Ready");
    #endif
    }
    
    void loop()
    {
      byte current_char = 0;
      while (true) {
        while (mySerial.available() > 0) {
          char c = mySerial.read();
          if (c == '\r' || c == '\n') {
            // We got a newline. That means end of message.
            if (current_char == 0) {
              // Some systems use \n for newline. Some \r. Some \r\n. In case of the last, we filter out the second character.
              continue;
            }
            handle_message(message);
            message = "";
            current_char = 0;
            continue;
          }
          // We got another character. Add it to the buffer.
          message = String(message + c);
          current_char++;
        }
      }
    }
    
    void handle_message(String message) {
      // 1. Split the message into command + the rest
      // 2. Create a hash of the command to make a switch statement work
      // 3. Let each case-switch parse the rest of the string
    
      int command_next_pos = message.indexOf(',');
      String command = message.substring(0, command_next_pos);
    
      unsigned long command_hash = hash(command);
    
    #if defined (DEBUG)
      Serial.print("case ");
      Serial.print(command_hash);
      Serial.print(": // ");
      Serial.println(command);
      Serial.println("");
      Serial.println("break;");
    #endif
      byte r, g, b, next_pos, pos;
    
      switch (command_hash) {
        case 1922:
    #if defined (DEBUG)
          Serial.println("TEST");
    #endif
          break;
        case 25880: // setBrightness
          // setBrightness is not supported by my screen. Implement later.
    
          break;
        case 5953: // fillScr(r,g,b)
          pos = command_next_pos;
          next_pos = message.indexOf(',', pos + 1);
    #if defined (DEBUG)
          Serial.print("pos=");
          Serial.println(pos);
          Serial.print("next_pos=");
          Serial.println(next_pos);
          Serial.print("message_first_substring=");
          Serial.println(message.substring(pos + 1, next_pos));
    #endif
          r = message.substring(pos + 1, next_pos).toInt();
          pos = next_pos;
          next_pos = message.indexOf(',', pos + 1);
          g = message.substring(pos + 1, next_pos).toInt();
          pos = next_pos;
          next_pos = message.indexOf(',', pos + 1);
          b = message.substring(pos + 1, next_pos).toInt();
    #if defined (DEBUG)
          Serial.print("fillScr(");
          Serial.print(r);
          Serial.print(",");
          Serial.print(b);
          Serial.print(",");
          Serial.print(g);
          Serial.println(")");
    #endif
          screen.fillScr(r, g, b);
          break;
        case 8260: // setColor
    
          break;
        case 20365: // setBackColor
    
          break;
        case 10548: // drawPixel
    
          break;
        case 7850: // drawLine
    
          break;
        case 7962: // drawRect
    
          break;
        case 24512: // drawRoundRect
    
          break;
        case 7917: // fillRect
    
          break;
        case 24403: // FillRoundRect
    
          break;
        case 13194: // drawCircle
    
          break;
        case 13149: // fillCircle
    
          break;
        case 9890: // printNumI
    
          break;
        case 9821: // printNumF
    
          break;
        case 4014: // lcdOff
          // lcdOff is not supported by my screen, but let's issue the command anyway
          //screen.lcdOff();
          break;
        case 2776: // lcdOn
          // lcdOn is not supported by my screen, but let's issue the command anyway
          //screen.lcdOn();
          break;
        case 5023: // drawHSL
          drawHSL();
          break;
    
      }
    }
    /*
    void drawHSL() {
        screen.drawBitmap(0, 0, 107, 80, hsl, 3);
    }
    */
    void drawHSL() {
      screen.fillScr(0,0,0);
      float hue;
      float saturation = 1;
      float lightness;
      unsigned int height = screen.getDisplayYSize();
      unsigned int width = screen.getDisplayXSize();
      byte rgb[3] = {0};
      for (unsigned int x = 0; x < width; x += SCALE) {
        hue = ((float)x) / width;
        for (unsigned int y = 0; y < height; y += SCALE) {
          lightness = ((float)y) / (height - NUM_WHITE_PIXELS); // top NUM_WHITE_PIXELS pixels represent white, full power
          hslToRgb(hue, saturation, lightness, rgb);
          screen.setColor(rgb[RED], rgb[GREEN], rgb[BLUE]);
          screen.drawPixel(x, y);
        }
      }
    }
    
    unsigned long hash(String command) {
      unsigned long hash = 0;
      for (int i = 0; i < command.length(); i++) {
        hash += PRIMES[i] * command[i];
      }
      return hash;
    }
    
    /**
    * Adapted from http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
    * Converts an HSL color value to RGB. Conversion formula
     * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
    * Assumes h, s, and l are contained in the set [0, 1] and
    * returns r, g, and b in the set [0, 255].
    *
    * @param   Number  h       The hue
    * @param   Number  s       The saturation
    * @param   Number  l       The lightness
    * @return  Array           The RGB representation
    */
    void hslToRgb(float h, float s, float l, byte * rgbIn) {
      float r, g, b;
    
      if (s == 0) {
        r = g = b = l; // achromatic
      } else {
        float q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        float p = 2 * l - q;
        r = hue2rgb(p, q, h + 1.0 / 3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1.0 / 3);
      }
      rgbIn[0] = min(r * 255, 255);
      rgbIn[1] = min(g * 255, 255);
      rgbIn[2] = min(b * 255, 255);
    }
    
    float hue2rgb (float p, float q, float t) {
      if (t < 0) t += 1;
      if (t > 1) t -= 1;
      if (t < 1.0 / 6) return p + (q - p) * 6 * t;
      if (t < 1.0 / 2) return q;
      if (t < 2.0 / 3) return p + (q - p) * (2.0 / 3 - t) * 6;
      return p;
    }
    

    Touch Dimmer

    #include <UTouch.h>
    #include <SoftwareSerial.h>
    #define DEBUG
    #include <MySensor.h>
    #include <SPI.h>
    
    #define SAVE_LIGHT_R 0
    #define SAVE_LIGHT_G 1
    #define SAVE_LIGHT_B 2
    const char INITIALCOLOR[] = "FFAA55";
    const byte PRIMES[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71}; // Used to hash the commands
    
    SoftwareSerial screen(A7, A2); // RX, TX. RX is not used in this sketch.
    UTouch myTouch( 6, 5, 4, 3, 2);
    MySensor gw;
    MyMessage RGBMsg(0, V_RGB);
    MyMessage solidColorMsg(2, V_STATUS);
    MyMessage setColorMsg(3, V_TEXT);
    
    void setup() {
      solidColorMsg.setDestination(12);
      setColorMsg.setDestination(12);
      Serial.begin(115200);
      Serial.println("Starting setup");
      delay(10);
      myTouch.InitTouch();
      myTouch.setPrecision(PREC_HI); // We don't need speed so we might as well go for precision
      screen.begin(57600);
      Serial.println("gw begin starts");
      delay(10);
      gw.begin(incomingMessage, AUTO, false);
      Serial.println("gw begin finished");
      delay(10);
      gw.sendSketchInfo("Mood light touchscreen", "1.0");
      gw.present(0, S_RGB_LIGHT, "RGB");// present to controller
      Serial.println("Setup done");
      // TODO: Fetch current value from controller instead of using black
      String initialColorCmd = "drawHSL";
      screen.println(initialColorCmd);
    
    
    }
    
    void loop() {
      gw.process();
    
      long x, y;
      byte temp_R, temp_G, temp_B;
      while (myTouch.dataAvailable() == true)
      {
        myTouch.read();
        x = myTouch.getX();
        y = myTouch.getY();
        if ((x != -1) and (y != -1))
        {
          Serial.print(x);
          Serial.print(",");
          Serial.println(y);
          float hue = x / 320.0;
          float saturation = 1;
          float lightness = y / (240.0 - 20); // top 20 pixels represent white, full power
          byte rgb[3] = {0};
          hslToRgb(hue, saturation, lightness, rgb);
    
          String colorCommand = rgbarrayToString(rgb);
          String command = String("fillScr," + colorCommand);
          char colorMessage[7];
          rgbarrayToHexstring(rgb).toCharArray(colorMessage, 7);
          //gw.send(RGBMsg.set(colorMessage));
          //gw.send(updateColor.set(colorMessage));
          gw.send(solidColorMsg.set(true));
          gw.send(setColorMsg.set(colorMessage));
          //screen.println(command);
          //screen.flush();
          //Serial.println(command);
          gw.wait(100); // More frequent updates than this will just make the screen updates irratic
        }
      }
    }
    
    void incomingMessage(const MyMessage &message) {
    
      if (message.type == V_RGB) {
        String hexstring = message.getString();
        Serial.print("RGB command: ");
        Serial.println(hexstring);
        setColor(hexstring);
      }
    }
    
    void setColor(String hexstring) {
      byte r, g, b;
      unsigned long number = strtoul( &hexstring[0], NULL, 16);
      Serial.print("Color long: ");
      Serial.println(number);
      byte RValue = number >> 16;
      byte GValue = number >> 8 & 0xFF;
      byte BValue = number & 0xFF;
    
      Serial.print("Color: ");
      Serial.println(hexstring);
      Serial.print("Red: ");
      Serial.println(RValue);
      Serial.print("Green: ");
      Serial.println(GValue);
      Serial.print("Blue: ");
      Serial.println(BValue);
    
    }
    
    String rgbarrayToString(byte *rgb) {
      return String(String(rgb[0]) + "," + String(rgb[1]) + "," + String(rgb[2]));
    }
    String rgbarrayToHexstring(byte *rgb) {
      char hexString[7];
      for (byte i = 0; i < 3; i++) {
        sprintf(hexString + i * 2, "%02X", rgb[i]);
      }
      hexString[6] = '\0';
      return hexString;
    }
    
    /**
    * Adapted from http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
    * Converts an HSL color value to RGB. Conversion formula
     * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
    * Assumes h, s, and l are contained in the set [0, 1] and
    * returns r, g, and b in the set [0, 255].
    *
    * @param   Number  h       The hue
    * @param   Number  s       The saturation
    * @param   Number  l       The lightness
    * @return  Array           The RGB representation
    */
    void hslToRgb(float h, float s, float l, byte *rgbIn) {
      float r, g, b;
    
      if (s == 0) {
        r = g = b = l; // achromatic
      } else {
        float q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        float p = 2 * l - q;
        r = hue2rgb(p, q, h + 1.0 / 3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1.0 / 3);
      }
      rgbIn[0] = min(r * 255, 255);
      rgbIn[1] = min(g * 255, 255);
      rgbIn[2] = min(b * 255, 255);
    }
    
    float hue2rgb (float p, float q, float t) {
      if (t < 0) t += 1;
      if (t > 1) t -= 1;
      if (t < 1.0 / 6) return p + (q - p) * 6 * t;
      if (t < 1.0 / 2) return q;
      if (t < 2.0 / 3) return p + (q - p) * (2.0 / 3 - t) * 6;
      return p;
    }
    
    unsigned long hash(String command) {
      unsigned long hash = 0;
      for (int i = 0; i < command.length(); i++) {
        hash += PRIMES[i] * command[i];
      }
      return hash;
    }
    

    Mood Light

    /*
     PROJECT: MySensors / RGB light NEOPIXEL
     PROGRAMMER: AWI
     DATE: october 10, 2015/ last update: october 14, 2015
     FILE: AWI_RGB.ino
     LICENSE: Public domain
    
     Hardware: Nano and MySensors 1.5, Wall light 16 WS2812B leds (neopixel)
    
     Special:
        uses Fastled library with NeoPixel (great & fast RBG/HSV universal library)             https://github.com/FastLED/FastLED
    
     SUMMARY:
    
        Different patterns and brightness settings
    
        Button switches on/off and cycles through all Color patterns on long press
    
     Remarks:
        Fixed node-id
    
    */
    
    #include <MySensor.h>
    #include <SPI.h>
    #include <FastLED.h>                                        // https://github.com/FastLED/FastLED
    #include <Button.h>                                         // https://github.com/JChristensen/Button
    
    const int stripPin = 5 ;                                    // pin where 2812 LED strip is connected
    const int buttonPin = 4 ;                                   // push button
    const int numPixel = 12 ;                                   // set to number of pixels (x top / y bottom)
    
    const int NODE_ID = 12;                                    // fixed MySensors node id
    const int RGB_LightChild = 0 ;                              // Child Id's, standard light child on/off/ dim
    const int RGB_RGBChild = 1 ;                                // RGB light child (on/off/dim/color, if controller supports V_RBG))
    const int RGB_SolidColorChild = 2 ;                         // when set, node reads Color text from ColorTextChild
    const int RGB_TextColorChild = 3 ;                          // Holds Text value for color (custom colors from controller)
    const int RGB_AlarmPatternChild = 4 ;                       // Switches to alarm status
    const int RGB_NextPatternChild = 5 ;                        // Move to next pattern when set
    
    CRGB leds[numPixel];
    
    // Kelving colors: Light & daylight (in Fastled reference only)
    /// 1900 Kelvin Candle=0xFF9329 /* 1900 K, 255, 147, 41 */,
    /// 2600 Kelvin Tungsten40W=0xFFC58F /* 2600 K, 255, 197, 143 */,
    /// 2850 Kelvin Tungsten100W=0xFFD6AA /* 2850 K, 255, 214, 170 */,
    /// 3200 Kelvin Halogen=0xFFF1E0 /* 3200 K, 255, 241, 224 */,
    /// 5200 Kelvin CarbonArc=0xFFFAF4 /* 5200 K, 255, 250, 244 */,
    /// 5400 Kelvin HighNoonSun=0xFFFFFB /* 5400 K, 255, 255, 251 */,
    /// 6000 Kelvin DirectSunlight=0xFFFFFF /* 6000 K, 255, 255, 255 */,
    /// 7000 Kelvin OvercastSky=0xC9E2FF /* 7000 K, 201, 226, 255 */,
    /// 20000 Kelvin ClearBlueSky=0x409CFF /* 20000 K, 64, 156, 255 */
    
    char controllerRGBvalue[] = "FF9329";                       // Controller sent RGB value, default
    uint16_t curBrightness, actualBrightness, controllerRGBbrightness = 0x7F ;  // Brightness globals
    unsigned long updateBrightnessDelay, lastBrightnessUpdate ; // Brightness timers
    int RGBonoff ;                                              // OnOff flag
    
    enum { pSolid, pOff, pAlarm, pFire, pFire2, pCandle, pRainbow}  ;   // Pattern globals (stored in int for convenience)
    const int lastPatternIdx = pRainbow + 1 ;                   // use last pattern for patterncount
    int curPattern ;                                            // current pattern
    unsigned long updatePatternDelay, lastPatternUpdate ;       // Pattern timers
    
    #define RADIODELAY 250
    unsigned long idleTimer = millis() ;                        // return to idle timer
    int idleTime = 10000 ;                                      // return to idle after 10 secs
    
    // initialize MySensors (MySensors 1.5 style)
    MySensor gw;
    
    MyMessage lightRGBMsg(RGB_LightChild,  V_RGB);              // standard messages, light
    MyMessage lightdimmerMsG(RGB_LightChild , V_DIMMER);
    MyMessage lightOnOffMessage(RGB_LightChild, V_STATUS);
    
    Button myBtn(buttonPin, true, true, 20);                    //Declare the button (pin, pull_up, invert, debounce_ms)
    
    // Simple state machine for button state
    enum {sIdle, sBrightness, sPattern} ;                        // simple state machine for button press
    int State ;
    
    void setup() {
      FastLED.addLeds<WS2812, stripPin, GRB >(leds, numPixel) ;  // initialize led strip .setCorrection(TypicalLEDStrip);
    
      gw.begin(incomingMessage, NODE_ID, false);              // initialize MySensors
      gw.sendSketchInfo("AWI RGB Wall 0", "1.0");
      gw.wait(RADIODELAY);
      gw.present(RGB_RGBChild, S_RGB_LIGHT, "RGB Wall RGB 0");// present to controller
      gw.wait(RADIODELAY);
      gw.present(RGB_LightChild, S_LIGHT, "RGB Wall Light 0");
      gw.wait(RADIODELAY);
      gw.present(RGB_SolidColorChild, S_LIGHT, "RGB Set Solid color (text) 0");
      gw.wait(RADIODELAY);
      gw.present(RGB_TextColorChild, S_INFO, "RGB Wall textcolor 0");
      gw.wait(RADIODELAY);
      gw.present(RGB_AlarmPatternChild, S_BINARY, "RGB Wall Alarm 0");
      gw.wait(RADIODELAY);
      gw.present(RGB_NextPatternChild, S_BINARY, "RGB Wall Pattern 0");
    
      // initialize strip with color and show (strip expects long, so convert from String)
      for (int i = 0 ; i < 6 ; i++) {                         // get color value from EEPROM (6 char)
        controllerRGBvalue[i] = gw.loadState(i) ;
      }
      setLightPattern(pSolid, NULL) ;                         // default controller Solid
      FastLED.show();
      State = sIdle ;                                         // Initial state
      //randomSeed(analogRead(0));
    }
    
    // read button and act accordingly
    // short press: on/off
    // longer press: set patterns with following short press
    // long press: set brightness increase
    void loop() {
      gw.process();                                           // wait for incoming messages
      myBtn.read();                                           //Read the button (only read)
      unsigned long now = millis();                           // loop timer reference
      switch (State) {
        case sIdle:                                         // default state, browse through patterns
          if (myBtn.wasReleased()) {                      // light on/ off in idle
            RGBonoff = !RGBonoff ;                      // invert light state
            setLightPattern((RGBonoff == 1) ? pOff : pSolid, 100);
            gw.send(lightOnOffMessage.set(RGBonoff));   // and update controller
          } else if (myBtn.pressedFor(500)) {             // move to Pattern update state with long press
            idleTimer = now ;                           // return to idle after ...
            State = sPattern ;
          }
          break ;
        case sPattern:                                      // entered after long press
          if (myBtn.pressedFor(2000)) {                   // when press even longer move to Brightness update
            State = sBrightness ;
          } else if (myBtn.wasPressed()) {
            setLightPattern((curPattern + 1) % lastPatternIdx, 500 ); // increase pattern and wrap
            idleTimer = now ;
          } else if ( now > idleTime + idleTimer  ) {     // return to idle after ...
            State = sIdle ;
          }
          break ;
        case sBrightness:                                   // entered after looong press
          if (myBtn.wasPressed()) {                           // if pressed again increase brightness
            setLightBrightness((curBrightness + 1) % 0xFF, 0) ; // increase brightness and wrap (0..0xFF)
            idleTimer = now ;
          } else if ( now > idleTime + idleTimer  ) {     // return to idle after ...
            State = sIdle ;
          }
          break ;
        default :
          State = sIdle ;
          break ;
      }
      updateLightBrightness();                                // update Brightness if time
      updateLightPattern();                                   // update Pattern if time
    }
    
    // Sets the light brightness, takes value and time (ms) as input
    void setLightBrightness(int newBrightness, unsigned long updateTime) {
      // global: curBrightness, actualBrightness, updateBrightnessDelay
      updateBrightnessDelay = updateTime / 0xFF ;             // delay = time / max steps
      actualBrightness = curBrightness ;                      // assume curBrightness is actual
      curBrightness = newBrightness ;                         // set curBrightness to new value, rest is done in update
    }
    
    // Update the light brightness if time
    void updateLightBrightness() {
      // global: curBrightness, actualBrightness, updateBrightnessDelay, lastBrightnessUpdate ;
      unsigned long now = millis() ;
      if (now > lastBrightnessUpdate + updateBrightnessDelay) { // check if time for update
        if ( actualBrightness > curBrightness) {
          FastLED.setBrightness( actualBrightness-- );
          FastLED.show();
        } else if ( actualBrightness < curBrightness) {
          FastLED.setBrightness( actualBrightness++ );
          FastLED.show();
        }
        lastBrightnessUpdate = now ;
      }
    }
    
    // **** Pattern routines *****
    // Sets and initializes the light pattern if nescessary
    void setLightPattern( int newPattern, unsigned long updateDelay) {
      // global: curPattern, updatePatternDelay
      curPattern = newPattern ;
      updatePatternDelay = updateDelay ;                      // delay for next pattern update, can be changed in pattern
      switch (curPattern) {
        case pSolid:                                        //  solid is controller value in all pixels
          for (int i = 0 ; i < numPixel ; i++) leds[i] = strtol( controllerRGBvalue, NULL, 16);
          FastLED.show();
          break ;
        case pOff:                                          //  off state all pixels off
          for (int i = 0 ; i < numPixel ; i++) leds[i] = 0 ;
          FastLED.show();
          break ;
        default :
          break ;
      }
    }
    
    // Update the light pattern when time for it
    void updateLightPattern() {
      // global: curPattern, updatePatternDelay, lastPatternUpdate
      unsigned long now = millis() ;
      if (now > lastPatternUpdate + updatePatternDelay) {     // check if time for update
        switch (curPattern) {
          case pAlarm:                                    // flash light
            patternAlarm();
            break ;
          case pFire:                                     // wild fire
            patternFire();
            break ;
          case pFire2:                                    // cosy fire
            patternFire2();
            break ;
          case pCandle:                                   // flame
            patternCandle();
            break ;
          case pRainbow:                                  // rotating rainbow
            patternRainbow();
            break ;
          case pSolid:                                    // do nothing fall through
          case pOff:
          default :                                       // def
            break ;
        }
        lastPatternUpdate = now ;
      }
    }
    
    // Define the different patterns
    // Alarm - intermittent white and red color, full intensity, intermittent top & bottom half
    void patternAlarm() {
      static boolean topBot ;                         // indicates direction for next entry
      const CRGB colorTop = CRGB(0xFF, 0, 0 );                // red color
      const CRGB colorBottom = CRGB(0xFF, 0xFF, 0xFF );       // white color
      FastLED.setBrightness(0xFF);                            // set the strip brightness
      for (int i = 0; i <= (numPixel / 2 - 1) ; i++) {                // for half of strip size
        leds[i] = topBot ? colorTop : colorBottom ;
        leds[i + (numPixel / 2)] = topBot ? colorBottom : colorTop ;
      }
      topBot = !topBot ;                                      // switch direction
      FastLED.show();
    }
    
    // Simulate fire with red color, varying number of leds intensity & tempo
    void patternFire() {
      byte numberLeds = random(0, numPixel);                  // start number and end of led's for flickering
      byte lum = random(100, 255);                            // set brightness
      CRGB color = CRGB(200, 50 + random(1, 180), 0 );        // get red color with varying green
      for (int i = 0; i <= numberLeds; i++) {
        leds[i] = color ;
        FastLED.setBrightness(lum);                           // set the strip brightness
        FastLED.show();
        gw.wait(random(0, 10));
      }
      updatePatternDelay = 100 ;
    }
    
    // Simulate fire with red color and varying intensity & tempo
    void patternFire2() {
      CRGB color = CRGB(200, random(100, 150), 0);            // get red color with varying green
      for (byte p = 0; p < numPixel; p++) {
        leds[p] = color;
      }
      FastLED.setBrightness(random(50, 255));
      FastLED.show();
      updatePatternDelay = random(20, 300);                   // variable delay
    }
    
    // Simulate candle based on fire with red color, varying number of leds intensity & tempo
    void patternCandle() {
      byte numberLeds = random(0, numPixel);                  // start number and end of led's for flickering
      byte lum = random(60, 80);                              // set brightness
      CRGB color = CRGB(200, 50 + random(40, 100), 0 );       // get red color with varying green
      for (int i = 0; i <= numberLeds; i++) {
        leds[i] = color ;
        FastLED.setBrightness(lum);                           // set the strip brightness
        FastLED.show();
        gw.wait(random(5, 10));
      }
      updatePatternDelay = 100 ;
    }
    
    
    void patternRainbow() {
      static uint16_t hue ;                               // starting color
      FastLED.clear();
      // for(hue=10; hue<255*3; hue++) {
      hue = (hue + 1) % 0xFF ;                                // incerease hue and wrap
      fill_rainbow( leds, numPixel , hue /*static hue value */, 5);// set a rainbow from hue to last in stepsize 5
      FastLED.show();
      updatePatternDelay = 100 ;
    }
    
    // Incoming messages from MySensors
    void incomingMessage(const MyMessage &message) {
      int ID = message.sensor;
      Serial.print("Sensor: ");
      Serial.println(ID);
      switch (ID) {
        case RGB_LightChild:                                // same behaviour as RGB child/ fall through
        case RGB_RGBChild:                                  // if controller can handle V_RGB
          if (message.type == V_RGB) {                    // check for RGB type
            strcpy(controllerRGBvalue, message.getString());// get the payload
            setLightPattern(pSolid, NULL);              // and set solid pattern
          } else if (message.type == V_DIMMER) {          // if DIMMER type, adjust brightness
            controllerRGBbrightness = map(message.getLong(), 0, 100, 0, 255);
            setLightBrightness(controllerRGBbrightness, 2000) ;
          } else if (message.type == V_STATUS) {          // if on/off type, toggle brightness
            RGBonoff = message.getInt();
            setLightBrightness((RGBonoff == 1) ? controllerRGBbrightness : 0, 2000);
          }
          break ;
        case RGB_SolidColorChild:                           // request color from controller
          if (message.type == V_STATUS) {                 // if get color from text child
            //gw.request(RGB_TextColorChild, V_TEXT);
            setLightPattern(pSolid, NULL);                  // and set solid pattern (if not alre)
          }
          break ;
        case RGB_TextColorChild:                            // Text color from controller
          if (message.type == V_TEXT) {                   // if get color from text child
            //gw.request(RGB_TextColorChild, V_TEXT);
            strcpy(controllerRGBvalue, message.getString());// get the payload
            Serial.print(" RGB_TextColorChild Color: "); Serial.println(controllerRGBvalue);
            for (int i = 0 ; i < 6 ; i++) {             // save color value to EEPROM (6 char)
              gw.saveState(i, controllerRGBvalue[i]) ;
            }// Save to EEPROM
          }
          break ;
        case RGB_AlarmPatternChild:                         // set Alarm pattern
          if (message.type == V_STATUS) {                 // if get color from text child
            if (message.getInt() == 1) {
              setLightPattern(pAlarm, 500);           // set slow alarm pattern
            } else {
              setLightPattern(pSolid, NULL);          // and set solid pattern
              FastLED.setBrightness(curBrightness);
            }
          }
          break ;
        case RGB_NextPatternChild:                          // next pattern
          if (message.type == V_STATUS) {                 // if get color from text child
            if (message.getInt() == 1 ) {
              setLightPattern((curPattern + 1) % lastPatternIdx, 500 ); // increase pattern and wrap
            }
          }
          break ;
      }
      FastLED.show();
      dispRGBstat();
    }
    // debug
    // display the status of all RGB: controller, requested, real
    void dispRGBstat(void) {
      Serial.print(" Color: "); Serial.print(controllerRGBvalue);
      Serial.print(" Brightness: "); Serial.println(controllerRGBbrightness);
    }
    

    Things I learned:

    Improvement ideas:

    • The HSL palette doesn't look very good when rendered on the screen. I don't know why, but I suspect some rounding problem.
    • With the current setup it is not possible to set the mood light to white and adjust brightness (without selecting a color).
    • It would be cool to add support for "gestures" - for example turn on the light at full brightness when swiping up, turn off the light when swiping down.
    • It would be possible to control several mood lights (or mood light in different rooms) from the same physical location by swiping left and right to select which room to control.
    • The String functions are not very good for memory-constrained environments like Arduinos so it would probably be wise to use something else. I haven't experiences any problems though.

    Special thanks to @m26872 for encouraging me to finish this crazy project and post about it


  • Contest Winner

    I absolutely love this project. And I wouldn't call it needlessly at all. Great job and thanx for sharing.


  • Admin

    Awesome, please add this to openhardware.io.


Log in to reply
 

Suggested Topics

  • 8
  • 1
  • 2
  • 1
  • 7
  • 90

15
Online

11.4k
Users

11.1k
Topics

112.7k
Posts