# Another Arduino LED light schedule sketch



## benjaf (Mar 27, 2012)

Here is the Arduino code I use to control the schedule of my LED lights.

Back story:
I've been redoing my 14G low-tech and wanted to try out LED, so I ordered a 10W ebay-special and planned to hook it up to an Arduino I had lying around. While waiting for it to arrive, I looked around for examples of how others had handled the software side of things. None of the code I found seemed to fit my needs.
I realize a lot of people have done the same, but I wanted something that allowed for individual schedules for any number of channels, including siesta and different length sunrise/sunset.

So in the end, I wrote my own code to control the lights.

Some things that need to be addressed before looking at the code:

1: This is not a complete sketch to control aquarium lights!
While it would work on a setup identical to mine, it wouldn't on much else. The code is meant as a helping hand for those that are not overly comfortable with programming, and find handling more complicated schedules difficult. I have removed all but the essentials in this sketch.

2: This has nothing to do with your hardware!
Ok, that may not be strictly true. But my point is: There are a lot of great guides out there regarding the hardware side of things, I will only be dealing with the software. This is the part that I have found relatively little of on these forums, whereas the hardware gurus seem very active. I provide a way to control the PWM signal, the rest is up to you.

Now, let's get started! 

Features:
- Any number of individual channels (limited by you Arduino's number of PWM pins)
- Pretty much any light schedule that repeats every 24 hours
- Suitable for RGB lights
- Reasonably smooth fading w. a resolution of 1/255 updated every second

Hardware requirements:
- Arduino (Uno in my case)
- RTC module (DS1307 is used in the example)
- PWM outputs wired CORRECTLY to your LED drivers

Other requirements:
- Some (not necessarily a lot) proficiency with Arduino coding

There are a lot of comments in the code, and I hope it is not too difficult to understand.

UPDATE:
I have made a considerable rewrite, making it much easier to add more channels.
Concept explained:

A lot of this is explained in the code, but I'll try to summarize it here.
To define a schedule for a channel, you set a series of 'Points'. This should be considered a chart, where the line represents the intensity of light over a 24 hour period.
Intensity is a value in the range 0-255. 0 = OFF, 255 = Fully ON

To make lights turn on in the morning with a 30 minute 'sunrise', you would add 2 Points:
08:00 - Intensity = 0
08:30 - Intensity = 255

Explanation:
From 08:00 to 08:30 channel is going from 0-255

It's pretty simple, really.

 Enough rambling, here is the code. Let me know if you have questions or find it useful! 

Link to source:
[STRIKE]http://codebender.cc/sketch:4232[/STRIKE]
EDIT:
New location of source:
https://github.com/benjaf/LightController


----------



## benjaf (Mar 27, 2012)

[placeholder]


----------



## danielt (Dec 19, 2012)

Thank you kind sir! Do I need the RTC module for this? I have the Arduino Mega and plan on writing myself a light timer for my salt water aquarium. Didn't looked at the code yet.


----------



## benjaf (Mar 27, 2012)

You're welcome! 
Yes, an RTC module will be necessary, the arduino itself is nowhere near accurate enough to to provide useful time keeping for longer periods.


----------



## danielt (Dec 19, 2012)

I thought so. My plan was to use the uptime counter and count the time interval passed to determine how long should I keep the light on. I realize it is a poor way of doing this. I'll try to get a hold on a RTC module and tryout your code.


----------



## benjaf (Mar 27, 2012)

Well it could work for a short period of time if you account for the time drift of your device.
The main issue with this approach is that everything is reset if you lose power. Then there is the rollover of the counter, can't quite remember how often that is.


----------



## Darkblade48 (Jan 4, 2008)

benjaf said:


> Well it could work for a short period of time if you account for the time drift of your device.
> The main issue with this approach is that everything is reset if you lose power. Then there is the *rollover of the counter*, can't quite remember how often that is.


Every 50 days. Also, as you mentioned, the drift with the Arduino counter can be significant (a few minutes per day) especially if the temperature changes.

Thanks for the code


----------



## benjaf (Mar 27, 2012)

You're welcome! Might as well share in case someone else can use it!

@danielt: If you make code to emulate a 24H clock and set start time to be the time of compilation, you just have to rebuild and upload to the arduino to set time once in a while. Could work as long as the power is stable!


----------



## benjaf (Mar 27, 2012)

Just a minor update:
The project has been added to scapegoats's new wiki: opensourceaquatics.com
and the source has moved to Github: Light Controller


----------



## AquaPorn (May 15, 2013)

Hi,

I was hoping you can help me a bit with your code. I came across this thread searching for sunrise/sunset code for my marine build.

I am new to Arduino as well as coding, but I usually can dissect it and figure out what does what.

I used this code with the only deviation being I have 9 CHANNELS and 3 MAXPERIODS.

The code compiles, however the results are not what I expect. I vaguely understand most of the code except for:

int lightMatrix [CHANNELS][MAXPERIODS][5] =

Channels index the array 9
Maxperiods index the array 3

5 = ? what does the 5 index in this statement?

I think that this is where my issue might be.

Thanks for the help, and the code!


----------



## JustAGuy716 (Aug 28, 2012)

The 5 in the statement designates how many values each row has - start hr, start tm, end hr, end time, and pwm intensity. That's 5 things for each period.


----------



## AquaPorn (May 15, 2013)

Shoot....that's what I thought it was for. I guess I have to keep looking for the bug.

when I have the 5 designated only channels 8 & 9 will light when all nine should be.

back to the drawing board.


----------



## AquaPorn (May 15, 2013)

Just a thought that might be my answer...

The function is only finding 2 channels when I have 9. Now, I did not change the code and left it as it was originally written...which was for a 2 channel set-up.

I am now thinking I will need to expand upon the function in order to find all channels and periods.
Does that sound like I may be on the right track??

Thanks for anything.


----------



## JustAGuy716 (Aug 28, 2012)

AquaPorn said:


> Just a thought that might be my answer...
> 
> The function is only finding 2 channels when I have 9. Now, I did not change the code and left it as it was originally written...which was for a 2 channel set-up.
> 
> ...


Did you change the #define lines for CHANNELS & MAXPERIODS?

It also looks like you need to update the int lightvalue[CHANNELS]= to 

int lightvalue[CHANNELS] = { 0, 0, 0, 0, 0 };

The same type of things need to be done whenever the phrase ...and so on shows up in the code. Without that change, the pins themselves won't be initialized (for pinMode statements) or updated (for analogWrite statements)


----------



## AquaPorn (May 15, 2013)

Thanks for taking the time man...

Yes I've had that set up from the beginning.

#define CHANNELS 9
#define MAXPERIODS 3

Also changed 

int lightValue [CHANNELS] = {0,0,0,0,0,0,0,0,0};

So, those are in place as well as my 9 channels in the lightMatrix.
The code is commented well, and easy enough to adjust to my set up.
That's why I'm stumped as to why it's not working. It compiles fine. Just not getting desired results. 

I think I have narrowed it down to the void function. The Intensity value is not being picked up by the lightValue array.
Still hunting... 
I removed the pinMode call in setup...I don't think it is necessary as the code uses analogWrite and the LEDs are on pwm pins, but I can try adding that back.

I can also try changing "#define" to "const"...one drawback with using #define is that it replaces anything with the word CHANNELS with the 9 value. That might be my bug, but I doubt it.
Gut is still telling me I have an issue in that void function somewhere. It's just not finding what period it is.


----------



## benjaf (Mar 27, 2012)

Glad to hear someone is using, or at least attempting to use, this!
If you can send me a copy of your code, I'd be glad to look it over. The original has not been tested with more than 2 channels, so bugs are by no means impossible.

By the way:
I have been doing some re-factoring to the code to make it more user friendly, but have not had a chance to test it yet.


----------



## benjaf (Mar 27, 2012)

UPDATE:
The sketch has been updated, hopefully this should make it much easier to add channels.
Any place in the code where you have to change anything (2 places) is marked with ¤CHANGE¤


----------



## Dookie (May 24, 2013)

benjaf,

i just registered to say THANK YOU.
I have been hacking on my ardunio for a while now and everything was working fine....
Menu and submenus, Light saving on display (switch of 1 minute after last button pressed), RTC, temperature reading.
but i had problems with the light controls.

first i tried to use TimeAlarms to set alarms when to trigger a new end value and then count down or up every 5 seconds... could have worked good, but i also have some manual settings in my menu.
for example all light off or at 25%, 50%, 75%, 100% or automatic (automatic was using the events of TimeAlarm)

it was working, but after going from manual mode to automatic it was missing the events and did not calculate the current value for the light.

you solved my biggest issue with my arduino! roud:

with kind regards
dookie


----------



## Dookie (May 24, 2013)

Oh,

just a quick question:
Do you have any idea why my lamp is on 0 now? time 22:33 with following channel config:
Channels[0].Pin = 13;
Channels[0].AddPoint(6, 0, 1); 
Channels[0].AddPoint(8, 0, 1); 
Channels[0].AddPoint(8, 30, 255); 
Channels[0].AddPoint(11, 30, 255); 
Channels[0].AddPoint(12, 0, 0);
Channels[0].AddPoint(17, 0, 0);
Channels[0].AddPoint(17, 30, 255);
Channels[0].AddPoint(22, 0, 255);
Channels[0].AddPoint(22, 30, 1);
Channels[0].AddPoint(0, 59, 1);
Channels[0].AddPoint(1, 00, 0);

The reason why i sometimes use 1 and 0 as intensity is because when it is 0 i set another digital output to switch off power completely. so with 1 it is supposed to be moon light.

Regards
Dookie


----------



## benjaf (Mar 27, 2012)

First of all, you're welcome! 
The reason your schedule does not work, is that you can't define something after 24:00. If the schedule does not end in 24, it will assume lights are off after last 'valid' entry.
What you have to do is move the last 2 entries to the top like this:
(0, 0, 1) 
(0, 59, 1)
(1, 0, 0)
(5, 59, 0)
and add (24, 0, 1) to the end. That should to the trick!


----------



## Dookie (May 24, 2013)

oh ok, i will try that.

Thanks!


----------



## Simpsoid (Jun 26, 2012)

Hi benjaf, great job here.

Looking to modify my Arduino sketch to contain more light periods. I want to split the photoperiod in two.

Can I assume that your code here does that?



> Channels[0].AddPoint(8, 0, 0);
> Channels[0].AddPoint(8, 30, 255);
> Channels[0].AddPoint(11, 0, 0);
> Channels[0].AddPoint(11, 10, 0);
> ...


From my understanding it goes from 0% (0) to 100% (255) from 8:00 to 8:30 and then it's at 0% again at 11. Does that mean that it fades down from 100% at 8:30 to 0% at 11:00? So the fade period there is 30 minutes up and then 2.5 hours down? Or does the light just switch straight off at 11:00?


Why do you also include another 0% marker at 11:10? Is this just redundancy to make sure it's totally off?

Good work though. Simpler than mine and quite nice.


----------



## benjaf (Mar 27, 2012)

Ah, sorry, that is a typo. It was supposed to be (11, 0, 255)! Hope it makes a bit more sense now


----------



## Simpsoid (Jun 26, 2012)

benjaf said:


> Ah, sorry, that is a typo. It was supposed to be (11, 0, 255)! Hope it makes a bit more sense now


Yep I understand now. The fade up for the "sunrise" is 30 minutes. Then a quick fade down of 10 minutes for a "midday rest". Fade up of 10 minutes "midday wake" then a final 30 minute fade down for a "sunset".

Nicely done.

Now one thing you may have noticed that I can't really seem to crack is that the fade looks a bit bright early on.

It turns out that human eyes perceive light in the logarithmic scale, yet our fades are linear. As such things get to full bright at around 25% of the fade cycle and stay that way until 100%

You may have noticed this. That there's not really a "dawn breaking" effect. It's more dawn for a few minutes then fully bright for the rest. That's because our eyes perceive the full brightness at around 25% and from then on it's still full brightness.

Here are the correct values for a true logarithmic fade from 100% to 0% for a 1 byte (255) range:


```
255, 180, 128, 90, 64, 45, 32, 23, 16, 12, 8, 6, 4, 3, 2, 1,0
```
If you implement these values instead of linearly fading from 100% to 0% (via some sort of lookup array) then you will have a nice fade over time.

I tried to do a calculation that would work out the proper PWM value given a minimum and maximum range (so instead of 100% to 0% if I wanted to fade from 80% to 10%) but I couldn't get one worked out (my maths is poor).

See what you think of the logarithmic fade instead of linear. It's a more gradual effect and I'm sure it's not an issue for the plants. It just means the dimmer values are held a bit longer and so the lights don't look full bright too quickly.


----------



## Simpsoid (Jun 26, 2012)

So I think I have finalised adding the code to process the linear vs logarithmic scale to my project and thought it could help you and others out.

Here are my results:









With the following values (in case anyone wants to use an array lookup).
Percentage on left, PWM on right:

```
1	0
2	0
3	0
4	0
5	0
6	0
7	0
8	1
9	1
10	1
11	1
12	1
13	1
14	1
15	1
16	1
17	2
18	2
19	2
20	2
21	2
22	2
23	3
24	3
25	3
26	3
27	3
28	4
29	4
30	4
31	4
32	5
33	5
34	5
35	6
36	6
37	7
38	7
39	8
40	8
41	9
42	9
43	10
44	10
45	11
46	12
47	12
48	13
49	14
50	15
51	16
52	17
53	18
54	19
55	20
56	21
57	22
58	23
59	25
60	26
61	28
62	30
63	31
64	33
65	35
66	37
67	39
68	42
69	44
70	47
71	49
72	52
73	55
74	58
75	62
76	65
77	69
78	73
79	77
80	82
81	87
82	92
83	97
84	102
85	108
86	115
87	121
88	128
89	136
90	143
91	152
92	160
93	169
94	179
95	190
96	200
97	212
98	224
99	237
100	250
```
I sourced this a while ago from THIS SITE

The formula is as follows. It accepts an integer input (0% - 100%) and will output a byte (0 - 255) which can be written directly to the PWM ports.

```
byte linearPWM(int percentage)
{
	// coefficients
	double a = 9.7758463166360387E-01;
	double b = 5.5498961535023345E-02;
	return (byte)(floor((a * exp(b * percentage) + 0.5)) -1);
}
```


----------



## benjaf (Mar 27, 2012)

I have actually been considering adding this, or at least a slightly simplified version of it for some time.
I have been doing some more rewrites to the project in order to simplify usage even more - and will probably add 'perceived linear' fading as an option for each channel. This code is not yet done, but testing is under way. Thanks for doing some of the numbers!


----------



## AquaPorn (May 15, 2013)

Hi....it's me again. I never got back to thank you for this code, or to let you know I got it up and running without any issues across 9 channels with 3 periods.

I've been running it as my default lighting schedule since the end of May. It's still the older version, but I don't have any issues with it. And I really don't plan to change it.

My question this time is about the timing/intensity matrix.

Is it possible to use a variable within it? Specifically the Intensity?

Current Example: 

{
{0, 0, 9, 45, 0},
{10, 45, 16, 55, 90}, ////Wht_Ch1
{17, 55, 24, 0, 0}
},{

I am trying to pass a variable(without success) from a touch "dimming/blending" screen so I will be able to change it without changing the sketch every time.

Something like this:

{
{0, 0, 9, 45, 0},
{10, 45, 16, 55, White_Max}, ////Wht_Ch1
{17, 55, 24, 0, 0}
},{

I figured it would be as simple as passing the variable value into the matrix like that, but it has not. I've tried various ways and variables with out success.

Will the schedule only accept a number between 0 and 255?

Thanks for any help or ideas!

EDIT: here is a photo to try and make clear what I am talking about.

This is a control screen where I can currently dim and blend each of my LED colors.
I would like to be able to set maximum value to the lighting schedule from here.


----------



## benjaf (Mar 27, 2012)

You're welcome, happy to see someone is still using it 
Nice interface by the way!
Anyway, on to what you are trying to do:

Passing a variable that way just passes the value of the variable at that given time.
To change the schedule after initialization you have to change the value directly in the matrix like this:

lightMatrix[Channel][Period][INTENSITY] = Variable;

Assuming you want to change period 2 in the first channel:

```
lightMatrix[0][1][INTENSITY] = White_Max;
```
Remember that array indexes start at 0, otherwise results will be rather odd...

EDIT: This must be done every time the variable is changed! (wasn't sure this came across in the original post)

The downside of doing what you do now - any changes will be lost at reset unless you have made some considerable changes to the sketch. 
This will be remedied by an upcoming API edition (currently testing) but it will most likely be released as a separate sketch since it has very little in common with the current version..


----------



## AquaPorn (May 15, 2013)

Thanks so much for the quick response, and compliment.

Makes perfect sense to me now. You know...sometimes I get in a rut thinking a specific solution that I can't get by that, and I just need a slap in the head to see things another way.

I'm going to give it a go.

I'm also thinking that I might be able to solve the reset/power loss issue by saving the max lighting value in EEPROM or SD card when the user pushes "enter" on my dimming screen. But, I'll cross that bridge when I get to it.

Another bridge down the road is setting the start time. The total period would be fixed(easier math). Now that I see how I have to designate the variable I hope I can bang that code out too.

Thanks again!!


----------



## benjaf (Mar 27, 2012)

Saving to EEPROM on change and then reading on startup is fairly straightforward. The version I am working on saves the entire schedule in EEPROM and reads next point on demand - it's not that difficult if you keep it simple.


----------



## AquaPorn (May 15, 2013)

Thanks Benjaf!

I was able to code this quite quickly with your help. EEPROM and all.

I am now able to set the Maximum lighting value from the TFT screen, and I won't lose settings if I lose power, or reset the board.

Now...on to being able to set/alter the start time from the screen.


----------



## Christophe (Oct 23, 2013)

Cool project!

I've done my own Arduino controller specifically for a Current-USA Satellite LED+. It is IR remote controlled, and has separate channels for low-intensity RGBs and a single-channel for higher-intensity white LEDs. It has a bunch of other presets that I am not all that interested in, but the controllability is nice. Hacking the IR protocol was easy, a few other people have detailed it in another thread here in this forum. The Arduino (I used an Uno R3) uses a single PWM channel to drive an IR LED, which could be used to control multiple lights with the same program.

I use an RTC to feed a decimal hour time value to sine functions that control light intensity for each channel. The functions are parametrized such that they are 0 at the specified power-on and power-off times. The amplitude given to the functions determines how fast they ramp, and for how long they plateau at the highest value. Split photoperiods can be done also.

If you're interested in trying to adapt it, I can put up the code, let me know!


----------



## benjaf (Mar 27, 2012)

Looks like an interesting solution!
By now I am really just fiddling with the serial communication between Arduino and Python client, the light scheduling is loosely based on my previous version of this sketch. It takes a while to get anywhere since I am learning Python in the process, but that was sort of the point.
The main issue is a lack of time to spend on the project! :icon_conf


----------



## AquaPorn (May 15, 2013)

Hi Benjaf...I'm here to bother you again.

I am now trying to incorporate being able to set a start time from the GUI.










How I am trying to code it is similar to how you showed me to add a variable to the intensity.

For every channel I have called it out like this:

sunMatrix[0][0][Sunset_Hr] = RiseHour;
sunMatrix[0][0][Sunset_Min] = RiseMin;
sunMatrix[0][1][Sunrise_Hr] = RiseHour;
sunMatrix[0][1][Sunrise_Min] = RiseMin;
sunMatrix[0][1][Sunset_Hr] = RiseHour;
sunMatrix[0][1][Sunset_Min] = RiseMin; 
sunMatrix[0][2][Sunrise_Hr] = RiseHour;
sunMatrix[0][2][Sunrise_Min] = RiseMin;

And then in the matrix array I am trying something like this:

},{
{0, 0, (RiseHour), (RiseMin+2), 0},
{(RiseHour), (RiseMin+10), (RiseHour+12), (RiseMin+7), RBlue_max}, ////RBlue_Ch1
{(RiseHour+12), (RiseMin+12), 24, 0, 0}
},{
{0, 0, (RiseHour), (RiseMin+8), 0},
{(RiseHour), (RiseMin+14), (RiseHour+12), (RiseMin+2), Blue_max}, ////Blue_Ch1
{(RiseHour+12), (RiseMin+8), 24, 0, 0} 

To avoid timing conflicts between Hours and to make sure minutes do not increase above 59.

I am unsure if the Update function is able to pick up which period the schedule is in, or if I can't use the math like I am?

I am hoping you have a suggestion for me.

Thanks!!
Steve


----------



## AquaPorn (May 15, 2013)

I am 80% sure I can't do the math within the matrix. I have changed the variable naming to:

sunMatrix[CHANNELS][0][Sunset_Hr] = RiseHour;
sunMatrix[CHANNELS][0][Sunset_Min] = RiseMin;
sunMatrix[CHANNELS][1][Sunrise_Hr] = RiseHour;
sunMatrix[CHANNELS][1][Sunrise_Min] = RiseMin;
sunMatrix[CHANNELS][1][Sunset_Hr] = RiseHour;
sunMatrix[CHANNELS][1][Sunset_Min] = RiseMin; 
sunMatrix[CHANNELS][2][Sunrise_Hr] = RiseHour;
sunMatrix[CHANNELS][2][Sunrise_Min] = RiseMin;

And I am getting the Blue's....whether they ramp up/down I am unsure. But, I am guessing the white's are not coming on because I have them starting 2 hours after the blues which I designated RiseHour+2 in the matrix.

Getting closer though...


----------



## benjaf (Mar 27, 2012)

Actually, to do this you will have to make a few changes.
First of all, you will have to move the initialization of the matrix into a function so you can call it as many times as you want. If you do that, then you can keep it like you described above:

int lightMatrix[CHANNELS][MAXPERIODS][5];

void UpdateMatrix() {
lightMatrix = {
{0, 0, (RiseHour), (RiseMin+2), 0},
{(RiseHour), (RiseMin+10), (RiseHour+12), (RiseMin+7), RBlue_max}, ////RBlue_Ch1
{(RiseHour+12), (RiseMin+12), 24, 0, 0}
},{
...
}

Keep in mind that you have to have the definition outside the function like above!

Any time one of the variables are changed, you reinitialize the entire matrix in the easiest possible way:

UpdateMatrix();

This makes sure everything is always updated, and you have to do as little work as possible!
Hope this helps, always happy to help


----------



## AquaPorn (May 15, 2013)

Thanks for the help!!

I am having an issue though. It's probably due to my lack of experience. A year ago I didn't even know what Arduino was, and never had any experience writing code until 10 months ago.
So, I don't understand the compilation error I am receiving. What it means that is. My lack of vocabulary with all of this.

I made the adjustments you recommended, but it won't compile. It is giving me an error on this line

lightMatrix={

"expected primary expression before {"

I'm unsure what this means...I kind of hunt and peck a few things, but no success.


----------



## benjaf (Mar 27, 2012)

Without having seen your code, my guess would be that you have an unfinished line above the line reporting the error. That is the case in the majority of odd errors!


----------



## AquaPorn (May 15, 2013)

I'm checking that out.

Something odd happening. I woke up in the middle of the night and all the blue LEDs were on?
This morning I turned on the LEDs to find all channels on??
Lol!

Maybe Sunday I'll have time to actually sit and sift through all the code.
At least I know something is happening. Lol!


----------



## AquaPorn (May 15, 2013)

Hi Benjaf,

I've been wrestling around with code, and I don't think I am any closer.

I posted the code on the Arduuino forum seeking help, and those guys are so f'n cryptic with their direction. The best I got was that "you can't initialize an array in that way"
"Your Declaration is correct, but your initialization is not".

Why can't they just show me??


----------



## benjaf (Mar 27, 2012)

Well they are like that sometimes, a lot of the more active people there assume you have quite a bit of experience in both electronics and programming.
*Declaration*: the 'creation' of the array ( int lightMatrix[x][x][x]; )
*Initialization*: creating the contents of the array ( lightMatrix = {{x},{x}}; )

But anyway, I have made a very basic sketch to show you how I meant it:
http://codebender.cc/sketch:23963
The variable names etc. are not accurate, but the idea stands. 
And it works - just not in the Arduino IDE (programming environment). Arduino has quite at few quirks like that, and I keep forgetting them. I usually use Codebender because it is more 'friendly'!


----------



## benjaf (Mar 27, 2012)

If you want to make it 'Arduino IDE Compatible' you will have to replace the contents of the UpdateMatrix() function with one of these for each channel:

// Channel 1:
sunMatrix[0][0][Sunrise_Hr] = 0;
sunMatrix[0][0][Sunrise_Min] = 0;
sunMatrix[0][0][Sunset_Hr] = RiseHour;
sunMatrix[0][0][Sunset_Min] = RiseMin + 2;
sunMatrix[0][0][INTENSITY] = 0;
sunMatrix[0][1][Sunrise_Hr] = RiseHour;
sunMatrix[0][1][Sunrise_Min] = RiseMin + 10;
sunMatrix[0][1][Sunset_Hr] = RiseHour + 12;
sunMatrix[0][1][Sunset_Min] = RiseMin + 7;
sunMatrix[0][1][INTENSITY] = RBlue_max;
sunMatrix[0][2][Sunrise_Hr] = RiseHour + 12;
sunMatrix[0][2][Sunrise_Min] = RiseMin + 12;
sunMatrix[0][2][Sunset_Hr] = 24;
sunMatrix[0][2][Sunset_Min] = 0; 
sunMatrix[0][2][INTENSITY] = 0;

It is a somewhat more tedious process!


----------



## AquaPorn (May 15, 2013)

Awesome! Awesome! Awesome!

Thank you so much for taking the time!!

One of the comments from the Arduino forum (I was trying not to bother you too much) stated I couldn't shortcut it the way I was trying to do, and I would have to initialize all the elements of the array.

But, I still had hope....

It is a tedious process, but I'm too far in to turn back now.

Thank you so much. You have full credit in my code, and shout outs on my You Tube channel and Build thread.


----------



## benjaf (Mar 27, 2012)

You're welcome 
While the original suggestion might be a 'shortcut', that is how it would normally be done. Why the standard Arduino compiler does not allow it is beyond me, the resulting code is a mess!
But hey, whatever works


----------



## AquaPorn (May 15, 2013)

Thanks again Benjaf!
Got it completely integrated with my UI. Thanks for taking the time to help me out.

https://www.youtube.com/watch?v=0Ldx0fuJl8s


----------



## benjaf (Mar 27, 2012)

Just read through your build thread - damn, suddenly I'm really appreciating dealing with a small low-tech planted tank! No PH probes with grounding issues, no massive LED driver arrays to burn. Oh, and no touchscreen!
Learning Python and using Qt for GUI is somewhat more forgiving.
Congratulations on getting this far, I'm confident you will be able to complete it at some point 

// Benjamin


----------



## coonass (Dec 11, 2012)

Thanks for putting this code out there for us! 

I'm having a problem that I hope someone can clarify. I would like to have the lights intensify to some medium value (here 150) between 0800 and 1000, then have a midday burst at full intensity from 1200 to 1400 followed by another period of medium intensity from 1500 to 1930 and finally a fade out from 1930 to 2130. With the following schedule the lights go out (0 intensity) at 14:00 and then come back up to 150 at 1500. 

// Channel 0:
Channels[0].Pin = 11;
Channels[0].AddPoint(8, 0, 0);
Channels[0].AddPoint(10, 0, 150);
Channels[0].AddPoint(12, 0, 255);
Channels[0].AddPoint(14, 0, 255); Lights go out here instead of fading down
Channels[0].AddPoint(15, 0, 150);
Channels[0].AddPoint(19, 30, 150);
Channels[0].AddPoint(21, 30, 0);

How am I coding this wrong?


----------



## benjaf (Mar 27, 2012)

Well that part looks fine, so my only other guess would be that you have MaxPoints set too low? If that's not the problem it's possible there may be a bug in the intensity calculation. 
Does the light come on again after turning off, or does it just stay off?


----------



## coonass (Dec 11, 2012)

The lights come back on. From 1400 to 1500 they fade from 0-150 rather than from 255-150.

MaxPoints = 10


----------



## benjaf (Mar 27, 2012)

Odd.. I'll try to replicate it and find out why that happens!


----------



## benjaf (Mar 27, 2012)

After a quick review I have updated the code. 
The old version had several bugs in the intensity calculation, so I have merged the 'old' public version and parts of my experimental branch. This means a few changes will have to be made to fit this in to your code, but I believe the benefits are worth some minor changes.

Major differences:
* Channel boilerplate code moved to a library to reduce mess
* Channels are initialized slightly differently
* A 'Points' array must be defined along with 'Channels'
* You can now choose between linear and exponential fading
* Intensity can be defined as integer (0 - 255) or float (0 - 1), but is handled as a float internally


----------



## coonass (Dec 11, 2012)

I am going out of town for the rest of the week. I'll give this another try on Friday and report back.


----------



## Pady (Dec 1, 2013)

I've looked at this code and it fits my array the most.
Like the idea of "low tech" arduino controlling without LCD displays and so on.
I just need 4 channels and a way to set the starting times... (can't really make sense off the code) I'm pretty noob to this programming bit...

lateron i'm thinking of adding a thunder storm and so on...
can some of you help with the code of adding 2 more channels?
and mabye help me understand the code.
if I want PWM 180 to be 100% and not PWM 255...


----------



## benjaf (Mar 27, 2012)

So just to be sure I got this right before the examples..
You currently want the following:

4 channels
Simple sunrise / sunset for all channels
Max intensity of ~180

To get 4 channels you have to do the following:
Change this: 

```
const int MaxChannels = 2;
```
to this:

```
const int MaxChannels = 4;
```
If all you need is simple sunrise / sunset, you don't need more than 4 points in each channel. So you can safely set MaxPoints to 4 in stead of 10:

```
const int MaxPoints = 4;
```
Then you need to set up the schedules for each channel.
Change the pin / times / intensities in Channel 0 to fit your schedule, and then replace this:

```
// Channel 1:
        channelNo = 1;        // Currently editing channel 1
        pin = 11;                // Channel 1 uses pin 11
        Channels[channelNo] = Channel(pin, MaxPoints, fademode_linear, Points[channelNo]);
        Channels[channelNo].AddPoint(8, 0, 0);
        Channels[channelNo].AddPoint(8, 30, 150);
        Channels[channelNo].AddPoint(11, 0, 255);
        Channels[channelNo].AddPoint(11, 15, 0);
        Channels[channelNo].AddPoint(12, 30, 0);
        Channels[channelNo].AddPoint(12, 45, 255);
        Channels[channelNo].AddPoint(19, 30, 150);
        Channels[channelNo].AddPoint(20, 0, 0);
```
with the following (set the correct pins / times / intensities to match your setup):

```
// Channel 1:
        channelNo = 1;        // Currently editing channel 1
        pin = 11;                // Channel 1 uses pin 11
        Channels[channelNo] = Channel(pin, MaxPoints, fademode_linear, Points[channelNo]);
        Channels[channelNo].AddPoint(8, 0, 0); // 08:00 - Intensity = 0
        Channels[channelNo].AddPoint(8, 30, 180); // 08:30 - Intensity = 180 
        Channels[channelNo].AddPoint(19, 30, 180); // 19:30 - Intensity = 180
        Channels[channelNo].AddPoint(20, 0, 0); // 20:00 - Intensity = 0
		
	// Channel 2:
        channelNo = 2;        // Currently editing channel 2
	pin = 9;                // Channel 2 uses pin 9
        Channels[channelNo] = Channel(pin, MaxPoints, fademode_linear, Points[channelNo]);
        Channels[channelNo].AddPoint(8, 0, 0);
        Channels[channelNo].AddPoint(8, 30, 180);
        Channels[channelNo].AddPoint(19, 30, 180);
        Channels[channelNo].AddPoint(20, 0, 0);
		
		// Channel 3:
        channelNo = 3;        // Currently editing channel 3
        pin = 6;                // Channel 3 uses pin 6
        Channels[channelNo] = Channel(pin, MaxPoints, fademode_linear, Points[channelNo]);
        Channels[channelNo].AddPoint(8, 0, 0);
        Channels[channelNo].AddPoint(8, 30, 180);
        Channels[channelNo].AddPoint(19, 30, 180);
        Channels[channelNo].AddPoint(20, 0, 0);
```
What you just did by altering and inserting the above, is to configure channels 0 - 3. No more code needs to be changed, now you have a complete 4-channel setup.

The rest of the code (in the main sketch at least) is quite straightforward. Every loop checks if a second has passed since last time the lights were updated, and then calculates the current intensity levels for each channel.


----------



## HunterX (May 19, 2012)

Ben,

Love the light code mate . I doing some comparison with some other light codes to see about working in the storm stuff.

Do you know if scapegoat's wiki is still up? I clicked on the link and page was not available. Thanks again. Great work!


----------



## benjaf (Mar 27, 2012)

HunterX said:


> Ben,
> 
> Love the light code mate . I doing some comparison with some other light codes to see about working in the storm stuff.
> 
> Do you know if scapegoat's wiki is still up? I clicked on the link and page was not available. Thanks again. Great work!


Thanks!
You will probably need to make a few changes to enable an 'effect' overlay, but it should be possible.

I think he made some breaking changes to the wiki. Last time I saw it there was some stuff about CO2 and then my code. I think he had trouble getting people to submit content.


----------



## AquaPorn (May 15, 2013)

Hi Benjaf...yep it's me again.

Just having an issue I can't seem to sort, and was hoping you could nudge me in the right direction.

Long story short I am having difficulties with the ramping up of the LEDs. This started happening when I had to undo your nice tidy array in order to integrate my UI.
What is happening is the increasing intensity of the LEDs has become jerky. It ramps up in steps rather than the nice smooth increase from 0 to the max LED vlaue.
Just a rough example...the LEDs come on at about 20%, 40% a few seconds later, and so on. Noticeable increase to the eye.

I've been trying to figure out why this is happening, and correct the issue for some time now, and finally had to bother you yet again.

Funny thing is nothing else is effected. Timing schedules for all the periods is correct with no issues, and the fading from max led value to 0 is still silky smooth.

Do you have any idea what might be causing this?
I could send my code if you need.

Thanks again!


----------



## benjaf (Mar 27, 2012)

Hello again Steve!
That does sound very strange, if you send me your code I will have a look at it as soon as I can. :smile:


----------



## AquaPorn (May 15, 2013)

Thanks for taking a look!

Hopefully you can make heads or tails of it....I try to be neat, but it doesnt always work out.


----------



## benjaf (Mar 27, 2012)

That's a lot of code! :red_mouth
I'll take a closer look when I have a bit of time.


----------



## AquaPorn (May 15, 2013)

I know..sorry.

The light schedule is grouped pretty close near the top of the sketch.

I do a function call UpdatateMatrix();
In the LED navigation code at the 'enter' buttons, and in the PrintMoonPhase();

I hope that narrows it down so you don't have to go through every line of code.

No hurry. Please look at you leisure. The scheduler is working. I'm just annoyed because I know how it used to work before I messed with it.

I appreciate the time.


----------



## benjaf (Mar 27, 2012)

Ah well, most of the code is display / touch related - so reading it wasn't that bad.
I can't see anything obviously wrong other than some of the old bugs in the light calculation, and as far as I can tell they should have no effect in your case. That issue only appears if you ramp up lights starting from a non-zero value. 

My only real suggestion would be to try and figure out why there seems by be stepping, mainly by printing an awful lot of stuff to the serial port and introduce a small delay in the main loop (½ second or something) to prevent too much console spam:

How often is UpdateSun() method called when the stepping occurs?
If it is called as often as it should (several times each second), which calculation is it that fails?

I'm sorry I can't be of more help in this case!


----------



## AquaPorn (May 15, 2013)

Thanks for taking the time to look!

Too bad there is not an obvious answer. However, you did give me an idea as to a possible solution.
I did not know about the bug in the original code about starting from a zero value.
I tried to get clever by integrating Lunar Illumination into the lighting period of the string of LEDs I'm using. (Moonmax > UVmax > Moonmax) instead of (0 > UVmax > 0)

This string happens to be the starting point of the whole schedule.

Maybe this is the answer??

Thanks for the nudge!


----------



## benjaf (Mar 27, 2012)

I don't think it can affect any other light strings, but it is a problem nonetheless!
I suggest you replace this:

```
int preVal = sunMatrix[channels][periods][SunLevel];     // Old light value
int postVal = sunMatrix[channels][periods + 1][SunLevel];	// New light value
int tDur = pNextStart - pEnd; 			// Transition duration
int intensityDiff = postVal - preVal;	// Difference in light intesity between periods
if(intensityDiff > 0) {		// Intensity increasing
	sunValue[channels] = (int)((seconds - pEnd) * ((float)intensityDiff / tDur));		// Light value
} else {					// Intensity decreasing
	sunValue[channels] = preVal - (int)((seconds - pEnd) * ((float)(0-intensityDiff) / tDur));	// Light value
}
```
with this:

```
int preVal = sunMatrix[channels][periods][SunLevel];     // Old light value
int postVal = sunMatrix[channels][periods + 1][SunLevel];	// New light value
int timeDiff = pNextStart - pEnd;		// Transition time
int progress = seconds - pEnd; 			// Transition progress
int intensityDiff = postVal - preVal;	// Difference in light intesity between periods
sunValue[channels] = (int)(preVal + (progress * ((float)intensityDiff / timeDiff))); // Current intensity
```
The old calculation was needlessly complex and dodgy at best! Please excuse me if there are errors in the suggested new code, I have not had a chance to compile it.
Some of the variables are not really needed (timeDiff, progress, intensityDiff), they are just there to make the final calculation easier to read..

Hope you find the issue, seems strange that it is only at sunrise!

EDIT: Oh and by the way, that is how I would have done moonlight as well :smile:


----------



## HunterX (May 19, 2012)

Hey Benjaf,

I'm going to use your code to manage my lights in my project if you don't mind . I understand the ChannelManger.h is a library file. But how do I get that file from gethub to my library folder? I'm missing something simple here ain't I?


----------



## benjaf (Mar 27, 2012)

Hey!
Libraries are really not something people seem to talk about a lot. All you have to do is to copy both the .h and the .cpp file to your sketch folder and you should be good to go


----------



## benjaf (Mar 27, 2012)

I should probably clarify. For a custom library like this, I would normally keep it as a part of the sketch where it is used. By keeping the library files in the same folder as your shetch .ino file the Arduino editor will open the library files in seperate tabs. This makes it much easier to make any changes necessary to the library, and much easier to figure out what is actually happening. 

The easiest way to get single files from github is to download the entire package and copy the files you need from there.


----------



## HunterX (May 19, 2012)

benjaf said:


> I should probably clarify. For a custom library like this, I would normally keep it as a part of the sketch where it is used. By keeping the library files in the same folder as your shetch .ino file the Arduino editor will open the library files in seperate tabs. This makes it much easier to make any changes necessary to the library, and much easier to figure out what is actually happening.
> 
> The easiest way to get single files from github is to download the entire package and copy the files you need from there.


I cannot figure out how to download the files. I have only been able to copy and paste the code but that doesn't give me a "file" for the library?

It's my understanding to with the newest version of Arduion's IDE, you have to have any libraries in the library folder in order for the IDE to use them. Which isn't an issue if I can figure out how to download the file from gethub.


----------



## HunterX (May 19, 2012)

HunterX said:


> I cannot figure out how to download the files. I have only been able to copy and paste the code but that doesn't give me a "file" for the library?
> 
> It's my understanding to with the newest version of Arduion's IDE, you have to have any libraries in the library folder in order for the IDE to use them. Which isn't an issue if I can figure out how to download the file from gethub.


Nevermind! I found it. How I missed the big DOWNLOAD ZIP button I do not know.

And for the record. The project will compile with the .h and .cpp files in either the "libraries" folder or the folder the sketch is contained it.


----------



## benjaf (Mar 27, 2012)

On the right side of the linked page there is a "download zip" button 
EDIT: It's surprising how easy it is to miss that one, I often have trouble too for some reason!


----------



## benjaf (Mar 27, 2012)

HunterX said:


> And for the record. The project will compile with the .h and .cpp files in either the "libraries" folder or the folder the sketch is contained it.


Yes, the IDE does not care which location you choose. If you do not intend to make changes to the library you might as well use the "libraries" folder and make it available to all sketches!


----------



## AquaPorn (May 15, 2013)

Hi Benjaf,

Thanks for the code update, and the nudge in the right direction! 

The new code compiled without issue.

Here's how you helped:
"How often is UpdateSun() method called when the stepping occurs?"
That's what got me thinking....I know eveyone could smell the smoke. 

Even though it was not the UpdateSun() function call, but the UpdateMatrix() call. Obviously I was wrong in my thinking of how the function/calculation worked. I thought I could UpdateMatrix() before the loop, and it would hold those values for the UpdateSun() function which was in the main loop.

However, both have to be in the main loop to get my silky smooth ramping back...


```
UpdateSun(GetSeconds(Hours, Minutes, Seconds));
  UpdateMatrix();
```
I don't know why I had it in my head I needed to keep it out of the main loop?

But, back in business!!!!

Thank you sir!!!


----------



## benjaf (Mar 27, 2012)

AquaPorn said:


> I don't know why I had it in my head I needed to keep it out of the main loop?


Well I see no reason why it should be called if nothing has changed. Honestly, I think something else may be the root of your problem - if nothing has changed in the light schedule there should be no need to update the array?
But hey, as long as it works..
Glad I could help in a way! :icon_smil


----------



## HunterX (May 19, 2012)

Hey Ben,

I'm using a different RTC library then the one in your sketch. I think I can handle making it work by changing your time reference to use my library instead of the one you have. In case I struggle would you be willing to take a look at it and help me out?


----------



## benjaf (Mar 27, 2012)

Sure, I'll take a look if you are having trouble. Pretty much everything is handled as seconds since midnight so converting to different rtc should be no big deal.


----------



## Rvacinek (Jan 19, 2014)

This is a seriously great thread to read through. keep the information flowing.


----------



## AquaPorn (May 15, 2013)

Alas Benjaf...you were right.

I'm going to have to hook it up to the serial port and try to trouble shoot it.

I changed it back to the old calculation. The new calculation with "progress" would start the lighting, but would not fade at the scheduled time.

I also found it steps on the fade as well. It just isn't as noticeable because it does fade smoothly to a point, but once it reaches that point it steps 3 or 4 times down to zero.
I can only assume this is what's happening on the ramping. It probably steps 4 times to a certain value, and then smoothed out the rest of the way.

My new thought is perhaps the "float" math...it may be taking longer to calculate due to breaking it out of the array???

Still not sure. Bottom line the code is not happy....

The search continues. I will let you know if I discover the issue.


----------



## benjaf (Mar 27, 2012)

AquaPorn said:


> I changed it back to the old calculation. The new calculation with "progress" would start the lighting, but would not fade at the scheduled time.


I guess that is what I get for suggesting untested code! Oh well.. As long as the old version works for you!


AquaPorn said:


> My new thought is perhaps the "float" math...it may be taking longer to calculate due to breaking it out of the array???


I highly doubt it. Float calculations really aren't THAT expensive, but that should be fairly easy to spot if you start debugging to find out where the processing time is spent.
I am slightly concerned that the whole schedule reading is simply too wasteful when you have that many channels. The new version should be somewhat better, but I certainly understand that you don't really want to replace it :smile:


----------



## AquaPorn (May 15, 2013)

Thanks Ben,

I think you might be right about the code...actually I have no doubt you wouldn't steer me wrong.

Comfort...is pretty much the reason I haven't looked into changing to your new code.
I never had an issue with your original code. I understand it well, and then I put in all that effort to integrate the touch screen. I'm stubborn and was going to make work come hell or high water!

Honestly, I haven't even seen the new version with he exception of the snippets that have been posted.

I'll check it out...so be prepared!

Thanks for all the help mate!


----------



## AquaPorn (May 15, 2013)

Lol!! Hi buddy.

Ok. So, I'm looking over the new sketch. I'm sure I will get used to it, but it is still kind of greek to me.

Before I start trying to integrate this new sketch, and since you already know what I'm trying to accomplish, do you foresee any issues I may have?

Like using variables instead of whole numbers...stuff like that.


----------



## benjaf (Mar 27, 2012)

Well the good thing is you don't really need to bother with most of the stuff in the .h and .cpp files. I think the most significant change is that you have to think of the light intensity as a chart with lines connecting a bunch of points.
Regarding the whole variable thing. Since you will be calling InitializeChannels several times you need to use SetPoint() rather than AddPoint(). The only real difference is that you need to include the point number:

```
Channels[channelNo].SetPoint(#, 8, 0, 0);
```
Where # is the point number starting from 0.


----------



## benjaf (Mar 27, 2012)

I committed a small fix, you could not add points with their float intensity (0-1) as everything was treated like an int (0-255).


----------



## AquaPorn (May 15, 2013)

Thanks Benjaf!

Ok. I had no issues getting the new sketch up and running as written with 7 channels on 4 periods(points).
No Steps!!! Just even smooth linear increasing!

Now the tricky part...the integration.

Just a couple of questions just to be clear.

SetPoint()?
So, something like this


```
Channels[channelNo].SetPoint(1, 8, 0, 0);
Channels[channelNo].SetPoint(2, 8, 30, 255);
Channels[channelNo].SetPoint(3, 20, 0, 255);
Channels[channelNo].SetPoint(4, 20, 30, 0);
```
And can I use the same set points for each channel? Or, do they have to be unique?

" Since you will be calling InitializeChannels several times"
Anything I need to change to call this function?

EDIT:
Ok. I see where it is initialized in setup().
I think I can just call that at my enter buttons on the LCD as well, so it is only called when a change is made?

Thanks!


----------



## benjaf (Mar 27, 2012)

AquaPorn said:


> So, something like this
> 
> 
> ```
> ...


Yes pretty much, except you start with 0 in stead of 1. The numbers are actually just the index in the Point array contained by each channel.



AquaPorn said:


> EDIT:
> Ok. I see where it is initialized in setup().
> I think I can just call that at my enter buttons on the LCD as well, so it is only called when a change is made?


Exactly. You use this just like the old one. Use your variables to set the correct times for each point and call it again whenever one of them changes.

And another thing. The example uses the same fade mode as the old one, where PWM is increased in a linear fashion (fademode_linear). But due to the way we perceive light this does not look linear at all. The alternative mode (fademode_exponential) attempts to compensate for this. The fademode is chosen for each channel during initialization.


----------



## AquaPorn (May 15, 2013)

I must be doing something wrong? It won't initialize with .SetPoint().

Using the example sketch I added 8 channels each channel has 4 periods.
It works fine with AddPoint().

I changed all periods to .SetPoint(), indexed each period 0 to 4, and then added variables risehour, risemin, color_max. With the exception of the first and last period where the intensity equals 0.
I then declared each variable with a value. 

int RBlue_max =255
int rise hour = 8
ect....

And it failed to initialize. I then changed all the set points back to whole numbers to test, and it again failed to initialize using .SetPoint()

I'm missing something?? Any suggestions?


----------



## benjaf (Mar 27, 2012)

AquaPorn said:


> I must be doing something wrong? It won't initialize with .SetPoint().
> 
> Using the example sketch I added 8 channels each channel has 4 periods.
> It works fine with AddPoint().
> ...


It may very well be my code that doesn't actually support this.. It was not really something I had thought of originally. :icon_wink

Oh well, new plan:
You initialize it just like the example with AddPoint() and set some generic times and intensities, let's call it InitializeLights(). The actual values don't matter, they will be overwritten anyway.
You then make a second function to set the actual values using SetPoint(), let's call it UpdateLights(). Since the channels are already set up elsewhere you only have to set the points this time, the channel creation with pins and such is not necessary. Example:

```
int channelNo = 0;
Channels[channelNo].SetPoint(0, 8, 0, 0);
Channels[channelNo].SetPoint(1, 8, 30, 255);
Channels[channelNo].SetPoint(2, 20, 0, 255);
Channels[channelNo].SetPoint(3, 20, 30, 0);

channelNo = 1;
// ... etc ...
```
During setup you then first call InitializeLights() to set up the basic structure and then UpdateLights() to set up the actual light values.Any time you need to rewrite the values you just call UpdateLights() again.


----------



## sketch804 (Mar 2, 2011)

Gonna follow this one!


----------



## AquaPorn (May 15, 2013)

Hi Benjaf,

Ok, so I'm seriously confused now. I've been fiddling around, and I just can't get it to stop the jerky step up. 
I don't understand why it's fine once, and then reverts back to stepping?
I've tried several different things: only using two channel, and I even went back to the old original sketch that I knew worked...I have video to prove it.
Nothing.

Now I'm down to blaming hardware as it is the only thing different since originally starting.
I went from a Sainsmart Mega to an actual Arduino Mega, and from LEDsee drivers to Meanwell LDDs.

I'm starting to question the PWM frequency of the Arduino board.
Do you think adjusting these would help?


----------



## benjaf (Mar 27, 2012)

AquaPorn said:


> Hi Benjaf,
> 
> Ok, so I'm seriously confused now. I've been fiddling around, and I just can't get it to stop the jerky step up.
> I don't understand why it's fine once, and then reverts back to stepping?
> ...


At this point I think it is likely that your hardware may be playing you. My experience in the hardware field is somewhat limited so unfortunately I don't think I am the best person to help you here.. :icon_sad:


----------



## AquaPorn (May 15, 2013)

I'll let you know what I find.

I'm at the Arduino forum as I don't know wether to increase or decrease the frequency of the pins.
Changing is pretty straight forward.

Truthfully, I've had nothing but issues ever since I changed out the Sainsmart clone for an actual Arduino.

Who would have thought?


----------



## Luksi (Sep 27, 2015)

*Different RTC library*

Hi Benjaf,

Thanks for sharing such a great code with us. I ve tried it and it works great on my test build, wich has DS1307 rtc attached to it. But for my tank I use DS3232 rtc, becose its more accurate and doesent miss minutes after longer periods of time. It needs different RTC library than the one used in your code. I changed your code a litle, but it doesent work, becose i dont undestand what rtc.now() funciton actualy outputs, at least I think thats wrong. I attached my code and library to this post and ask you if you could take a look what I ve done wrong.

Thanks,
Luka


----------



## benjaf (Mar 27, 2012)

Hi Luka
The rtc.now() function used in that version of the DS1307 library returns the time as a complex type called DateTime. This means minutes, seconds etc can be accessed directly as seen in the UpdateLights method.
The DS3232 library's get() method returns time in a type that is called time_t, and must be treated slightly differently. See http://playground.arduino.cc/Code/Time for some good examples. You will need a slight rewrite of the UpdateLights method to match this way of accessing seconds/minutes/hours, but not much should need changing otherwise! 
Good luck 

Benjamin 

Sent from my D6603 using Tapatalk


----------



## boxhead1990 (Aug 29, 2011)

just wondering if there was a possability that you could draw a pinout/wiring diagram of how you set yours up and list any other parts you didnt list in your original write up ?

just looking to make sure im doing everything corectly when putting mine together


----------



## benjaf (Mar 27, 2012)

boxhead1990 said:


> just wondering if there was a possability that you could draw a pinout/wiring diagram of how you set yours up and list any other parts you didnt list in your original write up ?
> 
> just looking to make sure im doing everything corectly when putting mine together


Hi!
Actually, I 'deconstucted' the controller this code ran on more than a year ago. It was a huge mess, and frankly not a design you'd want to follow...
Anyway, the basic idea was as follows:



Get a couple of cheap 10W LEDs incl. waterproof drivers from China
Use MOSFETs (IRL540 IIRC) between LED and negative terminal on driver to switch lights on or off
Connect Arduino PWM pin to MOSFET Gate pin via a 220 Ohm resistor to limit the max power draw
Connect high value (~10K) pull-down resistor between MOSFET gate and ground
Connect RTC module to Arduino
Hope your driver doesn't react too poorly to being switched at very high frequencies


This is the quick and dirty way to dim lights. Not all drivers will react nicely to this. I had one which would knock the Arduino into an 'unresponsive' state several times a day, presumably due to poor shielding. Took me forever to realize it was not a bug in the code!


----------



## boxhead1990 (Aug 29, 2011)

I have a light built already and got the code working on my 2560 mega about 2 hours ago but I did notice that when you try to set more then 2 channels it wouldnt add them in even tho I added the code and assigned a pin for that channel maybe I missed something well anyways I noticed the ramp up was sudden and not really a fade in or out 

I havent changed the code at all apart from adding the extra channel 

Sent from my D6653 using Tapatalk


----------



## boxhead1990 (Aug 29, 2011)

benjaf said:


> Hi!
> Actually, I 'deconstucted' the controller this code ran on more than a year ago. It was a huge mess, and frankly not a design you'd want to follow...
> Anyway, the basic idea was as follows:
> 
> ...


I got it all working properly now had another play with stuff in the code and noticed something I overlooked now its running 3 channels like it should be 

And I uave the pwm values reverse as my driver turns the diodes off with an input of 5v and full power on 0v 

So value 255 for me is all the way off and 0 being full power 

Cheers for keeping this available il definatly be adding to it when I get my touch screen and dosing pumps worked out 

Sent from my D6653 using Tapatalk


----------



## benjaf (Mar 27, 2012)

boxhead1990 said:


> I got it all working properly now had another play with stuff in the code and noticed something I overlooked now its running 3 channels like it should be
> 
> And I uave the pwm values reverse as my driver turns the diodes off with an input of 5v and full power on 0v
> 
> ...


Good to hear you got it working!
If you want to have the values back to 'normal' (0 = off) you can change the following line in UpdateLights:

```
analogWrite(Channels[channel].GetPin(), Channels[channel].GetLightIntensityInt(now));	// Get updated light intensity and write value to pin (update is performed when reading value)
```
to this:

```
analogWrite(Channels[channel].GetPin(), (255 - Channels[channel].GetLightIntensityInt(now)));	// Get updated light intensity and write value to pin (update is performed when reading value)
```


----------



## boxhead1990 (Aug 29, 2011)

benjaf said:


> Good to hear you got it working!
> If you want to have the values back to 'normal' (0 = off) you can change the following line in UpdateLights:
> 
> ```
> ...


Im not gunna worry about it now as that means I have to set all my values again and its all working sweet so I think il just leave it alone

Sent from my D6653 using Tapatalk


----------



## skoram (Aug 9, 2012)

I just noticed this thread now. Benjaf, thanks for sharing your fantastic code with us! This is just what I have been looking for :grin2:


----------



## benjaf (Mar 27, 2012)

skoram said:


> I just noticed this thread now. Benjaf, thanks for sharing your fantastic code with us! This is just what I have been looking for :grin2:


Happy to help!


----------



## remix7196 (Jun 10, 2017)

Hey, so sorry to dig up a rather old thread, but this stuff is totally still relevant!

There's a bunch of other sketches that just don't provide the simplicity and ease of this sketch, so I wanted to use it.

Unfortunately, it won't compile, with the following errors below. No idea why, I didn't change anything, and I tested with another Arduino-branded example sketch to make sure my connections were fine (which they are)... Help, anyone??


----------



## benjaf (Mar 27, 2012)

remix7196 said:


> Hey, so sorry to dig up a rather old thread, but this stuff is totally still relevant!
> 
> There's a bunch of other sketches that just don't provide the simplicity and ease of this sketch, so I wanted to use it.
> 
> Unfortunately, it won't compile, with the following errors below. No idea why, I didn't change anything, and I tested with another Arduino-branded example sketch to make sure my connections were fine (which they are)... Help, anyone??


I'm guessing you are using a recent (1.8) version of the Arduino editor?
You might have to try the 1.0.X versions, preferably 1.0.5. The newer ones (1.5 and newer) are using a bugged version of the gcc compiler, causing all sorts of problems.
I can't get it working on the newer versions either!


----------



## alphabeta (Jul 14, 2014)

interesting project! one thing i can not find is the schematics for the whole setup. Can anyone help with that? thanks.


----------



## sir.tie (Aug 28, 2016)

Bookmarking for future project

Sent from my Nexus 6P using Tapatalk


----------



## benjaf (Mar 27, 2012)

alphabeta said:


> interesting project! one thing i can not find is the schematics for the whole setup. Can anyone help with that? thanks.


Well - I don't have a complete schematic, as it depends to heavily on your choice of components.
What I did with this particular setup was the 'brute force' way of dimming. I connected the Arduino to a MOSFET, and used it to switch the neutral wire of the light (12 volt). This doesn't always work, it depends greatly on the power supply!

Sent from my Nexus 5X using Tapatalk


----------



## jenovauh (Oct 7, 2017)

benjaf said:


> Well - I don't have a complete schematic, as it depends to heavily on your choice of components.
> What I did with this particular setup was the 'brute force' way of dimming. I connected the Arduino to a MOSFET, and used it to switch the neutral wire of the light (12 volt). This doesn't always work, it depends greatly on the power supply!
> 
> Sent from my Nexus 5X using Tapatalk


Hi Benjaf, I read through the whole thread and realized there isn't any electrical schematic as well. I am pretty new to the microcontroller world but would like to build this project for my house aquarium as well. Wonder if you would draw out a simple drawing on all the wiring connection so that I can try to follow. Thanks a lot and sorry for the trouble.


----------



## Downwardflight (Oct 22, 2017)

Awesome code, I've been looking for something like this. How would I incorporate a push button settable sunrise time with an lcd to display current time, on set time, and intensity %?
I started with this code which is a simple on off and clock display...

```
//NJarpa
// Arduino timer on/off setpoint.
//If you find this code,visit INSTRUCTABLES and type the title above.
//Arduino UNO + RTC 1307 + 16X2 LCD + 4 buttons


#include <LiquidCrystal.h>   //Libraries
#include <RTClib.h>
#include <Wire.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7); //Arduino pins to lcd
//LCD 1602
//RS to 2pin, EN to 3pin, D4 to 4pin, D5 to 5pin, D6 to 6pin, D7 to 7pin
//Contrast=  10K potentiometer: 1 to Vcc, 2(center)to VO, 3 to GND
//Backlight= K to GND(with 1K resistor), A to Vcc
//Vss to GND , Vdd to +5v

RTC_DS1307 RTC;                     // define the Real Time Clock object
//RTC 1307
//SDA to analog 4,  SCL to analog 5

//You need 4 buttons like this,to enter the set points, with 10K resistor to ground (for each one)

//                  To arduino pins(8, 9, 10, 11)
//                        |
//                        |
//              switch    |   
//              _______   |         10K          
//    +5V  0-----0   0----0------/\/\/\/\-------0  to GND

const int DOWN_BUTTONon =9;        //Buttons Pins
const int UP_BUTTONon =8;
const int DOWN_BUTTONoff =11;       
const int UP_BUTTONoff =10;
const int Relay =12;             //Relay pin

//-----Variables for debouncing------//

boolean lastDownONButton = LOW;
boolean currentDownONButton = LOW;
boolean lastUpONButton = LOW;
boolean currentUpONButton = LOW;
boolean lastDownOFFButton = LOW;
boolean currentDownOFFButton = LOW;
boolean lastUpOFFButton = LOW;
boolean currentUpOFFButton = LOW;


int set_on = 12;        //The default "ON" desired time
int set_off= 12;        //The default "OFF" desired time


void setup(){
  pinMode(Relay, OUTPUT);
  
     Wire.begin();
     RTC.begin();
     lcd.begin(16, 2); // Configura lcd numero columnas y filas
     
     lcd.setCursor(0,0);  //Show "TIME" on the LCD
     lcd.print("TIME");
     lcd.setCursor(0,1);
     lcd.print("ON");    //Show "ON" on the lcd
     lcd.setCursor(6,1);
     lcd.print("OFF");  //Show "OFF" on the lcd
}
     
   //----Debouncing function for all buttons----//
boolean debounce(boolean last, int pin)
{
boolean current = digitalRead(pin);
if (last != current)
{
delay(5);
current = digitalRead(pin);
}
return current;
}
     
 

void loop(){
//--------Show current Time On LCD--------//


DateTime now = RTC.now();        // Clock call
now = RTC.now();

lcd.setCursor(5,0);                 
if(now.hour() < 10)
{
lcd.print("0");
}
lcd.print(now.hour(), DEC); //Print hour
lcd.print(':');
if(now.minute() < 10)
{
lcd.print("0");
}
lcd.print(now.minute(), DEC); //Print min
lcd.print(':');
if(now.second() < 10)
{
lcd.print("0");
}
lcd.print(now.second(), DEC); //Print sec

//----Debounce  buttons---//
currentDownONButton = debounce(lastDownONButton, DOWN_BUTTONon);
currentUpONButton = debounce(lastUpONButton, UP_BUTTONon);
currentDownOFFButton = debounce(lastDownOFFButton, DOWN_BUTTONoff);
currentUpOFFButton = debounce(lastUpOFFButton, UP_BUTTONoff);

//-----Turn down the set "ON"-----//
if (lastDownONButton== LOW && currentDownONButton == HIGH)
{
if(set_on>0){    //"ON" Set point  down never lower than 0
set_on--;        
}
else{
  lcd.setCursor(3,1);
  lcd.print("0");
}
}

//----Turn up the set "ON"----//
else if (lastUpONButton== LOW && currentUpONButton == HIGH)
{
  if(set_on<23){   //"ON" Set point up never higher than 23
  set_on++;
}
else{
  lcd.setCursor(3,1);
  lcd.print("23");
}
}

//---Print the set "ON"---//
lcd.setCursor(3,1);
if(set_on < 10){   
  lcd.print("0");
}
lcd.print(set_on);
lastDownONButton = currentDownONButton;
lastUpONButton = currentUpONButton;

//-----Turn down the set "OFF"-----//
if (lastDownOFFButton== LOW && currentDownOFFButton == HIGH)
{
if(set_off>0){    //"OFF" Set point  down never lower than 0
set_off--;
}
else{
  lcd.setCursor(10,1);
  lcd.print("0");
}
}

//----Turn up the set "OFF"----//
else if (lastUpOFFButton== LOW && currentUpOFFButton == HIGH)
{
  if(set_off<23){   //"OFF" Set point up never higher than 23
  set_off++;
}
else{
  lcd.setCursor(10,1);
  lcd.print("23");
}
}

//---Print the set "OFF"---//
lcd.setCursor(10,1);
if(set_off < 10){   
  lcd.print("0");
}
lcd.print(set_off);
lastDownOFFButton = currentDownOFFButton;
lastUpOFFButton = currentUpOFFButton;



//----Relay Function----//
if(set_on == set_off){
   digitalWrite(Relay, LOW);
}

if(set_on < set_off){
  
             if(now.hour() >= set_on && now.hour() < set_off ){             //Start
             digitalWrite(Relay, HIGH);
             }
             else if(now.hour() >= set_off) {
             digitalWrite(Relay, LOW);
             }
             else{
             digitalWrite(Relay, LOW);
             }
}
if (set_on > set_off){

            if(now.hour() >= set_on && now.hour() <= 23){                  //Start
            digitalWrite(Relay, HIGH);  
            }
            else if(now.hour() < set_off){
            digitalWrite(Relay, HIGH);
            }
            else if(now.hour() >= set_off && now.hour() < set_on){
            digitalWrite(Relay, LOW);  
            }
}

}// The End
```
 as I was just turning on and off relays before So id like to get rid off the set off buttons and make it a fixed 16 hours from set on time and replace the relay stuff with your addpoint array as well as adding randomizing into the array to simulate cloud cover? 
so the schedule would look something like this:

```
void InitializeChannels(int channels) {
	
	// Channel 1:
int	channelNo = 1;	// Currently editing channel 1
int	pin = 9;		// Channel 1 uses pin 9
	Channels[channelNo] = Channel(pin, MaxPoints, fademode_linear, Points[channelNo]);
/*--------Channel-----------(--------------Hours----------------,------Min----,----Intensity---);---Default time------Description--------------------*/
	Channels[1].SetPoint(              set_on               ,       0     ,        0       );  //    6 am   dawn starts; start of day
	Channels[1].AddPoint(            set_on + 1             ,       0     , random( 85-169));  //    7 am   fade up to 30-60% light within 1hr; end dawn
        Channels[1].AddPoint( random(set_on +  1 - set_on +  4) , random(5-55),       204      );  //  7-11am   randomly fade to 80% light; mid-morning burst
	Channels[1].AddPoint( random(set_on +  4 - set_on +  6) , random(5-55), random(143-190));  // 10a-1pm   randomly fade to 56-75% light; late-morning clouds
	Channels[1].AddPoint(            set_on + 7             ,       0     ,       227      );  //    1 pm   fade up to 90% light; midday burst
	Channels[1].AddPoint( random(set_on +  7 - set_on +  9) , random(5-55), random(165-204));  //   1-4pm   randomly fade dn to 60-80% light; midday cloud
	Channels[1].AddPoint( random(set_on + 10 - set_on + 12) , random(5-55), random(190-216));  //   4-7pm   fade up to 75-85% light; mid-evening burst
  Channels[1].AddPoint(                  set_on + 14            ,      30     ,       178      );  //  8:30pm   fade dn to 70% light; start dusk
	Channels[1].SetPoint(             set_off               ,       0     ,        0       );  //   10 pm   dusk ends lights are off; start of night 
}
```
 Please tell me if that makes sense, im new to the coding language and would like to progress from simple on/off triggers to fun stuff like this.


----------



## eltila (Dec 18, 2017)

Thanks


----------



## Hari (Apr 16, 2021)

Even though it is an 'ancient' post,
It would be ungrateful if I don't type something here.
*Thank you* for brightening my planted aquarium, regards, Hari.


----------



## boxhead1990 (Aug 29, 2011)

i am finnally finding this again after a few years and finding myself building basically the same setup but with 4 channels instead of 3 

i have to figure out why its not happy with channel 3 this time tho and im not entirely sure where to go from here


----------

