├── Arduino └── LightController │ ├── LightController.ino │ ├── ChannelManager.h │ └── ChannelManager.cpp ├── LICENSE └── README /Arduino/LightController/LightController.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaf/LightController/HEAD/Arduino/LightController/LightController.ino -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, User "benjaf" at github 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Arduino/LightController/ChannelManager.h: -------------------------------------------------------------------------------- 1 | 2 | #include "Arduino.h" 3 | 4 | // ----------------------- Fade Mode ----------------------- 5 | enum FadeMode 6 | { 7 | fademode_linear, 8 | fademode_exponential 9 | }; 10 | 11 | // ----------------------- Point ----------------------- 12 | class Point 13 | { 14 | public: 15 | Point(); 16 | Point(byte h, byte m, float intensity); 17 | long GetTimeSeconds(); 18 | byte GetHours(); 19 | byte GetMinutes(); 20 | float GetIntensity(); 21 | byte GetIntensityPercent(); 22 | byte GetIntensityInt(); 23 | bool IsValid(); 24 | bool IsZero(); 25 | void PrintPoint(); 26 | 27 | private: 28 | long _minutes; 29 | float _intensity; 30 | }; 31 | 32 | // Channel data type 33 | class Channel 34 | { 35 | public: 36 | Channel(); 37 | Channel(int pin, int maxLength, FadeMode fadeMode, Point* m); 38 | int GetPin(); 39 | void AddPoint(int h, int m, float intensity); 40 | void SetPoint(int index, int h, int m, float intensity); 41 | void SetPoint(int index, Point p); 42 | void ClearPoint(int index); 43 | Point GetPoint(int index); 44 | void GoToCurrentPosition(long time); 45 | void MoveForward(); 46 | int GetLightIntensityInt(long time); 47 | float CorrectForFadeMode(float intensity); 48 | void UpdateCurrentLightValue(long time); 49 | void UpdateData(); 50 | int GetLength(); 51 | void Reset(); 52 | 53 | private: 54 | int _pin; 55 | int _lightValue; 56 | FadeMode _fadeMode; 57 | int _currentPosition; 58 | int _length; 59 | int _maxLength; 60 | Point _previous; 61 | Point _next; 62 | Point* _storage; 63 | }; 64 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is an 'advanced' 24 hour light scheduler designed for DIY Aquarium lighting 2 | 3 | It allows for easy creation of pretty much any 24 hour light schedule imaginable within the limitations of the Arduino used. 4 | This was done to provide easy support for moonlight, sunrise/sunset, siesta etc. 5 | 6 | 7 | Limitations: 8 | - Schedule cannot exceed 24 hours (no handling of differences based on weekday, time of year etc.) 9 | - Max number of channels: Defined by the number of PWM pins available on the Arduino used 10 | - Max number of on/off cycles during a day: Any 11 | - Precision: 12 | - PWM duty cycle: 0-255 13 | - Time-to-PWM precision: 1 second (making the shortest fade timespan in which full PWM resolution is used 255 seconds) 14 | 15 | This is not intended to be a complete aquarium or light controller, 16 | it merely provides you with the code to handle light scheduling! 17 | 18 | You will need to download and install "RTClib" (https://github.com/adafruit/RTClib) for this to work. 19 | 20 | I have NOT included any code for interfaces (Serial, LCD, etc.), nor any delays. 21 | That would make the sketch more context-specific because these things depend on the users actual setup. 22 | Communication via Serial is easy to find documentation on elsewhere. 23 | It should be noted that this code contains no delays! If you start sending data (i.e. via Serial) 24 | you might want to make sure this is only done at apropriate intervals. 25 | 26 | This version uses 2 PWM pins because I only have 2 channels in my setup. 27 | Fore more advanced changes, the user (you) should have basic understanding of programming. 28 | This includes: 29 | - For-loops (and break conditions) 30 | - Objects 31 | 32 | As is probably evident from the code, I am not that experienced with writing software for microcontrollers. 33 | I have written this code from an Object Oriented point of view to make it more aesily understandable. 34 | Several optimizations are possible, but I tried to shoot for at least some degree of readability. 35 | -------------------------------------------------------------------------------- /Arduino/LightController/ChannelManager.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "ChannelManager.h" 3 | 4 | // ---------------- Point ------------------------ 5 | Point::Point() 6 | { 7 | _minutes = 0; 8 | _intensity = 0; 9 | } 10 | 11 | // Initialize Point with given values 12 | Point::Point(byte h, byte m, float intensity) 13 | { 14 | _minutes = (60 * (int)h) + m; 15 | if(intensity > 1) 16 | { 17 | _intensity = intensity / (float)255; 18 | } 19 | else 20 | { 21 | _intensity = intensity; 22 | } 23 | } 24 | 25 | long Point::GetTimeSeconds() 26 | { 27 | return _minutes * 60; 28 | } 29 | 30 | byte Point::GetHours() 31 | { 32 | return (byte)(_minutes / 60); 33 | } 34 | 35 | byte Point::GetMinutes() 36 | { 37 | return (byte)(_minutes % 60); 38 | } 39 | 40 | float Point::GetIntensity() 41 | { 42 | return _intensity; 43 | } 44 | 45 | byte Point::GetIntensityInt() 46 | { 47 | return (byte)((float) 255 * _intensity); 48 | } 49 | 50 | byte Point::GetIntensityPercent() 51 | { 52 | return (byte)(100 * _intensity); 53 | } 54 | 55 | bool Point::IsValid() 56 | { 57 | return _minutes >= 0 && _minutes < 60 * 24 && _intensity >= 0 && _intensity <= 1; 58 | } 59 | 60 | bool Point::IsZero() 61 | { 62 | return _minutes == 0 && _intensity == 0; 63 | } 64 | 65 | void Point::PrintPoint() 66 | { 67 | Serial.print("Point: "); 68 | Serial.print(GetHours()); 69 | Serial.print("-"); 70 | Serial.print(GetMinutes()); 71 | Serial.print("-"); 72 | Serial.print(GetIntensityPercent()); 73 | if(IsValid()) 74 | { 75 | Serial.println(" VALID"); 76 | } 77 | else 78 | { 79 | Serial.println(" NOT VALID"); 80 | } 81 | } 82 | 83 | // ------------------- Channel --------------------------- 84 | 85 | Channel::Channel() {} 86 | 87 | // Initialize channel w. no points 88 | Channel::Channel(int pin, int maxLength, FadeMode fadeMode, Point* m) 89 | { 90 | _pin = pin; 91 | _lightValue = 0; 92 | _fadeMode = fadeMode; 93 | _maxLength = maxLength; 94 | _length = 0; 95 | _currentPosition = 1; 96 | _storage = m; 97 | } 98 | 99 | int Channel::GetPin() 100 | { 101 | return _pin; 102 | } 103 | 104 | // Add Point to first available position (wrapper function) 105 | void Channel::AddPoint(int h, int m, float intensity) 106 | { 107 | Point p = Point(h, m, intensity); 108 | if(_length == 0) 109 | { 110 | _previous = p; 111 | _next = p; 112 | _currentPosition = 1; 113 | _length = 1; 114 | } 115 | else 116 | { 117 | _previous = p; 118 | _currentPosition++; 119 | _length++; 120 | } 121 | SetPoint(_currentPosition, p); 122 | } 123 | 124 | void Channel::SetPoint(int index, int h, int m, float intensity) 125 | { 126 | SetPoint(index, Point(h, m, intensity)); 127 | } 128 | 129 | void Channel::SetPoint(int index, Point p) 130 | { 131 | _storage[index] = p; 132 | } 133 | 134 | void Channel::ClearPoint(int index) 135 | { 136 | SetPoint(index, Point()); 137 | } 138 | 139 | Point Channel::GetPoint(int index) 140 | { 141 | return _storage[index]; 142 | } 143 | 144 | void Channel::GoToCurrentPosition(long time) 145 | { 146 | if(_length <= 1) return; 147 | while(true) 148 | { 149 | if (_previous.GetTimeSeconds() <= time && _next.GetTimeSeconds() > time) 150 | { 151 | // Between 'previous' point and 'next' 152 | break; 153 | } 154 | else if (_previous.GetTimeSeconds() <= time && _next.GetTimeSeconds() < _previous.GetTimeSeconds()) 155 | { 156 | // Between 'previous' point and 'next', currently before midnight and 'next' beeing after midnight 157 | break; 158 | } 159 | else if (_previous.GetTimeSeconds() > time && _next.GetTimeSeconds() > time && _previous.GetTimeSeconds() > _next.GetTimeSeconds()) 160 | { 161 | // Between 'previous' point and 'next', currently after midnight and 'previous' point being before midnight 162 | break; 163 | } 164 | else 165 | { 166 | // Better luck next time 167 | MoveForward(); 168 | } 169 | } 170 | } 171 | 172 | void Channel::MoveForward() 173 | { 174 | int nextPosition = 1; 175 | if(_currentPosition < _length - 1) 176 | { 177 | _currentPosition++; 178 | nextPosition = _currentPosition + 1; 179 | } 180 | else if(_currentPosition == _length - 1) 181 | { 182 | _currentPosition++; 183 | nextPosition = 1; 184 | } 185 | else if(_currentPosition == _length) 186 | { 187 | _currentPosition = 1; 188 | nextPosition = _currentPosition + 1; 189 | } 190 | _previous = _storage[_currentPosition]; 191 | _next = _storage[nextPosition]; 192 | } 193 | 194 | int Channel::GetLightIntensityInt(long time) 195 | { 196 | UpdateCurrentLightValue(time); 197 | return _lightValue; 198 | } 199 | 200 | float Channel::CorrectForFadeMode(float intensity) 201 | { 202 | switch(_fadeMode) 203 | { 204 | case fademode_exponential: 205 | return intensity * intensity; 206 | default: 207 | return intensity; 208 | } 209 | } 210 | 211 | void Channel::UpdateData() 212 | { 213 | _length = 1; 214 | Point _c; 215 | Point _n; 216 | for(_length; _length < _maxLength; _length++) 217 | { 218 | // End of channel has been reached if: 219 | _c = _storage[_length]; 220 | //_c.PrintPoint(); 221 | if(!_c.IsValid() || _length == _maxLength) 222 | { 223 | // Current point is invalid (can only happen at the first one) or Max length of channel has been reached 224 | return; 225 | } 226 | _n = _storage[_length + 1]; 227 | if(!_n.IsValid() || _n.GetTimeSeconds() < _c.GetTimeSeconds() || (_c.IsZero() && _n.IsZero())) 228 | { 229 | // Next point is invalid or earlier than current point (different kind of invalid) 230 | return; 231 | } 232 | } 233 | } 234 | 235 | int Channel::GetLength() 236 | { 237 | return _length; 238 | } 239 | 240 | void Channel::UpdateCurrentLightValue(long time) 241 | { 242 | GoToCurrentPosition(time); 243 | long timeDiff = 0; 244 | long prevPointTime = _previous.GetTimeSeconds(); 245 | float prevPointIntensity = _previous.GetIntensity(); 246 | long nextPointTime = _next.GetTimeSeconds(); 247 | float nextPointIntensity = _next.GetIntensity(); 248 | 249 | if(prevPointTime > nextPointTime) // Midnight rollover 250 | { 251 | timeDiff = (nextPointTime + 24 * 60 * 60) - prevPointTime; 252 | } 253 | else 254 | { 255 | timeDiff = nextPointTime - prevPointTime; 256 | } 257 | 258 | long progress = 0; 259 | if(time >= prevPointTime) // Currently before midnight 260 | { 261 | progress = time - prevPointTime; 262 | } 263 | else // Currently after midnight 264 | { 265 | progress = (time + 24 * 60 * 60) - prevPointTime; 266 | } 267 | float intensityDiff = nextPointIntensity - prevPointIntensity; // Difference in light intesity between points 268 | float intensity = prevPointIntensity + (progress * (intensityDiff / timeDiff)); // Current intensity 269 | _lightValue = 255 * CorrectForFadeMode(intensity); // Light value 270 | } 271 | 272 | void Channel::Reset() 273 | { 274 | _currentPosition = 1; 275 | _previous = GetPoint(1); 276 | _next = GetPoint(1); 277 | } --------------------------------------------------------------------------------