[Project] LEDnode from Jeelabs

All about Arduino, Jeenode and other Clones (hardware & Software)

Moderator: Mdamen

Post Reply
airox
Member
Member
Posts: 214
Joined: Sat May 15, 2010 10:42 pm

[Project] LEDnode from Jeelabs

Post by airox »

Hi guys,

Lately I've been exploring the world of LED-strips. Those nice things which can lower your energy bill. I explored the options to get them wireless controlled. Seen a lot and a lot I didn't like. Overkill solutions or to complicated (WIFI for LED controllers?). I also wanted to keep the price low for each LED-strip. I expect to implement three of them in my house (and one ambilight effect you can view numerous videos on youtube). I settled for the nice new LED node v2 from Jeelabs. Not afraid of doing some soldering and software development.

Bill of materials used for my first LED-strip (which is put on our sleepingroom):
- RGB strip (only 2 meter used) - 7,2 euros
http://samenkopen.net/action_product/909457/822877
- 12v adapter - 6 euros
http://samenkopen.net/action_product/909457/681680
- LED-node - 28 euros
- http://jeelabs.com/products/led-node-v2
- Some corner profile (or whatever it's called) from our local Gamma
Something like this: http://www.bouwmarktconcurrent.nl/lamin ... er-4423704
------ TOTAL => +/- 50 euros

But there wasn't any nice good software on the internet capable of things I wanted the strip to do. Scenario's I had were the following:

- Wireless send commands to control the LED-strip.
- Capable of doing effects continously or just once.
- Signal an alarm by glowing red.
- Signal an incoming telephone conversation by flashing yellow a few times, one second of silence and than flashing again.
- Signal a notification by fading to orange and back to the current color again (whenever I'm sitting with my headphones on I know something is happining).
- Fading effects.
- Plain simpel show color effects.
- Certain beautifull presets available (blacklight, purple, and whites)
- Random show some colors (disco effect).
- Send a "turn up" or "turn down" command for an RGB channel or the whole strip (easy adjusting).
- Queue a series of effects described above in the "continous" queue to be played by the LED node.
- Queue some effects described above in the "do once" queue. Whenever something is added to this queue the strip will do this and return to playing the continous queue.
- Fire and water effects (TODO)
- HUE color codes effects (TODO)

I basically want to mimic events in the house on the LED-strip. In the next code samples I have the LED-strip libraries and a sample sketch (which will take it commands from a Jeelink or from a serial port during development).
airox
Member
Member
Posts: 214
Joined: Sat May 15, 2010 10:42 pm

Re: [Project] LEDnode from Jeelabs

Post by airox »

One library used from the web:
http://playground.arduino.cc/Code/QueueArray

The ledstrip written libraries:

LEDStrip.h

Code: Select all

#ifndef LEDSTRIP_H
#define LEDSTRIP_H

#include "Arduino.h"
#include <QueueArray.h>

#ifndef LED_R
#define LED_R 6 // IRQ
#endif

#ifndef LED_G
#define LED_G 9 // IRQ
#endif

#ifndef LED_B
#define LED_B 5 // IRQ
#endif
 
#define numBufSize 11

typedef struct {
	byte red;
	byte green;
	byte blue;
} RGB;

struct Ramp {
	RGB rgb;
	unsigned int seconds;
	boolean fade;
};

class LEDStrip {
  RGB fromColor;
  RGB toColor;
  RGB curColor;

  boolean fade;
  boolean prevOnce;

  QueueArray <Ramp> once;
  QueueArray <Ramp> continous;

  unsigned long beginTime;
  unsigned long endTime;
  byte calcNewLevel(byte from, byte to, unsigned int diffEnd, unsigned int diffCur);
  byte calcUpDown(byte from, byte up, byte amount);
  void fadeNext();
  unsigned int stringToNumber(String thisString);

public:
      LEDStrip();
         ~LEDStrip();
        void process();
        boolean isactive();
		void processCommand(String command);
		void command(String command);

		void addonce(Ramp ramp);
		void addcontinous(Ramp ramp);
		void add(Ramp ramp, boolean addToOnce);
		void start();
		void reset();
		void setcolor(RGB rgb);
        void setnext(Ramp ramp);

};
 
#endif
LEDStrip.cpp

Code: Select all

#include "Arduino.h"
#include "LEDStrip.h"
#include <QueueArray.h>

// #define DEBUG 

LEDStrip::LEDStrip() {
  bitSet(TCCR1B, WGM12);
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);

  reset();
}

LEDStrip::~LEDStrip(){/*nothing to destruct*/}

void LEDStrip::reset(){
  toColor = (RGB) {0,0,0};
  fromColor = (RGB) {0,0,0};
  curColor = (RGB) {0,0,0};
  
  beginTime = 0;
  endTime = 0;
  fade = false;
  prevOnce = false;

  setcolor((RGB){0,0,0});
}

boolean LEDStrip::isactive() {
	return (endTime != 0);
}

unsigned int LEDStrip::stringToNumber(String thisString) {
  unsigned int i, value, length;
  length = thisString.length();
  char blah[(length+1)];
  for(i=0; i<length; i++) {
    blah[i] = thisString.charAt(i);
  }
  blah[i]=0;
  value = atoi(blah);
  return value;
}

void LEDStrip::processCommand(String command) {
	#ifdef DEBUG
	Serial.println("received: " + command);
	#endif

	if ( command.charAt(0) == 'B' || command.charAt(0) == 'A' || command.charAt(0) == 'C' ) {
		unsigned int secs = stringToNumber(command.substring(2,6));
		
		RGB begin = { (byte)stringToNumber(command.substring(6,9)),
						(byte)stringToNumber(command.substring(9,12)),
							(byte)stringToNumber(command.substring(12,15)) };
		RGB end = {0,0,0};
		
		boolean once = command.charAt(1) == 1; 
					
		// B 1000 120 130 140
		if ( command.length() > 14 ) {
			// B 1000 120 130 140 150 160 170
			end.red = (byte)stringToNumber(command.substring(15,18));
			end.green = (byte)stringToNumber(command.substring(18,21));
			end.blue = (byte)stringToNumber(command.substring(21,24));
		}
		
		// add them
		if ( command.charAt(0) == 'B' ) {
			add((Ramp){begin, secs, false}, once);
			add((Ramp){end, secs, false}, once);
		} else {
			add((Ramp){begin, secs, (command.charAt(0) == 'C')}, once);
		}
	} else if ( command.charAt(0) == 'D' ) {
		int secs = stringToNumber(command.substring(2,6));
		byte number = stringToNumber(command.substring(6,8));
		
		boolean once = command.charAt(1) == 1; 
		
		for(byte i=0; i<number;i++) {
			RGB rgb = {random(0,255),random(0,255),random(0,255)};
			Ramp ramp = {rgb, secs, true};
			add(ramp, once);
		}
	} else if ( command.charAt(0) == 'F' ) {
		byte whichChannel = stringToNumber(command.substring(1,2)); // 0=r,1=g,2=b,3=all
		byte upOrDown = stringToNumber(command.substring(2,3)); // 0 - 1
		byte amount = stringToNumber(command.substring(3,5)); // 0 - 99
		
		if ( continous.count() == 1 ) {
			Ramp ramp = (Ramp) continous.pop();
			
			if ( whichChannel == 0 || whichChannel == 3 ) {
				ramp.rgb.red = calcUpDown(ramp.rgb.red, upOrDown, amount);
			}
			
			if ( whichChannel == 1 || whichChannel == 3 ) {
				ramp.rgb.green = calcUpDown(ramp.rgb.green, upOrDown, amount);
			}
			
			if ( whichChannel == 2 || whichChannel == 3 ) {
				ramp.rgb.blue = calcUpDown(ramp.rgb.blue, upOrDown, amount);
			}
			
			// read again
			continous.push(ramp);
			setnext(ramp);
		} 
	} else if ( command.charAt(0) == 'E' ) {
		int preset = stringToNumber(command.substring(1,3));
		if ( preset == 1 ) {
			// fire
			//1F00500255040000.F00200255050000.F00300255010000.F00300255070000

			for(int i=0; i<15;i++) {
				byte r = (byte)random(25);
				byte g = (byte)random(10);
				addcontinous((Ramp){(RGB){(byte)r+225, (byte)g+50, 0}, 1000, true});
			}
		} else if ( preset == 2 ) {
			// color = purple
			addcontinous((Ramp){(RGB){255, 0, 30}, 2000, false});
		} else if ( preset >= 20 && preset <= 29 ) {
			/** WHITES **/
			if ( preset == 20 ) { 
				// color = warm white effect
				addcontinous((Ramp){(RGB){255, 70, 0}, 2000, false});
			} else if ( preset == 21 ) {
				addcontinous((Ramp){(RGB){255, 244, 229}, 2000, false});
			} else if ( preset == 22 ) {
				addcontinous((Ramp){(RGB){250, 235, 215}, 2000, false});
			} else if ( preset == 23 ) {
				addcontinous((Ramp){(RGB){248, 245, 212}, 2000, false});
			} else if ( preset == 24 ) {
				// candle
				addcontinous((Ramp){(RGB){255, 147, 41}, 2000, false});
			} else if ( preset == 25 ) {
				// Clear Blue Sky
				addcontinous((Ramp){(RGB){64, 156, 255}, 2000, false});
			} else if ( preset == 26 ) {
				// Overcast Sky
				addcontinous((Ramp){(RGB){201, 226, 255}, 2000, false});
			} else if ( preset == 27 ) {
				addcontinous((Ramp){(RGB){255, 241, 224}, 2000, false});
			} 
		} else if ( preset >= 30 && preset <= 39 ) {
			// alarm effect (continous!)
			if ( preset == 30 ) { 
				addcontinous((Ramp){(RGB){255, 0, 0}, 250, true});
				addcontinous((Ramp){(RGB){0, 0, 0}, 700, true});
			} else if ( preset == 31 ) {
				addonce((Ramp){(RGB){0, 0, 0}, 700, true});
				addonce((Ramp){(RGB){255, 0, 0}, 250, true});
				addonce((Ramp){(RGB){0, 0, 0}, 700, true});
			}  else if ( preset == 32 ) {
				// orange notification (once!)
				addonce((Ramp){(RGB){0, 0, 0}, 700, true});
				addonce((Ramp){(RGB){240, 10, 0}, 250, true});
				addonce((Ramp){(RGB){0, 0, 0}, 700, true});
				
				// 1C0100025525525591C01000001001001
				// 1C010002400100009C01000000000000
				
				// 1C010002400100009C01000000000000
			} else if ( preset == 33 ) {
				// yellow ringing notification (two times!)
				// 1A11000255100000
				
				addonce((Ramp){(RGB){255, 100, 0}, 100, false});
				addonce((Ramp){(RGB){0, 0, 0}, 100, false});
				
				addonce((Ramp){(RGB){255, 100, 0}, 100, false});
				addonce((Ramp){(RGB){0, 0, 0}, 100, false});
				
				addonce((Ramp){(RGB){255, 100, 0}, 100, false});
				addonce((Ramp){(RGB){0, 0, 0}, 1500, false});
				
				addonce((Ramp){(RGB){255, 100, 0}, 100, false});
				addonce((Ramp){(RGB){0, 0, 0}, 100, false});
				
				addonce((Ramp){(RGB){255, 100, 0}, 100, false});
				addonce((Ramp){(RGB){0, 0, 0}, 100, false});
				
				addonce((Ramp){(RGB){255, 100, 0}, 100, false});
				addonce((Ramp){(RGB){0, 0, 0}, 1000, false});
			} 
		} else if ( preset == 5 ) {
			// Black Light Fluorescent
			addcontinous((Ramp){(RGB){167, 0, 255}, 2000, false});
		} else if ( preset == 6 ) {
			// water
			// 1C015001001002559C01500100100255
			
			// addcontinous((Ramp){(RGB){201, 226, 255}, 2000, false});

			
		}
	}
}

void LEDStrip::command(String command) {
	String buffer = "";
	
	if ( command.charAt(0) == '1' ) {
		#ifdef DEBUG
		Serial.println("empty continous queue");
		#endif

		while(once.count() > 0) {
			once.pop();
		}
		
		while(continous.count() > 0) {
			continous.pop();
		}
		
		reset();
	}
	
	for(int i=1; i<command.length();i++) {
		if ( command.charAt(i) == '9' ) {
			processCommand(buffer);
			buffer = "";
		} else {
			buffer += command.charAt(i);
		}
	}
	processCommand(buffer);
}

void LEDStrip::add(Ramp ramp, boolean addToOnce) {
	if ( addToOnce ) {  
		once.push(ramp);
	} else {
		continous.push(ramp);
	}
}

void LEDStrip::addonce(Ramp ramp) {
	once.push(ramp);
}

void LEDStrip::addcontinous(Ramp ramp){
	continous.push(ramp);
}

void LEDStrip::setnext(Ramp ramp) {
  beginTime = millis();
  
  fromColor = curColor;
  toColor = ramp.rgb;
  
  fade = ramp.fade;
  endTime = beginTime+ramp.seconds;
}

void LEDStrip::fadeNext() {
	if ( once.count() > 0 ) {
		setnext((Ramp) once.pop());
		prevOnce = true;
	} else if ( continous.count() > 0 ) {
		if ( prevOnce ) {
			prevOnce = false;
			
			Ramp ramp = (Ramp) continous.peek();
			ramp.fade = true;
			ramp.seconds = 1000;
			
			// fade to the next continous color
			setnext(ramp);
		} else {
			Ramp ramp = (Ramp) continous.pop();
		
			// read again
			continous.push(ramp);
			setnext(ramp);
		}
	}
}

void LEDStrip::start() {
	fadeNext();
}

void LEDStrip::setcolor(RGB rgb) {
	curColor = rgb;
	
	analogWrite(LED_R, curColor.red);
	analogWrite(LED_G, curColor.green);
	analogWrite(LED_B, curColor.blue);
}

byte LEDStrip::calcUpDown(byte from, byte up, byte amount) {
	if ( up == 1 ) { // up
		if ( from + amount > 255 ) {
			return 255;
		} else {
			return (byte)(from + amount);
		}
	} else {
		if ( from - amount < 0 ) {
			return 0;
		} else {
			return (byte)(from - amount);
		}
	}

}

byte LEDStrip::calcNewLevel(byte from, byte to, unsigned int diffEnd, unsigned int diffCur) {
	if ( from == to ) {
		return from;
	}
	
	byte diff = abs(from-to);
	
	float newf = round( ( (float)diff / diffEnd ) * diffCur );

	byte newv;
	if ( newf > 255 ) {
		newv = 255;
	} else {
		newv = (byte) newf;
	}
	
	if ( from > to ) {
		newv = diff - newv;
	}
	return newv;
}

void LEDStrip::process() {
  // check if already passed
  unsigned long curTime = millis();  

  if ( isactive() ) {
 	  if ( curTime >= endTime ) {
		endTime = 0;
		fadeNext();
	  } else {
	  	// if fade just set the new value
		if ( fade ) {
		
		    // get the correct color
		    unsigned int diff = (int) endTime - beginTime;
		    unsigned int diffToCur = (int) curTime - beginTime;
		
			byte red = calcNewLevel(fromColor.red,toColor.red, diff, diffToCur);
			byte green = calcNewLevel(fromColor.green,toColor.green, diff, diffToCur);
			byte blue = calcNewLevel(fromColor.blue,toColor.blue, diff, diffToCur);
						
			setcolor((RGB){red,green,blue});
		} else {
			setcolor(toColor);
		}
	  }
	}
}

airox
Member
Member
Posts: 214
Joined: Sat May 15, 2010 10:42 pm

Re: [Project] LEDnode from Jeelabs

Post by airox »

And the sample sketch which I used. Kept some commands in the comments which you can send.

Code: Select all

#include <LEDStrip.h>
#include <QueueArray.h>
#include <JeeLib.h>

LEDStrip strip;

String readString;
String lastCommand;
unsigned long lastCommandTime;

void setup() {
  Serial.begin(57600);
    
  rf12_initialize(33 /* node */, RF12_868MHZ, 0);
  lastCommand = "";
  lastCommandTime = 0;
}

void loop() {

  readString = "";  
  while (Serial.available()) {
    delay(10);  //delay to allow buffer to fill 
    if (Serial.available() >0) {
      char c = Serial.read();  //gets one byte from serial buffer
      readString += c; //makes the string readString
    } 
  }
    
  if (readString.length() >0) {
    strip.command(readString);
    strip.start();
  }
  
  strip.process();
  delay(10);
  
  if (rf12_recvDone() && rf12_crc == 0) {
    const byte* p = (const byte*) rf12_data;
    
    Serial.println("Received something");
    
    int nodeID = rf12_hdr & 0x1F;  // get node ID
    
    if ( nodeID == 1 ) {
      
      String str = "";
      for(int i=0; i<rf12_len;i++) {
       String newstr = String(rf12_data[i], HEX);
       newstr.toUpperCase();
       str += newstr;
      }
      
      if ( str.charAt(0) == '1' || str.charAt(0) == '0' ) {
        // ignore the same messages for three seconds!
        unsigned int curTime = millis();
                
        boolean run = true;
        if ( str == lastCommand ) {
          if ( lastCommandTime + 1000 > curTime ) {
            run = false;
          }
        } 
        
        if ( run ) {
          Serial.println("Accept command: "+str);
          lastCommand = str;
          lastCommandTime = curTime;
          strip.command(str);
          strip.start();
        } else {
           Serial.println("Ignoring previous commmand"); 
        }     
      } else {
        Serial.println("incorrect data received");
        Serial.println(nodeID);   
        Serial.println(str);    
      }
      
      
      if (RF12_WANTS_ACK) {
          rf12_sendStart(RF12_ACK_REPLY, 0, 0);
          Serial.println("Sending ACK");
      } else {
          Serial.println("Sending ACK");        
      }
    } else {
      // odd node
    }
  }
  
    /*
    
    Channel test:
    1A11000255000000
    1A11000000255000
    1A11000000000255
    1A11000255255255
    1A10500255244229
    
    1C010002552552559C010000000002559C01000255255255 // white -> blue -> white
    1C010002552552559C010000002550009C01000255255255 // white -> blue -> white
 
    1D1100021 // random
    
    1B01250255070000 // blink

    0F0110
    1A01000255060000
    
    1B01250255070000 = blink 
    1D0100012 = random
    1C01000255000000 = fade
    1A11000255060000 = warm orange/yellow
    1C010002552552559C010000000000009C01000255255255 = fade in white .. fade out white
    1C010002550400009C010002550500009C01000255010000
    
    1C005002550400009C002002550500009C003002550100009C00300255070000
    
    1C0100025
    0D0050012
    1E01 == fire
    1E02 == preset 2, etc!
    
    */  
}
airox
Member
Member
Posts: 214
Joined: Sat May 15, 2010 10:42 pm

Re: [Project] LEDnode from Jeelabs

Post by airox »

Some documentation about the codes you can test on the serial console and can be sent through the air:

Example: 1A11000255000000
First char [1] = Replace all (once and continous) queues and with the command just send (zero to add instead of replace)
Second char [A] = Effect type (A=Set to RGB immediately)
Next: [1] = Add this command to the once queue (0 = continous)
Next: [1000] = The number of seconds the effect should take (in this case no effect)
Next: [255] = Red
Next: [000] = Green
Next: [000] = Blue

For more documentation on the effects check the source code. Not all effects have the same command setup.
airox
Member
Member
Posts: 214
Joined: Sat May 15, 2010 10:42 pm

Re: [Project] LEDnode from Jeelabs

Post by airox »

Oh and for those wondering how I control this LED-strip in a Wife-Acceptance-Factor fashion. I ordered the following remote control on dx.com:

http://dx.com/p/24-key-wireless-infrare ... 2025-47019

The already in place Raspberry PI with IguanaIR infrared USB module behind the television at the sleeping room picks up the signal and sends it towards my home automation server. This then converts this to the command and sends it through the Jeelink towards the LED-node. Other effects are obviously send whenever an event arrises.
vanisher

Re: [Project] LEDnode from Jeelabs

Post by vanisher »

Nice!!!

Esp the PI behind the bedroom TV :D

More details on that?
airox
Member
Member
Posts: 214
Joined: Sat May 15, 2010 10:42 pm

Re: [Project] LEDnode from Jeelabs

Post by airox »

It runs Raspbmc (XBMC) and has an IR usb stick from iguanaworks and a WIFI N adapter attached. The IR usb stick receives IR commands but also to sends IR commands to turn on the TV and on correct input (HDMI) for "Movie mode", "Ambient mode", "Relax mode", etc. I just connected the LIRCD instances from my home automation server and the one running on the Raspberry PI using the --connect and --listen parameters.

The movie, ambient, relax, party, sleeping and normal mode also influence the LED-node light settings :-)
Digit
Global Moderator
Global Moderator
Posts: 3388
Joined: Sat Mar 25, 2006 10:23 am
Location: Netherlands
Contact:

Re: [Project] LEDnode from Jeelabs

Post by Digit »

Nice work!
One question though: when will we be invited to have a look at the 'party' mode?
Really interested in that one! :) 8)
Post Reply

Return to “Raspberry, Arduino, Cubietruck and other clones Forum”