The Planted Tank Forum banner

Guide: Arduino based LED controller for Current Satellite LED+

174K views 715 replies 69 participants last post by  Dahammer 
#1 · (Edited)
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.
 
See less See more
#267 · (Edited)
Here is the code that I've been tinkering with. I won't be home until tomorrow night, so I can't test it with my fixture until then (although I do have my controller with me and it runs fine on it). And I don't have an LCD to test that end of it, so could some of you try it out and see how it does?

I bumped the version up to a major release, since there are so many changes in the way the code works. Here are some of the highlights:

  • Redirected STDOUT to the UART so that the fprint family of functions could be used instead of Serial.print()
  • Moved all strings and the IR codes to PROGMEM
  • Optimized some of routines & removed others

The main thing is the switch to fprint. I grew tired of the lack of formatting capabilities of Serial.print(). fprint adds some overhead, but it is much much more powerful and by the time I was done the code is only around 200 bytes larger than previous versions. It's also a lot cleaner looking without all of the Serial.print calls.

Version 4.0
Code:
///////////////////////////////////////////////////////////////////
// Current Satellite LED+ Controller  V4.0                       //
//   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.
//
// You can test the IR commands via the Arduino software's serial monitor
// by sending in a value from 1 - 32. Values follow the remote control, 
// left to right, top to bottom (e.g 1 = Orange, 2 = Blue, 21 = Moon1, etc)
//
// 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;

byte randAnalogPin = 0;   // This needs to be set to an unused Analog pin, Used by RandomStorm()
#define LCD_COLS 20      // Number of columns on the LCD (e.g. 16, 20, etc)
#define LCD_ROWS 4       // Number of rows on the LCD (e.g. 2, 4, etc)
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Current Satellite+ IR Codes (NEC Protocol)
unsigned long codeHeader = 0x20DF; // Always the same
// Remote buttons listed left to right, top to bottom
PROGMEM unsigned int arrCodes[32] = {0x3AC5,  // 1 -  Orange
                                     0xBA45,  // 2 -  Blue
                                     0x827D,  // 3 -  Rose
                                     0x02FD,  // 4 -  Power On/Off
                                     0x1AE5,  // 5 -  White
                                     0x9A65,  // 6 -  FullSpec
                                     0xA25D,  // 7 -  Purple
                                     0x22DD,  // 8 -  Play/Pause
                                     0x2AD5,  // 9 -  Red Up
                                     0xAA55,  // 10 - Green Up
                                     0x926D,  // 11 - Blue Up
                                     0x12ED,  // 12 - White Up
                                     0x0AF5,  // 13 - Red Down
                                     0x8A75,  // 14 - Green Down
                                     0xB24D,  // 15 - Blue Down
                                     0x32CD,  // 16 - White Down
                                     0x38C7,  // 17 - M1 Custom
                                     0xB847,  // 18 - M2 Custom
                                     0x7887,  // 19 - M3 Custom
                                     0xF807,  // 20 - M4 Custom
                                     0x18E7,  // 21 - Moon 1
                                     0x9867,  // 22 - Moon 2
                                     0x58A7,  // 23 - Moon 3
                                     0xD827,  // 24 - Dawn/Dusk
                                     0x28D7,  // 25 - Cloud 1
                                     0xA857,  // 26 - Cloud 2
                                     0x6897,  // 27 - Cloud 3
                                     0xE817,  // 28 - Cloud 4
                                     0x08F7,  // 29 - Storm 1
                                     0x8877,  // 30 - Storm 2
                                     0x48B7,  // 31 - Storm 3
                                     0xC837}; // 32 - Storm 4

// These are the messages that print on the serial monitor & lcd when each IR code is sent
#define MAX_MSG_LEN 13  // Maximum length of the arrMSG messages
prog_char PROGMEM arrMSG[][MAX_MSG_LEN+1] = {"Orange",        "Blue",          "Rose",          "Power On/Off",
                                             "White",         "Full Spectrum", "Purple",        "Play/Pause",
                                             "Red Up",        "Green Up",      "Blue Up",       "White Up",
                                             "Red Down",      "Green Down",    "Blue Down",     "White Down",
                                             "Custom Mix 1",  "Custom Mix 2",  "Custom Mix 3",  "Custom Mix 4",
                                             "Moonlight 1",   "Moonlight 2",   "Moonlight 3",   "Dawn/Dusk",
                                             "Cloud Cover 1", "Cloud Cover 2", "Cloud Cover 3", "Cloud Cover 4",
                                             "Storm 1",       "Storm 2",       "Storm 3",       "Storm 4"};

void SetAlarms()
{
  // Set up your desired alarms here
  // The default value of dtNBR_ALARMS is 6 in TimeAlarms.h.
  // This code sets 9 alarms by default, so you'll need to change dtNBR_ALARMS to 9 or more
  // Changes the times to suit yourself. Add as many alarms as you like, just stay within dtNBR_ALARMS
  Alarm.alarmRepeat(7,00,0, DawnDusk);
  Alarm.alarmRepeat(9,00,0, Cloud2);     // (HR,MIN,SEC,FUNCTION)
  Alarm.alarmRepeat(10,00,0, FullSpec);
  Alarm.alarmRepeat(16,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, RandomStorm);
  RandomStorm();  // Sets up intial storm so we don't have wait until alarm time
}

// Setup file structure & output function for stdout redirect
static FILE uartout = {0}; // create a FILE structure to reference our UART output function
static int uart_putchar (char c, FILE *stream)
{
  // Serial write fumction
  Serial.write(c);
  return 0;
}

// File structure & output function for lcd output
static FILE lcdout = {0} ;      // lcd FILE structure
static int lcd_putchar(char ch, FILE* stream)
{
  // lcd write function
  lcd.write(ch);
  return (0);
}

void setup()
{
  Wire.begin();
  RTC.begin();
  Serial.begin(9600);
  lcd.begin(LCD_COLS, LCD_ROWS);
  
  // fill in the UART file descriptor with pointer to writer.
  fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
  stdout = &uartout;  // Output stdout to UART
   
  // fill in lcd file descriptor (we'll use fprintf to output to it)
  fdev_setup_stream (&lcdout, lcd_putchar, NULL, _FDEV_SETUP_WRITE);
  
  if (! RTC.isrunning()) {
    // If no RTC is installed, set time to compile time at each reset
    printf_P(PSTR("RTC is NOT running!\n"));  // Store this string in PROGMEM
    RTC.adjust(DateTime(__DATE__, __TIME__));
    }
  
  setSyncProvider(syncProvider);     // reference our syncProvider function instead of RTC_DS1307::get()
  
  printf_P(PSTR("Time: %02d:%02d:%02d\n"), hour(), minute(), second());  // Print the time
  SetAlarms();  // Set up above alarms
  
  // Print available SRAM for debugging, comment out if you want
  printf_P(PSTR("SRAM: %d\n"), freeRam());
  
  printf_P(PSTR("To test IR codes, send 1 - 32\n"));
}

void loop()
{
  if (Serial.available() > 0) {
    delay(5); //Wait for transmission to finish
    TestCodes(SerialReadInt());  // Go grab IR code and send it
  }
  Alarm.delay(100);   // Service alarms & wait (msec)
  lcd.setCursor(0,0);
  fprintf(&lcdout, "%02d:%02d", hour(), minute());  // Print the time HH:MM to the lcd
}

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

void RandomStorm ()
{ 
  // Schedules a storm between 1 & 8 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 RandomStorm
  byte RM = random(59);
  byte RS = random(59);
  byte TSDurationH = random(2);
  byte TSDurationM = random(59);
  
  lcd.setCursor(0,1);
  if (RH >= 13 && RH <= 21)
    { // If 1:00PM - 8:00PM schedule a storm
      Alarm.alarmOnce(RH,RM,RS,Storm2);
      
      // Store strings in PROGMEM
      printf_P(PSTR("Scheduled Storm: %02d:%02d:%02d Duration: %02d:%02d\n"), RH, RM, RS, TSDurationH, TSDurationM);
      fprintf_P(&lcdout, PSTR("Sch Storm: %02d:%02d"), RH, RM);
          
      if ((RH + TSDurationH) < 19)   // Switch to Cloud2 if storm ends between 1:00-6:59pm
        {
          Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,Cloud2);
        }
      else if ((RH + TSDurationH) < 21)  // Switch to DawnDusk if storm ends between 7:00-8:59pm
        {
          Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,DawnDusk);
        }
      else                               // Otherwise, switch to Night2
        {
          Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,Moon2);
        }
    }
    else
    { // Don't really need this, but it can stay till we need the space
      printf_P(PSTR("No storm today\n"));
      fprintf_P(&lcdout, PSTR("No storm today\n"));
    }
}

int SerialReadInt()
{
  // Reads first 2 bytes from serial monitor; anything more is tossed
  byte i;
  char inBytes[3];
  char * inBytesPtr = &inBytes[0];  // Pointer to first element
    
    for (i=0; i<2; i++)             // Only want first 2 bytes
      inBytes[i] = Serial.read();
    inBytes[i] =  '\0';             // Put NULL character at the end
    while (Serial.read() >= 0)      // If anything else is there, throw it away
      ; // do nothing      
    return atoi(inBytesPtr);        // Convert to decimal
}

void TestCodes (int cmd)
{
  // Handles commands sent in from the serial monitor
  if (cmd >= 1 && cmd <= 32)
    { 
      // cmd must be 1 - 32
      SendCode(cmd-1, 1);
    }
    else { printf_P(PSTR("Invalid Choice\n")); }
}

void SendCode ( int cmd, byte numTimes )
{ // cmd = the element of the arrCode[] array that holds the IR code to be sent
  // numTimes = number of times to emmit the command
  // Shift header 16 bits to left, fetch code from PROGMEM & add it
  unsigned long irCode = (codeHeader << 16) + pgm_read_word_near(arrCodes + cmd);
  for( byte i = 0; i < numTimes; i++)
  {
    irsend.sendNEC(irCode,32); // Send/emmit code
    delay(100);
  }
  // Print the string associated with the IR code & the time
  //printf("%S: 0x%lx %02d:%02d:%02d\n", arrMSG[cmd], irCode, hour(), minute(), second());
  printf("%S: %02d:%02d:%02d\n", arrMSG[cmd], hour(), minute(), second());
  if (LCD_COLS == 16)
    {
      lcd.setCursor(0,1);
      fprintf(&lcdout, "%-16S", arrMSG[cmd]);
    }
  else
    {
      lcd.setCursor(6,0);
      fprintf(&lcdout, "%-14S", arrMSG[cmd]);
    }
}

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

// IR Code functions, called by alarms
void Orange() {SendCode(0,2);}
void Blue() {SendCode(1,2);}
void Rose() {SendCode(2,2);}
void PowerOnOff() {SendCode(3,1);}
void White() {SendCode(4,2);}
void FullSpec() {SendCode(5,2);}
void Purple() {SendCode(6,2);}
void Play() {SendCode(7,1);}
void RedUp() {SendCode(8,1);}
void GreenUp() {SendCode(9,1);}
void BlueUp() {SendCode(10,1);}
void WhiteUp() {SendCode(11,1);}
void RedDown() {SendCode(12,1);}
void GreenDown() {SendCode(13,1);}
void BlueDown() {SendCode(14,1);}
void WhiteDown() {SendCode(15,1);}
void M1Custom() {SendCode(16,2);}
void M2Custom() {SendCode(17,2);}
void M3Custom() {SendCode(18,2);}
void M4Custom() {SendCode(19,2);}
void Moon1() {SendCode(20,2);}
void Moon2() {SendCode(21,2);}
void Moon3() {SendCode(22,2);}
void DawnDusk() {SendCode(23,2);}
void Cloud1() {SendCode(24,2);}
void Cloud2() {SendCode(25,2);}
void Cloud3() {SendCode(26,2);}
void Cloud4() {SendCode(27,2);}
void Storm1() {SendCode(28,2);}
void Storm2() {SendCode(29,2);}
void Storm3() {SendCode(30,2);}
void Storm4() {SendCode(31,2);}
 
#270 ·
I loaded up v4.0 this afternoon. My LCD display isn't displaying right, but I'm using a 16x2 LCD, not a 20x4 like Indychus. The serial monitor is displaying correctly.

I had it working on v3.5, but apparently I didn't do the edits correctly in v4.

No problem...that will be something for me to play with until my 20x4 LCD arrives in the mail in the next day or so. Then I'll be on the same page as you guys.

Got my 18" LED+ yesterday and have it installed on the 6gal shrimp tank. It and the 20 long are both being run on a controller running v3.5 and look great!
 
#275 · (Edited)
Are you getting any output on the LCD or is it simply blank? Re-reading what you posted earlier, I'm guessing the output is there but just not correct?

I see a problem. Try changing line 246 (last line in the SendCode() function) from this:
Code:
fprintf(&lcdout, "%-14S", "%-14s: %02d:%02d", arrMSG[cmd], hour(), minute());
to this:
Code:
fprintf(&lcdout, "%-10S: %02d:%02d", arrMSG[cmd], hour(), minute());
I had too many specifiers in the code and too many spaces for that size screen. I'll fix it to where you don't have to hunt down & change the code for a 16x2 LCD once we confirm it's working as intended.
 
#273 ·
You need to remove 4 spaces from each of the text strings in the IR codes. Each string is 14 characters long and only needs to be 10 for a 16 x 2. The extra spaces will wrap around and overwrite anything you have on line 2.

Sent from my HTC One X
 
#276 ·
Well, nevermind... Just noticed its different in the V4 that dahammer posted earlier. I haven't tried that one yet. No clue how to place text at certain spots on the LCD without setting the cursor.

Sent from my HTC One X

Check out a printf reference.

It looks complex, but it really isn't once you understand how it works. I "think" the only specifier that doesn't work on the Arduino is the one for floats.

Code:
fprintf(&lcdout, "%-10S: %02d:%02d", arrMSG[cmd], hour(), minute());
&lcdout = reference to the file stream you want to print too. Could also be &uartout in this case.

%-10S
% = format specifier follows
-10 = left justify 10 characters wide
S = string in PROGMEM (arrMSG[cmd] in this case)

%02d: = HH:

% = format specifier follows
0 = adds the leading zero for numbers less than 10
2 = 2 characters wide
d = decimal value (hour() in this case)
: = colon between the HH:MM

So what we end up with is yyyyyyyyyy: HH:MM

To get familiar with it, code yourself a simple test sketch and play with printf. Hope that helps.
 
#278 ·
Thanks for all the suggestions, unfortunately they came a little too late for me to try out last night and I only had a few minutes to play with them before work this morning.

I loaded up the revised v4 and made the couple of edits for my 16x2 display. It came up with the clock on line 1 and "No Thunderstorm today" on line 2. I then sent a code 6 (Full Spectrum) command in the serial monitor. The Serial monitor displayed correctly.

Here is what I got on the LCD. Excuse the cruddy cell phone pic. FYI...this display works fine on v3.5...

Hopefully today my 20x4 LCD will get here along with some DS18b20 temperature probes. Then my weekend play project will have all it's parts.
 

Attachments

#279 ·
Sorry Caver, I must have been brain dead last night from lack of sleep. The fix I added last night didn't fix it. I shouldn't even be printing the time to the LCD in SendCode(). Anyway, I think I have it now. Recopy/paste the code and try it. I think I have it fixed now.....maybe..

Also, I added the below definitions at the top to keep folks from having to search through the code and change the LCD size. Just change them to reflect your LCD size. I've only implemented 16x2 and 20x4, though.

Code:
#define LCD_COLS 16      // Number of columns on the LCD (e.g. 16, 20, etc)
#define LCD_ROWS 2       // Number of rows on the LCD (e.g. 2, 4, etc)
Unfortunately, the * width specifier isn't implemented in the Arduino, so I couldn't use it to set the number of spaces in the fprintf() call in SendCode.
 
#280 ·
Don't worry about fixing the 16x2 display just for me. I should have a 20x4 this afternoon.

This is all very instructional for me to work with the Arduino tutorials online. Then play with the new versions of the controller code from you guys. I feel like I'm picking up things pretty well (for me!).

I should also have a couple of DS18b20 thermometer sensors to play with. I'm going to build up a dual channel thermometer to monitor my two tanks. There's not much code to the thermometer. I may have to see if I can figure out how to splice it into the controller code so one box can do both jobs...then again I may be way off in left field with that idea...:confused1:
 
#281 ·
I have a 16x2 LCD on the way to go in mine. The 20x4 won't fit in my enclosure without some modification, so I'll use the 16x2. If you don't mind, before you switch to the 20x4, could you see if the 16x2 works correctly? Just curious.

I looked at the thermometer libraries yesterday and looked like it wouldn't be too much trouble to add it to this project. I almost ordered one of the stainless steel tipped DS18B20 sensors last night when I ordered the LCD but decided not too because I didn't think I'd use it. My controller is across the room from my tank inside a curio cabinet, so displaying temp on it just isn't practical.

Anyway you could probably just set the sensor up as shown here and then just add a function for an alarm to call every hour or so (or ever how often you want it updated).

Here is another good reference to the printf set of functions, if you interested in learning how to use it's formatting capabilities:
http://www.eecs.wsu.edu/~cs150/reading/printf.htm
 
#287 ·
If you don't mind, before you switch to the 20x4, could you see if the 16x2 works correctly? Just curious.
Hi DaHammer,

I loaded up your modified code on my 16x2 display. It's working much better, but not quite perfectly.

Basically it isn't clearing all the text from the previous message and you can see remaining characters after the new message.

Pic 1 is your code with no mods.
Pic 2 is modded to display the last remote code sent on line 2.
Pic 3 is the power off/on code sent after a full spectrum code. You can see the m left over from the previous code.

Here's some very crummy cell phone pics...
 

Attachments

#282 ·
No problem DaHammer.

I'll try the code on the 16x2 after I get home this afternoon. I probably won't get the 20x4 set up till later in the weekend.

As it happens, my controller is sitting on a shelf right next to my tanks, so running the temp sensor wires shouldn't be too big a deal. The Dallas Semi link you posted is one of the ones I was reading. I like that you can put multiple sensors on the same port.

I'm hoping there's enough room in the Arduino to incorporate the thermometer code with the controller. My current WAG (wild a**ed guess) is to let the light controller use display line 1 & 2 and the thermometer use lines 3 & 4.

Either way I'm really digging the mode switching on these lights in my tanks. The simple on/off from the timer was just too dull and I didn't like having to manually grab the remote to take advantage of all the features on the light. Plus I can fine tune the lights for the needs of the plants in the tank.

I'll try to post this evening on how the code works with the 16x2 display.
 
#283 ·
I have mine set up with a thermometer right now, it just displays the temp in the bottom left corner of the display. There is enough room on the bottom line to display up to three individual temps. Alternately, you could do 2 and the average. I need to work on moving it into the latest code that dahammer posted, then I'll post it.

Sent from my HTC One X
 
#288 ·
Ok, thanks. I had assumed that the code in the previous version handled the 16x2 correctly and didn't really pay any attention when I copied it over. The code messages are too long to display on the same line as the time on the 16x2. So it's going to be a trade off either way you go.

I've modified the code and reposted it to where the time in on the 1 row by itself on the 16x2 and the code messages display on the 2nd row by themselves. The down side is that the storm time gets overwritten, so you don't if one is scheduled or not unless you catch it before the next state change.

You could also just shorten the code messages in the array up at the top of the source, to where the time and message would both fit on the same line. This is probably what I'll do when mine comes in, as just a few characters to let me know what mode it is is all I really need.

Or you could do away with the time altogether and have the messages on 1 line and the scheduled storm time on the other.
 
#289 · (Edited)
For me personally...

I'm not using the storms, so having the time on line 1 and the mode on line 2 is my preferred method.

I got my 20x4 display and some DS18b20's today. I got the temp sensors working...kinda...:icon_roll

I got the sensor addresses and they work when they're displayed in the serial monitor, but they don't want to work with my LCD 16x2 display shield board. There's some kind of scrambling of the temperature data from the sensors and the temp displays as trash characters or 0°C. The 16x2 display is a full on shield board with buttons and everything. I haven't gone through it to see what pins the buttons may be on. I suspect those are conflicting with the temp sensors.

Tomorrow I'm going to try to wire up the 20x4 and see if it works better. I'll need the extra two lines of display to make it work with the light controller anyway. That board is just the display, so I can wire it how I want.

I haven't committed to a specific enclosure for this yet, so it can grow as it wants and I'll work out the box later.

Fun...fun...fun...:hihi:
 
#291 ·
Sorry,

I probably should have been more specific. I haven't tried to integrate the thermometer code into the light controller code yet. Indychus said he did and I was going to wait and see how he did it.

I was playing with some of the DS18b20 tutorial sketches I found online. My plan was to verify I could get the thermometers working as a standalone before I tried to splice it into the controller. As it is, I found this apparent compatibility problem with my LCD shield board (a Sainsmart btw).

The 20x4 display is significantly larger than the 16x2. I'm going to have to do a completely new physical layout on some perf board to make this pretty.

Looks like a trip down to Rat Shack or Frys today. Some of the people there know me on a first name basis...:icon_roll
 
#294 ·
OK...

I made a stab at combining the v4 controller code with a 2 sensor thermometer tutorial off the web.

It seems to work. I'm going to let it run on the bench for a while and see what happens.

I can't get my code to show up in the nice scrollable box like you guys have done. Besides, I'm sure I committed several dozen programming sins combining the sketches and I wouldn't want you to fall out of your chairs laughing...:eek5:
 

Attachments

#295 ·
Pretty much anywhere on a computer if you wrap something with [CODE.] Text here [/CODE.] it will put it into that format. Remove the periods in the brackets for it to work. I haven't messed with version 4 too much, I'm still running 3.7. Im trying to figure out exactly how the new code works before I update to it.

I damn sure won't do any laughing... I can write just enough code to be dangerous. Every time I program something at work the software guys redo it. Not in my job description haha.

Sent from my HTC One X
 
#296 ·
Here's my attempt at combining v4 and thermometer code.

Code:
///////////////////////////////////////////////////////////////////
//   Current Satellite LED+ Controller  V4.1b                    //
//   Indychus...Dahammer...mistergreen @ plantedtank.net         //
//   Clumsy mod to include dual channel thermometer              //
//   by Caver @ plantedtank.net                                  //
//   This code is public domain.  Pass it on.                    //
//   Confirmed on Arduino UNO 1.0.5                              //
//                                                               //
///////////////////////////////////////////////////////////////////
//
// 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.
//
// You can test the IR commands via the Arduino software's serial monitor
// by sending in a value from 1 - 32. Values follow the remote control, 
// left to right, top to bottom (e.g 1 = Orange, 2 = Blue, 21 = Moon1, etc)
//
// 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 <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal.h>

RTC_DS1307 RTC;
IRsend irsend;

byte randAnalogPin = 0;   // This needs to be set to an unused Analog pin, Used by RandomStorm()
#define LCD_COLS 20      // Number of columns on the LCD (e.g. 16, 20, etc)
#define LCD_ROWS 4       // Number of rows on the LCD (e.g. 2, 4, etc)
LiquidCrystal lcd(10, 8, 7, 6, 5, 4);

// Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// Assign the addresses of your 1-Wire temp sensors.
// See the tutorial on how to obtain these addresses:
// http://www.hacktronics.com/Tutorials/arduino-1-wire-address-finder.html

DeviceAddress insideThermometer = { 0x28, 0x98, 0x82, 0xA8, 0x04, 0x00, 0x00, 0x6D }; //Red
DeviceAddress outsideThermometer = { 0x28, 0xA6, 0x33, 0xA8, 0x04, 0x00, 0x00, 0x11 }; //blue

// Current Satellite+ IR Codes (NEC Protocol)
unsigned long codeHeader = 0x20DF; // Always the same
// Remote buttons listed left to right, top to bottom
PROGMEM unsigned int arrCodes[32] = {0x3AC5,  // 1 -  Orange
                                     0xBA45,  // 2 -  Blue
                                     0x827D,  // 3 -  Rose
                                     0x02FD,  // 4 -  Power On/Off
                                     0x1AE5,  // 5 -  White
                                     0x9A65,  // 6 -  FullSpec
                                     0xA25D,  // 7 -  Purple
                                     0x22DD,  // 8 -  Play/Pause
                                     0x2AD5,  // 9 -  Red Up
                                     0xAA55,  // 10 - Green Up
                                     0x926D,  // 11 - Blue Up
                                     0x12ED,  // 12 - White Up
                                     0x0AF5,  // 13 - Red Down
                                     0x8A75,  // 14 - Green Down
                                     0xB24D,  // 15 - Blue Down
                                     0x32CD,  // 16 - White Down
                                     0x38C7,  // 17 - M1 Custom
                                     0xB847,  // 18 - M2 Custom
                                     0x7887,  // 19 - M3 Custom
                                     0xF807,  // 20 - M4 Custom
                                     0x18E7,  // 21 - Moon 1
                                     0x9867,  // 22 - Moon 2
                                     0x58A7,  // 23 - Moon 3
                                     0xD827,  // 24 - Dawn/Dusk
                                     0x28D7,  // 25 - Cloud 1
                                     0xA857,  // 26 - Cloud 2
                                     0x6897,  // 27 - Cloud 3
                                     0xE817,  // 28 - Cloud 4
                                     0x08F7,  // 29 - Storm 1
                                     0x8877,  // 30 - Storm 2
                                     0x48B7,  // 31 - Storm 3
                                     0xC837}; // 32 - Storm 4

// These are the messages that print on the serial monitor & lcd when each IR code is sent
#define MAX_MSG_LEN 13  // Maximum length of the arrMSG messages
prog_char PROGMEM arrMSG[][MAX_MSG_LEN+1] = {"Orange",        "Blue",          "Rose",          "Power On/Off",
                                             "White",         "Full Spectrum", "Purple",        "Play/Pause",
                                             "Red Up",        "Green Up",      "Blue Up",       "White Up",
                                             "Red Down",      "Green Down",    "Blue Down",     "White Down",
                                             "Custom Mix 1",  "Custom Mix 2",  "Custom Mix 3",  "Custom Mix 4",
                                             "Moonlight 1",   "Moonlight 2",   "Moonlight 3",   "Dawn/Dusk",
                                             "Cloud Cover 1", "Cloud Cover 2", "Cloud Cover 3", "Cloud Cover 4",
                                             "Storm 1",       "Storm 2",       "Storm 3",       "Storm 4"};

void SetAlarms()
{
  // Set up your desired alarms here
  // The default value of dtNBR_ALARMS is 6 in TimeAlarms.h.
  // This code sets 9 alarms by default, so you'll need to change dtNBR_ALARMS to 9 or more
  // Changes the times to suit yourself. Add as many alarms as you like, just stay within dtNBR_ALARMS
  Alarm.alarmRepeat(13,00,0, PowerOnOff);
  Alarm.alarmRepeat(13,02,0, Moon1);
  Alarm.alarmRepeat(13,45,0, DawnDusk);
  Alarm.alarmRepeat(14,00,0, Cloud2);     // (HR,MIN,SEC,FUNCTION)
  Alarm.alarmRepeat(15,00,0, Cloud1);
  Alarm.alarmRepeat(17,00,0, FullSpec);
  Alarm.alarmRepeat(19,00,0, Cloud1);
  Alarm.alarmRepeat(20,00,0, Cloud2);
  Alarm.alarmRepeat(21,45,0, Moon3);
  Alarm.alarmRepeat(22,00,0, Moon1);
  Alarm.alarmRepeat(23,00,0, Moon2);
  Alarm.alarmRepeat(23,59,0, PowerOnOff);
   
  // Comment these out if you don't want the chance of a random storm each day
  // Alarm.alarmRepeat(12,00,00, RandomStorm);
  // RandomStorm();  // Sets up intial storm so we don't have wait until alarm time
}

// Setup file structure & output function for stdout redirect
static FILE uartout = {0}; // create a FILE structure to reference our UART output function
static int uart_putchar (char c, FILE *stream)
{
  // Serial write fumction
  Serial.write(c);
  return 0;
}

// File structure & output function for lcd output
static FILE lcdout = {0} ;      // lcd FILE structure
static int lcd_putchar(char ch, FILE* stream)
{
  // lcd write function
  lcd.write(ch);
  return (0);
}

void setup()
{
  Wire.begin();
  RTC.begin();
  Serial.begin(9600);
  lcd.begin(LCD_COLS, LCD_ROWS);
  
  // Start up the library
sensors.begin();
// set the resolution to 10 bit (good enough?)
sensors.setResolution(insideThermometer, 9);
sensors.setResolution(outsideThermometer, 9);

lcd.begin(20,4); // columns, rows. use 16,2 for a 16x2 LCD, etc.
lcd.clear(); // start with a blank screen
  
  // fill in the UART file descriptor with pointer to writer.
  fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
  stdout = &uartout;  // Output stdout to UART
   
  // fill in lcd file descriptor (we'll use fprintf to output to it)
  fdev_setup_stream (&lcdout, lcd_putchar, NULL, _FDEV_SETUP_WRITE);
  
  if (! RTC.isrunning()) {
    // If no RTC is installed, set time to compile time at each reset
    printf_P(PSTR("RTC is NOT running!\n"));  // Store this string in PROGMEM
    RTC.adjust(DateTime(__DATE__, __TIME__));
    }
  
  setSyncProvider(syncProvider);     // reference our syncProvider function instead of RTC_DS1307::get()
  
  printf_P(PSTR("Time: %02d:%02d:%02d\n"), hour(), minute(), second());  // Print the time
  SetAlarms();  // Set up above alarms
  
  // Print available SRAM for debugging, comment out if you want
  printf_P(PSTR("SRAM: %d\n"), freeRam());
  
  printf_P(PSTR("To test IR codes, send 1 - 32\n"));
}

void loop()
{
  if (Serial.available() > 0) {
    delay(5); //Wait for transmission to finish
    TestCodes(SerialReadInt());  // Go grab IR code and send it
  }
  Alarm.delay(100);   // Service alarms & wait (msec)
  lcd.setCursor(0,0);
  fprintf(&lcdout, "%02d:%02d", hour(), minute());  // Print the time HH:MM to the lcd
    
delay(1000);
sensors.requestTemperatures();
lcd.setCursor(0,2);
lcd.print("Tank 1: ");
printTemperature(insideThermometer);
lcd.setCursor(0,3);
lcd.print("Tank 2: ");
printTemperature(outsideThermometer);
} 

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

void RandomStorm ()
{ 
  // Schedules a storm between 1 & 8 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 RandomStorm
  byte RM = random(59);
  byte RS = random(59);
  byte TSDurationH = random(2);
  byte TSDurationM = random(59);
  
  lcd.setCursor(0,1);
  if (RH >= 13 && RH <= 21)
    { // If 1:00PM - 8:00PM schedule a storm
      Alarm.alarmOnce(RH,RM,RS,Storm2);
      
      // Store strings in PROGMEM
      printf_P(PSTR("Scheduled Storm: %02d:%02d:%02d Duration: %02d:%02d\n"), RH, RM, RS, TSDurationH, TSDurationM);
      fprintf_P(&lcdout, PSTR("Sch Storm: %02d:%02d"), RH, RM);
          
      if ((RH + TSDurationH) < 19)   // Switch to Cloud2 if storm ends between 1:00-6:59pm
        {
          Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,Cloud2);
        }
      else if ((RH + TSDurationH) < 21)  // Switch to DawnDusk if storm ends between 7:00-8:59pm
        {
          Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,DawnDusk);
        }
      else                               // Otherwise, switch to Night2
        {
          Alarm.alarmOnce((RH + TSDurationH),(RM + TSDurationM),RS,Moon2);
        }
    }
    else
    { // Don't really need this, but it can stay till we need the space
      printf_P(PSTR("No storm today\n"));
      fprintf_P(&lcdout, PSTR("No storm today\n"));
    }
}

void printTemperature(DeviceAddress deviceAddress)
{
float tempC = sensors.getTempC(deviceAddress);
if (tempC == -127.00) {
lcd.print("Error");
} else {
// lcd.print(tempC);
// lcd.print("/");
lcd.print(DallasTemperature::toFahrenheit(tempC));
}
}
int SerialReadInt()
{
  // Reads first 2 bytes from serial monitor; anything more is tossed
  byte i;
  char inBytes[3];
  char * inBytesPtr = &inBytes[0];  // Pointer to first element
    
    for (i=0; i<2; i++)             // Only want first 2 bytes
      inBytes[i] = Serial.read();
    inBytes[i] =  '\0';             // Put NULL character at the end
    while (Serial.read() >= 0)      // If anything else is there, throw it away
      ; // do nothing      
    return atoi(inBytesPtr);        // Convert to decimal
}

void TestCodes (int cmd)
{
  // Handles commands sent in from the serial monitor
  if (cmd >= 1 && cmd <= 32)
    { 
      // cmd must be 1 - 32
      SendCode(cmd-1, 1);
    }
    else { printf_P(PSTR("Invalid Choice\n")); }
}

void SendCode ( int cmd, byte numTimes )
{ // cmd = the element of the arrCode[] array that holds the IR code to be sent
  // numTimes = number of times to emmit the command
  // Shift header 16 bits to left, fetch code from PROGMEM & add it
  unsigned long irCode = (codeHeader << 16) + pgm_read_word_near(arrCodes + cmd);
  for( byte i = 0; i < numTimes; i++)
  {
    irsend.sendNEC(irCode,32); // Send/emmit code
    delay(100);
  }
  // Print the string associated with the IR code & the time
  //printf("%S: 0x%lx %02d:%02d:%02d\n", arrMSG[cmd], irCode, hour(), minute(), second());
  printf("%S: %02d:%02d:%02d\n", arrMSG[cmd], hour(), minute(), second());
  if (LCD_COLS == 16)
    {
      lcd.setCursor(0,1);
      fprintf(&lcdout, "%-16S", arrMSG[cmd]);
    }
  else
    {
      lcd.setCursor(0,1);
      // lcd.setCursor(6,0);
      fprintf(&lcdout, "%-14S", arrMSG[cmd]);
    }
}

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

// IR Code functions, called by alarms
void Orange() {SendCode(0,2);}
void Blue() {SendCode(1,2);}
void Rose() {SendCode(2,2);}
void PowerOnOff() {SendCode(3,1);}
void White() {SendCode(4,2);}
void FullSpec() {SendCode(5,2);}
void Purple() {SendCode(6,2);}
void Play() {SendCode(7,1);}
void RedUp() {SendCode(8,1);}
void GreenUp() {SendCode(9,1);}
void BlueUp() {SendCode(10,1);}
void WhiteUp() {SendCode(11,1);}
void RedDown() {SendCode(12,1);}
void GreenDown() {SendCode(13,1);}
void BlueDown() {SendCode(14,1);}
void WhiteDown() {SendCode(15,1);}
void M1Custom() {SendCode(16,2);}
void M2Custom() {SendCode(17,2);}
void M3Custom() {SendCode(18,2);}
void M4Custom() {SendCode(19,2);}
void Moon1() {SendCode(20,2);}
void Moon2() {SendCode(21,2);}
void Moon3() {SendCode(22,2);}
void DawnDusk() {SendCode(23,2);}
void Cloud1() {SendCode(24,2);}
void Cloud2() {SendCode(25,2);}
void Cloud3() {SendCode(26,2);}
void Cloud4() {SendCode(27,2);}
void Storm1() {SendCode(28,2);}
void Storm2() {SendCode(29,2);}
void Storm3() {SendCode(30,2);}
void Storm4() {SendCode(31,2);}
 
#297 ·
That delay (1000) in the loop function is going to cause you issues. It doesnt just delay the temp sensor, it stops the entire code from proceeding for a second. Being in the loop function, those seconds will add up quick. I'm not using a delay on mine and it seems to work ok. If you need the delay, look up the millis() function to create a delay that doesn't stop everything.
 
This is an older thread, you may not receive a response, and could be reviving an old thread. Please consider creating a new thread.
Top