The Planted Tank Forum

The Planted Tank Forum (http://www.plantedtank.net/forums/index.php)
-   DIY (http://www.plantedtank.net/forums/forumdisplay.php?f=20)
-   -   Guide: Arduino based LED controller for Current Satellite LED+ (http://www.plantedtank.net/forums/showthread.php?t=391265)

Indychus 07-24-2013 03:26 PM

Guide: Arduino based LED controller for Current Satellite LED+
 
So I made an off-hand comment in one of Current USA's threads last night about an Arduino controller for their LED+ freshwater fixture. In the 12 or so hours since posting that, I have probably received a dozen PM's requesting code, IR protocol, sketches, or other information. Instead of responding to each request for information separately, it became obvious that I just needed to put together a thread for my project. This is that thread.

First, a disclaimer. While I'm not your average DIY tinkerer, I'm also not an electronics expert. I'm a mechanical engineer by day, and like to think of myself as a modern mad scientist by night. If you decide to build your own controller based on this thread, be aware that you're on your own. That's not to say I won't help... I think open source is the way of the future and love to pass knowledge on. I just mean that if you burn up your Arduino, yourself, or your house, that it's on you.

I'm putting together a guide to follow along, but none of the steps should be considered the last step. The nice thing about Arduino is how easy it is to constantly modify your code and add features. Throughout this thread, I will periodically post my newest code so that you can upload it and get the newer features. If you're experienced with Arduino, feel free to hack my code apart and have your way with it.

This process should work for any light fixture with some tweaking!

I'm writing this step-by-step so hopefully anyone can follow along. The more Arduino users we have in the world, the better life can be for everyone! If you're new to Arduino, don't be overwhelmed... it's not as difficult as it seems at first glance. And if you're an old hand please don't bash my primitive coding too much!



Everything in this post should be considered open source and public domain. Feel free to use my code, distribute it, hack it up, whatever.

Indychus 07-24-2013 03:33 PM

Check this post for the most up to date code and features.

Most Current Version : 3.6
Other versions can be found throughout this thread.

Functionality:
  • Allows user to set up to 24 triggers to activate pre-defined functions at any time.
  • Random thunderstorms.
  • RTC support maintains time even if power is lost.
  • Range of around 10 feet for IR emitter.
  • No modification to the fixture or remote required.
  • Can be powered by USB or via a wall wart.
  • Factory remote still functions. Will return to programmed functions at next trigger.
  • All factory functions can be programmed to activate at any time.
  • Supports a 20 x 4 LCD screen.

Default scheduling:
  • 07.00 am - Dawn/Dusk
  • 09.00 am - Cloud2
  • 11.00 am - FullSpec
  • 03.00 pm - Cloud2
  • 07.00 pm - Dawn/Dusk
  • 09.00 pm - Night2

Required libraries:
  • Time
  • TimeAlarms
  • RTClib
  • IRremote
  • LiquidCrystal

Code:

///////////////////////////////////////////////////////////////////
// Current Satellite LED+ Controller  V3.6                      //
//  Indychus...Dahammer...mistergreen @ plantedtank.net        //
//  This code is public domain.  Pass it on.                    //
//  Confirmed on Arduino UNO 1.0.5                              //
//  Req. Time, TimeAlarms, RTClib, IRremote                    //
///////////////////////////////////////////////////////////////////
//
// This version uses Ken Shirriff's IRremote library to Rx/Tx the IR codes
// http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html
//
// This code does NOT use PIN 13 on the Uno, as do previous versions
// Instead PIN 3, which is a PWM pin, is used. So you'll need to connect
// your LED to PIN 3 instead of PIN 13 for it to work.

// Install LCD per instructions at http://learn.adafruit.com/character-lcds/overview

#include <Wire.h>
#include <RTClib.h>
#include <Time.h>
#include <TimeAlarms.h>
#include <IRremote.h>
#include <LiquidCrystal.h>

RTC_DS1307 RTC;
IRsend irsend;
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

/*********** BEGIN USER DEFINED VARIABLES ***********/
// DEBUG_IR adds the option to test the IR commands via the Arduino software's serial monitor
// You can send any value from 1 to 32 and it send the corresponding IR code
// The codes follow the remote controller, left to right, top to bottom
// e.g 1 = Orange, 2 = Blue, 21 = Moon1, etc
#define DEBUG_IR

int postDelay = 100;        // Delay after codes are sent
int randAnalogPin = 0;      // This needs to be set to an unused Analog pin, Used by ThunderStorm()

// Current Satellite+ IR Codes (NEC Protocol)
unsigned long codeHeader = 0x20DF; // Always the same

// Remote buttons listed left to right, top to bottom
unsigned int codeOrange = 0x3AC5;
unsigned int codeBlue = 0xBA45;
unsigned int codeRose = 0x827D;
unsigned int codePowerOnOff = 0x02FD;
unsigned int codeWhite = 0x1AE5;
unsigned int codeFullSpec = 0x9A65;
unsigned int codePurple = 0xA25D;
unsigned int codePlay = 0x22DD;
unsigned int codeRedUp = 0x2AD5;
unsigned int codeGreenUp = 0xAA55;
unsigned int codeBlueUp = 0x926D;
unsigned int codeWhiteUp = 0x12ED;
unsigned int codeRedDown = 0x0AF5;
unsigned int codeGreenDown = 0x8A75;
unsigned int codeBlueDown = 0xB24D;
unsigned int codeWhiteDown = 0x32CD;
unsigned int codeM1Custom = 0x38C7;
unsigned int codeM2Custom = 0xB847;
unsigned int codeM3Custom = 0x7887;
unsigned int codeM4Custom = 0xF807;
unsigned int codeMoon1 = 0x18E7;
unsigned int codeMoon2 = 0x9867;
unsigned int codeMoon3 = 0x58A7;
unsigned int codeDawnDusk = 0xD827;
unsigned int codeCloud1 = 0x28D7;
unsigned int codeCloud2 = 0xA857;
unsigned int codeCloud3 = 0x6897;
unsigned int codeCloud4 = 0xE817;
unsigned int codeStorm1 = 0x08F7;
unsigned int codeStorm2 = 0x8877;
unsigned int codeStorm3 = 0x48B7;
unsigned int codeStorm4 = 0xC837;

void SetAlarms()
{
  // Set up your desired alarms here
  // The default value of dtNBR_ALARMS is 6 in Alarms.h.
  // This code sets 12 alarms by default, so you'll need to change dtNBR_ALARMS to 12 or more
  Alarm.alarmRepeat(7,00,0, DawnDusk);
  Alarm.alarmRepeat(9,00,0, Cloud2);    // (HR,MIN,SEC,FUNCTION)
  Alarm.alarmRepeat(11,00,0, FullSpec);
  Alarm.alarmRepeat(15,00,0, Cloud2);
  Alarm.alarmRepeat(19,00,0, DawnDusk);
  Alarm.alarmRepeat(21,00,0, Moon2);
 
  // Comment these out if you don't want the chance of a random storm each day
  Alarm.alarmRepeat(12,00,00, ThunderStorm);
  ThunderStorm();  // Sets up intial storm so we don't have wait until alarm time
}
/************* END USER DEFINED VARIABLES *************/

void setup()
{
  Wire.begin();
  RTC.begin();
  lcd.begin(20, 4);
  Serial.begin(9600);
      //Serial.println(freeRam());
 
  if (! RTC.isrunning()) {
    Serial.println("RTC Error");
    RTC.adjust(DateTime(__DATE__, __TIME__));}  //Adjust to compile time
   
 
  setSyncProvider(syncProvider);    //reference our syncProvider function instead of RTC_DS1307::get()
 
  Alarm.timerRepeat(900, digitalClockDisplay);  // Display the time every 15 minutes
  digitalClockDisplay();
  SetAlarms();
 Serial.print("SRAM : ");          //un-comment these line to check available SRAM
 Serial.println(freeRam());} 

void loop()
{
#ifdef DEBUG_IR
  if (Serial.available() > 0) {
    delay(5); //Wait for transmission to finish
    CurrentCMDs(SerialReadInt());
  }
#endif
  Alarm.delay(100);
  // Service alarms & wait (msec)
  lcdClockDisplay();
}

time_t syncProvider()
{
  //this does the same thing as RTC_DS1307::get()
  return RTC.now().unixtime();
}

void ThunderStorm ()
{
  // Schedules a storm between 1 & 9 in the evening
  // It sets Storm2, followed by Cloud2 or DawnDusk or Moon2, depending on when the storm is over
  randomSeed(analogRead(randAnalogPin));  // Generate random seed on unused pin
  byte RH = random(23);                  // Randomizer for thunderstorm
  byte RM = random(59);
  byte RS = random(59);
  byte TSDurationH = random(2);
  byte TSDurationM = random(59);
 
  if (RH <= 12)
    {
      Serial.println("No storm today");
      lcd.setCursor(0,1);
      lcd.print("No storm today");
      return;
    }
     
  if (RH > 12)                            // If random value is after 1 pm, allow storm
    {
      Alarm.alarmOnce(RH,RM,RS,Storm2);
      Serial.print("Next Storm: ");
      Serial.print(RH);
      printDigits(RM);
      printDigits(RS);
      Serial.print("  ");
      Serial.print("Duration = ");
      Serial.print(TSDurationH);
      printDigits(TSDurationM);
      Serial.println();
    lcd.setCursor(0,1);
    lcd.print("Next Storm: ");
    lcdHRdigits(RH);
    lcdDigits(RM);}
     
      if ((RH + TSDurationH) < 19)  // Return to Cloud2 if storm ends between 1-7pm
        {Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,Cloud2);}
      else if ((RH + TSDurationH) < 21)  // Return to DawnDusk if storm ends between 7-9pm
        {Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,DawnDusk);}
      else                                      // Return to Night2 if storm ends after 9pm
        {Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,Moon2);}
    }


void digitalClockDisplay()          // Digital clock
{
  Serial.print("Time = ");
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.println(); }

 
void lcdClockDisplay() 
  {lcd.setCursor(0,0);
    lcdHRdigits(hour());
  lcdDigits(minute());}

void printDigits(int digits)        // Add :
{
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

void lcdDigits(int digits)        // Add :
{
  lcd.print(":");
  if(digits < 10)
    lcd.print('0'); 
  lcd.print(digits);
}
void lcdHRdigits(int HRdigits)        // Preface hour with 0
{
  if(HRdigits < 10)
    lcd.print('0'); 
  lcd.print(HRdigits);
}

#ifdef DEBUG_IR
int SerialReadInt()
{
  int i, numAva;
  char inBytes[3];                  // Array to hold the bytes
  char * inBytesPtr = &inBytes[0];  // Pointer to the first element of the array
     
    numAva = Serial.available();    // Read number of input bytes
    if (numAva > 2)
      numAva = 2;                  // Only allow 2 characters to prevent overflow
     
    for (i=0; i<numAva; i++)        // Load input bytes into array
      inBytes[i] = Serial.read();
    inBytes[i] =  '\0';            // Put NULL character at the end
    return atoi(inBytesPtr);        // Call atoi function and return result
}

void CurrentCMDs (int cmd)
{switch (cmd)
  {
    case 1:
      Orange();
      break;
    case 2:
      Blue();
      break;
    case 3:
      Rose();
      break;
    case 4:
      PowerOnOff();
      break;
    case 5:
      White();
      break;
    case 6:
      FullSpec();
      break;
    case 7:
      Purple();
      break;
    case 8:
      Play();
      break;
    case 9:
      RedUp();
      break;
    case 10:
      GreenUp();
      break;
    case 11:
      BlueUp();
      break;
    case 12:
      WhiteUp();
      break;
    case 13:
      RedDown();
      break;
    case 14:
      GreenDown();
      break;
    case 15:
      BlueDown();
      break;
    case 16:
      WhiteDown();
      break;
    case 17:
      M1Custom();
      break;
    case 18:
      M2Custom();
      break;
    case 19:
      M3Custom();
      break;
    case 20:
      M4Custom();
      break;
    case 21:
      Moon1();
      break;
    case 22:
      Moon2();
      break;
    case 23:
      Moon3();
      break;
    case 24:
      DawnDusk();
      break;
    case 25:
      Cloud1();
      break;
    case 26:
      Cloud2();
      break;
    case 27:
      Cloud3();
      break;
    case 28:
      Cloud4();
      break;
    case 29:
      Storm1();
      break;
    case 30:
      Storm2();
      break;
    case 31:
      Storm3();
      break;
    case 32:
      Storm4();
      break;
    default:
      Serial.println("Invalid Choice");}}
#endif

void SendCode (unsigned int code, byte numTimes, const char *sMessage)
{unsigned long irCode = (codeHeader << 16) + code; // Header is 2 bytes, shift all the way to left & add code to it
 
  for( int i = 0; i < numTimes; i++)
  {irsend.sendNEC(irCode,32); // Send code
    Alarm.delay(postDelay);}
   
  Serial.println(sMessage);  // Print message
  lcd.setCursor(6,0);
  lcd.print(sMessage);
  for(byte i = sizeof(sMessage); i <= 14; i++)
    lcd.print(" ");
  digitalClockDisplay();}

void Orange()
{SendCode(codeOrange, 2, "Orange");}

void Blue()
{SendCode(codeBlue, 2, "Blue");}

void Rose()
{SendCode(codeRose, 2, "Rose");}

void PowerOnOff()
{SendCode(codePowerOnOff, 1, "Power On/Off");}

void White()
{SendCode(codeWhite, 2, "White");}

void FullSpec()
{SendCode(codeFullSpec, 2, "Full Spectrum");}

void Purple()
{SendCode(codePurple, 2, "Purple");}

void Play()
{SendCode(codePlay, 1, "Play/Pause:");}

void RedUp()
{SendCode(codeRedUp, 1, "Red Up");}

void GreenUp()
{SendCode(codeGreenUp, 1, "Green Up");}

void BlueUp()
{SendCode(codeBlueUp, 1, "Blue");}

void WhiteUp()
{SendCode(codeWhiteUp, 1, "White Up");}

void RedDown()
{SendCode(codeRedDown, 1, "Red Down");}

void GreenDown()
{SendCode(codeGreenDown, 1, "Green Down");}

void BlueDown()
{SendCode(codeBlueDown, 1, "Blue Down");}

void WhiteDown()
{SendCode(codeWhite, 1, "White Down");}

void M1Custom()
{SendCode(codeM1Custom, 2, "Custom Mix 1");}

void M2Custom()
{SendCode(codeM2Custom, 2, "Custom Mix 2");}

void M3Custom()
{SendCode(codeM3Custom, 2, "Custom Mix 3");}

void M4Custom()
{SendCode(codeM4Custom, 2, "Custom Mix 4");}

void Moon1()
{SendCode(codeMoon1, 2, "Moonlight 1");}

void Moon2()
{SendCode(codeMoon2, 2, "Moonlight 2");}

void Moon3()
{SendCode(codeMoon3, 2, "Moonlight 3");}

void DawnDusk()
{SendCode(codeDawnDusk, 2, "Dawn/Dusk");}

void Cloud1()
{SendCode(codeCloud1, 2, "Cloud Cover 1");}

void Cloud2()
{SendCode(codeCloud2, 2, "Cloud Cover 2");}
 
void Cloud3()
{SendCode(codeCloud3, 2, "Cloud Cover 3");}

void Cloud4()
{SendCode(codeCloud4, 2, "Cloud Cover 4");}

void Storm1()
{SendCode(codeStorm1, 2, "Thunderstorm 1");}

void Storm2()
{SendCode(codeStorm2, 2, "Thunderstorm 2");}

void Storm3()
{SendCode(codeStorm3, 2, "Thunderstorm 3");}

void Storm4()
{SendCode(codeStorm4, 2, "Thunderstorm 4");}

int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}


Indychus 07-24-2013 03:44 PM

Here's what you need to get started. These parts can be had at radioshack or other electonics supplies, although e b a y and Amazon are great sources.
  • Arduino: I used an Uno for this project, which is $29 straight from Arduino, or around $9 for a knock-off board. I prefer the genuine Arduino, but many of the knock-offs are very nice. Boards other than Uno can be used, but some of the pins may be different. I am using a Revision 3 board running Arduino version 1.0.5. If you're new to this, I'd suggest a starter kit that includes a breadboard, jumpers, and an assortment of goodies.

  • IR Receiver: The Current system uses a 38 kHz frequency just like most A/V equipment. At least, my 48" Satellite LED+ does. You can use an oscilloscope and photodiode to determine the frequency if you are trying to control a light other than Current. Any 38 khz IR receiver should work. They are around $2-3 usually, but can be had for as cheap as $.50 if you buy more than one. I used a TSOP4838.

  • IR Emitter LED: Again, any emitter should work, and they are dirt cheap. I used Lite-On LTE-5208A which are around $0.12 each. Buy a pack of 20 or so, you'll find uses for them.

  • 150 ohm Resistor: If you use the same parts as I have listed above, a 150 ohm resistor is needed. One resistor is around $0.02. Again, I recommend buying an assortment since they're so cheap. If you're not using identical components as those listed above, you will need to figure out what resistor is needed for your specific needs. I can help with this if needed.

  • Real Time Clock: An RTC is needed so that your Arduino knows what time it is and your program can resume operation properly after a power outage. I recommend any RTC based on the DS1307. You can buy kits that must be assembled, assembled boards that need the headers soldered on, or complete plug and play units. It's up to you. Cost is between $5-$25.

  • LCD Screen: The code supports a 20 x 4 LCD screen, but it is completely optional. If you want the extra ease of use and eye-candy, I suggest the 20 x 4 backlit LCD from Adafruit.

O2surplus 07-24-2013 03:52 PM

Do the Current Led fixtures accept a 5V PWM dimming signal directly?

Indychus 07-24-2013 04:04 PM

Quote:

Originally Posted by O2surplus (Post 3899769)
Do the Current Led fixtures accept a 5V PWM dimming signal directly?

I'm not sure what is going on on the Current side of things. One of my goals was to maintain the remote functionality, so I chose to go with the IR and haven't looked into the fixture itself much. I'm fairly sure that it will be 5V PWM if you investigate it though.

I have the IR protocol in hex, I'll be posting it shortly! You should be able to skip the IR stuff and wire the IR lead from the fixture directly to the Arduino if wanted.

crazymittens 07-24-2013 04:04 PM

Someone in the lighting sub-forum was asking about this...I'm curious if there are any biological benefits to this. Is this cool (it is), or functionally cool?

Indychus 07-24-2013 04:11 PM

Quote:

Originally Posted by crazymittens (Post 3899913)
Someone in the lighting sub-forum was asking about this...I'm curious if there are any biological benefits to this. Is this cool (it is), or functionally cool?

I've seen a few studies implying that fish in a system with dynamic lighting are more sexual active, and therefor healthier... But I don't think anything absolutely conclusive has been done. My main goal is the cool factor. With the Current product announcement and their own controllers out now, there isn't much reason to build your own economically speaking (I was a little shocked at how low their MSRP is), but this does allow you more control and the ability to tailor it exactly to your needs.

When I first started this, Current had no controller available. So I had a nice new light with dozens of functions that I had to control manually. It was a bit of a bummer.

Indychus 07-24-2013 04:22 PM

I've never had occasion to post pics of my tanks (mainly because they look pretty shabby compared to many on here), so here's a pair of them. I have 6 total, but these are the only ones worthy of being posted.

Here's my 20H. This is my favorite tank right now. There's a pair of male dwarf gourami, a bunch of chili rasbora, some otos, and some cories in here.
http://i553.photobucket.com/albums/j...psa7189230.jpg

And this is my 55. It's not looking too good right now, as it's recovering from the destruction caused by a yellow-bellied slider. This is the tank that the LED+ and automation is going on.
http://i553.photobucket.com/albums/j...ps136ac9cc.jpg

mistergreen 07-24-2013 04:32 PM

I'd suggest you get a Real Time Clock (RTC) component for your arduino. It'll keep time and date for you even in a power outage. It comes with a battery.

Indychus 07-24-2013 04:36 PM

Quote:

Originally Posted by mistergreen (Post 3900121)
I'd suggest you get a Real Time Clock (RTC) component for your arduino. It'll keep time and date for you even in a power outage. It comes with a battery.

I have one on the way! Using a software clock right now, but syncing to the PC is proving to be a headache. I finally broke down and ordered an RTC.

O2surplus 07-24-2013 04:40 PM

Quote:

Originally Posted by Indychus (Post 3899897)
I'm not sure what is going on on the Current side of things. One of my goals was to maintain the remote functionality, so I chose to go with the IR and haven't looked into the fixture itself much. I'm fairly sure that it will be 5V PWM if you investigate it though.

I have the IR protocol in hex, I'll be posting it shortly! You should be able to skip the IR stuff and wire the IR lead from the fixture directly to the Arduino if wanted.

Oh I see what you're up to now. Don't you have to install the Arduino between the IR controller and the internal led drivers? Seems to me that you would, as the IR controller is only programmed to perform the commands that it receives from the IR remote control? The Arduino could then perform other programmed "tricks" that aren't available from the IR remote, and the IR controller would simply become an input to the Arduino.

Indychus 07-24-2013 04:41 PM

Now for the hardware!

This is the basic setup. The IR range is limited to around 3 feet like this due to the current limit on the Arduino's output channel. Later, I will build an amplifier to boost the range to hopefully around 10 feet. Since the controller will sit right next to the IR receiver on the fixture, this isn't really an issue.

Here's what your IR receiver should look like:
http://i553.photobucket.com/albums/j...psd3d805ae.jpg

Now connect it to your Arduino, like this:
http://i553.photobucket.com/albums/j...ps4941ad9f.jpg

The IR lead is connected to the Arduino's 2 pin. If you're not using an UNO, any PWM or digital channel will work. Connect the 5V and ground leads to the appropriate pins on the Arduino.

Indychus 07-24-2013 04:44 PM

Quote:

Originally Posted by O2surplus (Post 3900201)
Oh I see what you're up to now. Don't you have to install the Arduino between the IR controller and the internal led drivers? Seems to me that you would, as the IR controller is only programmed to perform the commands that it receives from the IR remote control? The Arduino could then perform other programmed "tricks" that aren't available from the IR remote, and the IR controller would simply become an input to the Arduino.

Yeah, it's certainly possible to do it that way, and that's what I plan to do eventually. Right now, the Arduino is basically just mimicking the remote, so custom functions are not possible. I figured this method would be more appetizing to most people since they wouldn't have to modify their fixture.

Indychus 07-24-2013 05:00 PM

Now for the emitter. LED's need to be installed with the proper polarity, in most cases the negative lead is denoted with a flat spot on the side of the bulb.

http://i553.photobucket.com/albums/j...ps9c54d3b6.jpg

Next, you need a resistor to limit the current through the LED and protect the channel on the Arduino. In this case, I used a 150 ohm resistor which gives me a current of 33 mA... safely below the Arduino's rating of 40 mA for this channel. The emitter is designed to operate at 100 mA, so it has a short range right now, but a transistor amplifier will fix that in the near future.

Wire it like this:
http://i553.photobucket.com/albums/j...ps58a48e69.jpg

The perspective in that picture is off, so don't let it fool you. The yellow wire is connected to the GND pin, and the green to pin 13. Again, you can use any non-digital pin for this purpose. Connect the ground to the - side of the emitter, then the + side to the resistor, then the resistor to channel 13 on the Arduino.

Note that the most current code uses PWM pin 3 instead of pin 13


That's all there is for the hardware, until I add an amplifier in the future and an RTC for timing. This is enough for basic functions and pretty much operates as an overly-complicated timer. More to come though...

scotie aquatic 07-24-2013 05:04 PM

Here we go, im subcribed. And will be asking alot of questions soon lol ;)


All times are GMT. The time now is 12:10 PM.

Powered by vBulletin®
Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.