├── .gitignore ├── ColorState.cpp ├── ColorState.h ├── DisplayOutput.cpp ├── DisplayOutput.h ├── README.md ├── Schematic (Wiring Diagram).pdf └── day-cycle-clock-code.ino /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | -------------------------------------------------------------------------------- /ColorState.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorState.h" 2 | 3 | 4 | ColorState::ColorState(bool debug_print){ 5 | 6 | } 7 | 8 | bool ColorState::transitionTimes(int16_t sunrise_minute, 9 | int16_t sunset_minute){ 10 | transition_time[NIGHT_END] = sunrise_minute - rise_set_duration / 3; 11 | transition_time[RISE_PEAK] = sunrise_minute; 12 | transition_time[DAY_START] = sunrise_minute + rise_set_duration / 1.25; 13 | transition_time[DAY_MID] = sunrise_minute + (sunset_minute - sunrise_minute)/2; 14 | transition_time[DAY_END] = sunset_minute - rise_set_duration / 1.25; 15 | transition_time[SET_PEAK] = sunset_minute; 16 | transition_time[NIGHT_START] = sunset_minute + rise_set_duration / 3; 17 | transition_time[NIGHT_MID] = 1440; //sunset_minute + (sunrise_minute + 1440 - sunset_minute)/2; 18 | 19 | 20 | 21 | for (int8_t i = 0; i < number_of_transition_times; i++){ 22 | #ifdef DEBUG 23 | Serial.print(i); 24 | Serial.print(" "); 25 | Serial.println(transition_time[i]); 26 | #endif 27 | } 28 | 29 | 30 | 31 | 32 | return true; 33 | } 34 | 35 | int8_t ColorState::nextTransition(int16_t current_time_in_minutes){ 36 | 37 | for(int8_t i = 0; i < number_of_transition_times; i++){ 38 | 39 | if (i == NIGHT_END){ 40 | prev_transition_time = 0; 41 | } else { 42 | prev_transition_time = transition_time[i-1]; 43 | } 44 | 45 | if (i == NIGHT_MID){ 46 | next_transition_time = 1440; 47 | } else { 48 | next_transition_time = transition_time[i]; 49 | } 50 | 51 | if (current_time_in_minutes < next_transition_time && current_time_in_minutes >= prev_transition_time){ 52 | next_transition = i; 53 | if (i==0){ 54 | prev_transition = 7; 55 | }else{ 56 | prev_transition = i-1; 57 | } 58 | 59 | break; 60 | } 61 | } 62 | return next_transition; 63 | } 64 | 65 | int ColorState::currentColors(int16_t current_time_in_minutes){ 66 | int next_transition = nextTransition(current_time_in_minutes); 67 | #ifdef DEBUG 68 | Serial.print("Current Time: "); 69 | Serial.println(current_time_in_minutes); 70 | Serial.print("Prev Transition Time: "); 71 | Serial.println(prev_transition_time); 72 | Serial.print("Next Transition Time: "); 73 | Serial.println(next_transition_time); 74 | Serial.print("next_transition: "); 75 | Serial.println(next_transition); 76 | #endif 77 | int16_t time_since_last_transition = current_time_in_minutes - prev_transition_time; 78 | #ifdef DEBUG 79 | Serial.print("Time since last: "); 80 | Serial.println(time_since_last_transition); 81 | #endif 82 | uint8_t transition_progress = (float) 100* time_since_last_transition / (next_transition_time - prev_transition_time);//(time_since_last_transition / (next_transition_time - prev_transition_time))*100; 83 | #ifdef DEBUG 84 | Serial.print("Transition Progress: "); 85 | Serial.println(transition_progress); 86 | Serial.print("prev_transition: "); 87 | Serial.println(prev_transition); 88 | Serial.print("next_transition: "); 89 | Serial.println(next_transition); 90 | #endif 91 | for (int i=0; i < 3; i++){ 92 | for (int j=0; j < 3; j++){ 93 | #ifdef DEBUG 94 | Serial.print(i); 95 | Serial.println(j); 96 | Serial.print(colors[prev_transition][i][j]); 97 | Serial.print(" + "); 98 | Serial.print((float)transition_progress/100); 99 | Serial.print(" * ("); 100 | Serial.print(colors[next_transition][i][j]); 101 | Serial.print(" - "); 102 | Serial.print(colors[prev_transition][i][j]); 103 | Serial.println(" )"); 104 | #endif 105 | float float_color_value = (float)colors[prev_transition][i][j] + (colors[next_transition][i][j] - colors[prev_transition][i][j])*transition_progress/100; 106 | current_colors[i][j] = static_cast(float_color_value); 107 | } 108 | } 109 | 110 | return current_colors; 111 | } 112 | 113 | uint8_t ColorState::currentAngle(int16_t current_time_in_minutes){ 114 | if (next_transition >= RISE_PEAK && next_transition <= NIGHT_START){ 115 | current_angle = static_cast((float)(western_horizon - eastern_horizon)*(current_time_in_minutes - transition_time[NIGHT_END])/(transition_time[NIGHT_START] - transition_time[NIGHT_END])); 116 | daytime = 1; 117 | } else if (next_transition == NIGHT_MID){ 118 | current_angle = static_cast((float)(western_horizon - eastern_horizon) - (western_horizon - eastern_horizon)/2*(current_time_in_minutes - transition_time[NIGHT_START])/(transition_time[NIGHT_MID] - transition_time[NIGHT_START])); 119 | daytime = 0; 120 | } else if (next_transition == NIGHT_END){ 121 | current_angle = static_cast((float)(western_horizon - eastern_horizon)/2 - (western_horizon - eastern_horizon)/2*(current_time_in_minutes)/(transition_time[NIGHT_END])); 122 | daytime = 0; 123 | } 124 | 125 | return current_angle; 126 | } 127 | 128 | bool ColorState::updateColors(int16_t current_time_in_minutes) { 129 | nextTransition(current_time_in_minutes); 130 | currentColors(current_time_in_minutes); 131 | currentAngle(current_time_in_minutes); 132 | return true; 133 | } 134 | -------------------------------------------------------------------------------- /ColorState.h: -------------------------------------------------------------------------------- 1 | #ifndef cs 2 | #define cs 3 | 4 | #if (ARDUINO >=100) 5 | #include "Arduino.h" 6 | #else 7 | #include "WProgram.h" 8 | #endif 9 | 10 | // #define DEBUG 11 | 12 | enum DayTransitions {NIGHT_END, 13 | RISE_PEAK, 14 | DAY_START, 15 | DAY_MID, 16 | DAY_END, 17 | SET_PEAK, 18 | NIGHT_START, 19 | NIGHT_MID}; 20 | 21 | 22 | #define number_of_transition_times 8 23 | 24 | #define eastern_horizon 0 25 | #define western_horizon 170 26 | 27 | #define rise_set_duration 180 28 | 29 | class ColorState { 30 | public: 31 | // Constructor 32 | ColorState(bool debug_print); 33 | 34 | // Configuration 35 | bool transitionTimes(int16_t sunrise_minute, 36 | int16_t sunset_minute); 37 | 38 | // Mehods 39 | bool updateColors(int16_t); 40 | int8_t nextTransition(int16_t); 41 | int currentColors(int16_t); 42 | uint8_t currentAngle(int16_t); 43 | 44 | //variables 45 | int16_t transition_time[number_of_transition_times] = {}; 46 | int8_t next_transition; 47 | int8_t prev_transition; 48 | uint8_t current_angle; 49 | bool daytime; 50 | int current_colors[3][3]; 51 | int colors[8][3][3] = { 52 | {{4,35,115},{35,30,25},{0,15,52}}, 53 | {{245, 15, 0},{225,70,0},{247,103,10}}, 54 | {{50, 100, 255},{100, 70, 50},{70,50,45}}, 55 | {{79, 170, 255},{180, 140, 140},{120, 100, 100}}, 56 | {{50, 100, 255},{150,80,15},{80,50,30}}, 57 | {{253,60,15},{189,32,40},{132,103,159}}, 58 | {{4,35,115},{125,100,45},{0,15,52}}, 59 | {{0,0,80},{15,10,30},{0,5,20}} 60 | }; 61 | 62 | private: 63 | 64 | bool _debug_print; 65 | int16_t prev_transition_time; 66 | int16_t next_transition_time; 67 | }; 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /DisplayOutput.cpp: -------------------------------------------------------------------------------- 1 | #include "DisplayOutput.h" 2 | 3 | DisplayOutput::DisplayOutput(){ 4 | FastLED.addLeds(leds, NUM_LEDS); 5 | } 6 | 7 | void DisplayOutput::begin(){ 8 | 9 | tft.begin(); 10 | tft.setRotation(2); 11 | } 12 | 13 | void DisplayOutput::servoMoveTo(uint8_t degree, uint16_t s_delay){ 14 | #ifdef DEBUG 15 | Serial.print("Servo Move To: "); 16 | Serial.println(degree); 17 | #endif 18 | servo.attach(SERVO_PIN); 19 | servo.write(degree); 20 | delay(s_delay); 21 | servo.detach(); 22 | } 23 | 24 | void DisplayOutput::servoAttach(){ 25 | servo.attach(SERVO_PIN); 26 | } 27 | 28 | void DisplayOutput::servoDetach(){ 29 | servo.attach(SERVO_PIN); 30 | } 31 | 32 | void DisplayOutput::stripRGBRow(int r, int g, int b, int row) { 33 | #ifdef DEBUG 34 | Serial.print(r); 35 | Serial.print(" , "); 36 | Serial.print(g); 37 | Serial.print(" , "); 38 | Serial.print(b); 39 | Serial.print(" , "); 40 | Serial.println(row); 41 | #endif 42 | int first_led_in_row = NUM_LEDS / NUM_ROWS * row; 43 | int last_led_in_row = NUM_LEDS / NUM_ROWS * (row + 1); 44 | for (int x = first_led_in_row; x < last_led_in_row; x++) { 45 | leds[x] = CRGB(r, g, b); 46 | } 47 | FastLED.show(); 48 | } 49 | 50 | void DisplayOutput::stripRGB(int colors[NUM_ROWS][3]){ 51 | 52 | #ifdef DEBUG 53 | Serial.print("{"); 54 | #endif 55 | for (int row; row < NUM_ROWS; row++){ 56 | #ifdef DEBUG 57 | Serial.print("{"); 58 | Serial.print(colors[row][0]); 59 | Serial.print(", "); 60 | Serial.print(colors[row][1]); 61 | Serial.print(", "); 62 | Serial.print(colors[row][2]); 63 | Serial.print("}, "); 64 | #endif 65 | stripRGBRow(colors[row][0], colors[row][1], colors[row][2], row); 66 | } 67 | 68 | #ifdef DEBUG 69 | Serial.println("}"); 70 | #endif 71 | } 72 | 73 | void DisplayOutput::drawSun() { 74 | tft.fillScreen(); 75 | fillCircle(YELLOW); 76 | } 77 | 78 | 79 | void DisplayOutput::fillArc(int32_t x0, int32_t y0, int32_t r1, bool side, int8_t percent_transition, uint8_t phase, int16_t color){ 80 | int32_t y1; 81 | int32_t y2; 82 | int32_t width = r1*percent_transition/100; 83 | int32_t r2 = width/2+sq(r1)/(2*width); 84 | int32_t x02 = x0 - (r2 - width); 85 | #ifdef DEBUG 86 | Serial.print("percent_transition: "); 87 | Serial.print(percent_transition); 88 | Serial.print("width: "); 89 | Serial.print(width); 90 | Serial.print(" r2: "); 91 | Serial.print(r2); 92 | Serial.print(" x02: "); 93 | Serial.println(x02); 94 | 95 | #endif 96 | if(x0+width < tft_width && x0+width > 0){ 97 | 98 | for(int16_t x = x0; x < x0+r1; x++ ){ 99 | y1 = sqrt(sq(r1)-sq(x-x0)); 100 | y2 = sqrt(sq(r2)-sq(x-x02)); 101 | #ifdef DEBUG 102 | Serial.print("x: "); 103 | Serial.print(x); 104 | Serial.print(" y1: "); 105 | Serial.print(y1); 106 | Serial.print(" y2: "); 107 | Serial.print(y2); 108 | Serial.print(" h: "); 109 | Serial.println(y1*2); 110 | #endif 111 | 112 | if (phase == WAXING_CRESCENT || phase == WANING_CRESCENT){ 113 | if(side == 0){ 114 | tft.drawFastVLine(2*x0-x, y0-y1, y1-y2, color); 115 | tft.drawFastVLine(2*x0-x, y0+y2, y1-y2, color); 116 | } 117 | if(side == 1){ 118 | tft.drawFastVLine(x, y0-y1, y1-y2, color); 119 | tft.drawFastVLine(x, y0+y2, y1-y2, color); 120 | } 121 | }else{ 122 | if(side == 0){ 123 | tft.drawFastVLine(2*x0-x, y0-y2, y2*2, color); 124 | } 125 | if(side == 1){ 126 | tft.drawFastVLine(x, y0-y2, y2*2, color); 127 | } 128 | } 129 | } 130 | 131 | } 132 | } 133 | 134 | 135 | void DisplayOutput::updateMoon( uint8_t moon_phase_precentage){ 136 | uint8_t quot = moon_phase_precentage / 25; 137 | uint8_t rem = moon_phase_precentage % 25; 138 | int8_t moon_progress; 139 | 140 | if (rem <= phase_buffer){ 141 | moon_phase = quot * 2; 142 | }else if( rem < 25 - phase_buffer){ 143 | moon_phase = quot * 2 + 1; 144 | }else{ 145 | moon_phase = quot * 2 + 2; 146 | } 147 | 148 | tft.fillScreen(); 149 | //Draw Moon shadow gradient 150 | if (MOON_SHADOW && moon_phase_precentage % 50 > phase_buffer*2 && moon_phase_precentage % 50 < 50 - phase_buffer*2){ 151 | if (moon_phase <= FULL_MOON){ 152 | moon_progress = -1; 153 | }else{ 154 | moon_progress = 1; 155 | } 156 | drawMoon(moon_phase, moon_phase_precentage, tft_width/2+(9*moon_progress), 0x4208); 157 | drawMoon(moon_phase, moon_phase_precentage, tft_width/2+(6*moon_progress), 0x8410); 158 | drawMoon(moon_phase, moon_phase_precentage, tft_width/2+(3*moon_progress), 0xDEDB); 159 | } 160 | drawMoon(moon_phase, moon_phase_precentage, tft_width/2, WHITE); 161 | } 162 | 163 | void DisplayOutput::drawMoon( uint8_t moon_phase, uint8_t moon_phase_precentage, int16_t x0, int16_t color){ 164 | 165 | 166 | uint8_t percent_transition; 167 | 168 | 169 | #ifdef DEBUG 170 | Serial.print("Moon Phase Test: "); 171 | Serial.println(moon_phase); 172 | #endif 173 | 174 | switch (moon_phase) { 175 | case NEW_MOON: 176 | 177 | break; 178 | case WAXING_CRESCENT: 179 | percent_transition = 100-moon_phase_precentage*100/25; 180 | fillArc(x0, tft_y_center, 62, 1, percent_transition, moon_phase, color); 181 | break; 182 | case FIRST_QUARTER: 183 | fillArc(x0, tft_y_center, 62, 1, 100, moon_phase, color); 184 | break; 185 | case WAXING_GIBBOUS: 186 | percent_transition = (moon_phase_precentage-25)*100/25; 187 | fillArc(x0, tft_y_center, 62, 1, 100, moon_phase, color); 188 | fillArc(x0, tft_y_center, 62, 0, percent_transition, moon_phase, color); 189 | break; 190 | case FULL_MOON: 191 | fillArc(x0, tft_y_center, 62, 0, 100, moon_phase, color); 192 | fillArc(x0, tft_y_center, 62, 1, 100, moon_phase, color); 193 | break; 194 | case WANING_GIBBOUS: 195 | percent_transition = 100-(moon_phase_precentage-50)*100/25; 196 | fillArc(x0, tft_y_center, 62, 0, 100, moon_phase, color); 197 | fillArc(x0, tft_y_center, 62, 1, percent_transition, moon_phase, color); 198 | break; 199 | case THIRD_QUARTER: 200 | fillArc(x0, tft_y_center, 62, 0, 100, moon_phase, color); 201 | break; 202 | case WANING_CRESCENT: 203 | percent_transition = (moon_phase_precentage-75)*100/25; 204 | fillArc(x0, tft_y_center, 62, 0, percent_transition, moon_phase, color); 205 | break; 206 | } 207 | 208 | #ifdef DEBUG 209 | Serial.print("Moon Phase: "); 210 | Serial.println(moon_phase_precentage); 211 | #endif 212 | } 213 | 214 | void DisplayOutput::printMenuTitle(String titleString) { 215 | fillMenuTitle(); 216 | int x_offset = (4 - titleString.length()) * 8; 217 | tft.setCursor(32 + x_offset, 16); 218 | tft.setTextColor(BLACK); 219 | tft.setTextSize(3); 220 | tft.print(titleString); 221 | } 222 | 223 | bool DisplayOutput::amCheck(int time_hour){ 224 | if( time_hour < 12){ 225 | return true; 226 | }else{ 227 | return false; 228 | } 229 | } 230 | 231 | int DisplayOutput::convert12Hr(int time_hour){ 232 | if (time_hour == 0){ 233 | return 12; 234 | }else if( time_hour > 12){ 235 | return time_hour - 12; 236 | }else{ 237 | return time_hour; 238 | } 239 | 240 | } 241 | 242 | void DisplayOutput::printAMPM(bool AM){ 243 | fillAMPM(); 244 | 245 | tft.setCursor(48, 84); 246 | tft.setTextColor(BLACK); 247 | tft.setTextSize(3); 248 | if(AM){ 249 | tft.print("AM"); 250 | }else{ 251 | tft.print("PM"); 252 | } 253 | } 254 | 255 | void DisplayOutput::printTime(int time_hour, int time_min) { 256 | bool AM = amCheck(time_hour); 257 | time_hour = convert12Hr(time_hour); 258 | printAMPM(AM); 259 | 260 | fillValue(); 261 | tft.setCursor(7, 48); 262 | tft.setTextColor(BLACK); 263 | tft.setTextSize(4); 264 | if(time_hour < 10){ 265 | tft.print(0, DEC); 266 | } 267 | tft.print(time_hour, DEC); 268 | tft.print(':'); 269 | if(time_min < 10){ 270 | tft.print(0, DEC); 271 | } 272 | tft.println(time_min, DEC); 273 | 274 | 275 | 276 | } 277 | 278 | void DisplayOutput::printDate(uint8_t month, uint8_t day, uint16_t year) { 279 | fillValue(); 280 | tft.setCursor(16, 42); 281 | tft.setTextColor(BLACK); 282 | tft.setTextSize(3); 283 | tft.print(daysOfTheMonth[month]); 284 | tft.print(' '); 285 | tft.println(day, DEC); 286 | tft.setCursor(28, 72); 287 | tft.print(year, DEC); 288 | } 289 | 290 | void DisplayOutput::printValue(int value, bool hr = 0) { 291 | 292 | if(hr){ 293 | bool AM = amCheck(value); 294 | value = convert12Hr(value); 295 | printAMPM(AM); 296 | } 297 | 298 | int val_length = String(value).length(); 299 | int cursor_x; 300 | tft.setTextColor(BLACK); 301 | if (val_length <= 5) { 302 | tft.setTextSize(4); 303 | cursor_x = 6 + (5 - val_length) * 12; 304 | } else { 305 | tft.setTextSize(3); 306 | cursor_x = 6 + (5 - val_length) * 10; 307 | } 308 | fillValue(); 309 | tft.setCursor(cursor_x, 48); 310 | tft.print(value); 311 | 312 | 313 | 314 | } 315 | 316 | void DisplayOutput::fillMenuTitle() { 317 | tft.fillRect(32, 16, 70, 24, YELLOW); 318 | } 319 | 320 | void DisplayOutput::fillValue() { 321 | tft.fillRect(7, 48, 118, 30, YELLOW); 322 | } 323 | 324 | void DisplayOutput::fillAMPM() { 325 | tft.fillRect(48, 80, 28, 24, YELLOW); 326 | } 327 | 328 | void DisplayOutput::fillCircle(uint16_t color) { 329 | tft.fillCircle(tft_width/2, tft_y_center, tft_width/2-2, color); 330 | } 331 | -------------------------------------------------------------------------------- /DisplayOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef do 2 | #define do 3 | 4 | #if (ARDUINO >=100) 5 | #include "Arduino.h" 6 | #else 7 | #include "WProgram.h" 8 | #endif 9 | 10 | #include 11 | #include "FastLED.h" 12 | #include 13 | #include 14 | #include 15 | 16 | //#define DEBUG 17 | 18 | #define SERVO_PIN 3 19 | 20 | #define NUM_LEDS 45 21 | #define NUM_ROWS 3 22 | #define LED_PIN 6 23 | #define LED_TYPE WS2812B 24 | #define COLOR_ORDER GRB 25 | 26 | #define __CS 8 27 | #define __DC 9 28 | #define __A0 10 29 | 30 | #define BLACK 0x0000 31 | #define YELLOW 0xFFE0 32 | #define WHITE 0xFFFF 33 | 34 | enum MoonPhases { 35 | NEW_MOON, 36 | WAXING_CRESCENT, 37 | FIRST_QUARTER, 38 | WAXING_GIBBOUS, 39 | FULL_MOON, 40 | WANING_GIBBOUS, 41 | THIRD_QUARTER, 42 | WANING_CRESCENT 43 | }; 44 | 45 | 46 | class DisplayOutput { 47 | public: 48 | // Constructor 49 | DisplayOutput(); 50 | 51 | 52 | // Configuration 53 | Servo servo; 54 | CRGB leds[NUM_LEDS]; 55 | TFT_ILI9163C tft = TFT_ILI9163C(__CS, __A0 , __DC); 56 | void begin(); 57 | 58 | // Mehods 59 | void servoMoveTo(uint8_t degree, uint16_t s_delay); 60 | void servoAttach(); 61 | void servoDetach(); 62 | void stripRGBRow(int r, int g, int b, int row); 63 | void stripRGB(int colors[NUM_ROWS][3]); 64 | void updateScreen(bool daytime, uint8_t moon_phase_precentage); 65 | void drawSun(); 66 | void fillArc(int32_t x0, int32_t y0, int32_t r1, bool side, int8_t percent_transition, uint8_t phase, int16_t color); 67 | void updateMoon( uint8_t moon_phase_precentage); 68 | void drawMoon( uint8_t moon_phase, uint8_t moon_phase_precentage, int16_t x0, int16_t color); 69 | void printMenuTitle(String titleString); 70 | bool amCheck(int time_hour); 71 | int convert12Hr(int time_hour); 72 | void printAMPM(bool AM); 73 | void printTime(int time_hour, int time_min); 74 | void printDate(uint8_t month, uint8_t day, uint16_t year); 75 | void printValue(int value, bool hr = false); 76 | void fillMenuTitle(); 77 | void fillValue(); 78 | void fillAMPM(); 79 | void fillCircle(uint16_t color); 80 | 81 | 82 | //variables 83 | 84 | 85 | private: 86 | String daysOfTheMonth[13] = {"", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 87 | uint8_t moon_phase; 88 | uint8_t phase_buffer = 2; 89 | const bool MOON_SHADOW = 1; 90 | uint8_t tft_width = 128; 91 | uint8_t tft_height = 128; 92 | uint8_t tft_center_height_offset = 1; 93 | uint8_t tft_y_center = tft_height / 2 + tft_center_height_offset; 94 | 95 | 96 | }; 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # day-cycle-clock-code 2 | 3 | This repository contains the code for the Day Cycle Clock project. It was written to run on an Arduino Nano, it should work on other Arduino compatible boards, but likely will need to change some pin definitions. 4 | 5 | The CAD models for this project are in this repo: https://github.com/Rich-Nelson/day-cycle-clock 6 | 7 | Links to the build video and blog post coming soon. 8 | 9 | ## Structure 10 | 11 | The project could use some refactoring, but here is the high level project organization 12 | - **day-cycle-clock-code.ino** Contains the main loop and it primarility handles the input buttons, clock settings, RTC, and menu 13 | - **ColorState** Contains the functions to determine what the state of the clock should be based on the current date, time, and location. 14 | - **DisplayOutput** Controls the output updating the LED strips, servo angle, and screen. 15 | 16 | ## Dependencies 17 | 18 | You will need some 3rd party libraries as well see below includes for reference: 19 | ``` 20 | #include 21 | #include "RTClib.h" 22 | #include 23 | #include 24 | #include "FastLED.h" 25 | #include 26 | #include 27 | #include 28 | ``` 29 | 30 | Some of these may already be included in the arduino environment, but I included them all just to be safe. 31 | -------------------------------------------------------------------------------- /Schematic (Wiring Diagram).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rich-Nelson/day-cycle-clock-code/718529edafc0bf9fd126e699744a60d9f2a085ab/Schematic (Wiring Diagram).pdf -------------------------------------------------------------------------------- /day-cycle-clock-code.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RTClib.h" 3 | #include 4 | #include "ColorState.h" 5 | #include "DisplayOutput.h" 6 | 7 | //#define DEBUG 8 | 9 | ColorState colorstate(false); 10 | DisplayOutput displayoutput; 11 | 12 | 13 | #define BUTTON_UP 16 14 | #define BUTTON_LEFT 15 15 | #define BUTTON_DOWN 14 16 | 17 | Pushbutton button_up(BUTTON_UP); 18 | Pushbutton button_left(BUTTON_LEFT); 19 | Pushbutton button_down(BUTTON_DOWN); 20 | 21 | 22 | RTC_DS1307 rtc; 23 | 24 | TimeLord tardis; 25 | 26 | //Initial Settings 27 | //Note: consider storing these in persistant storage 28 | int8_t latitude = 40; 29 | int16_t longitude = -75; 30 | int8_t time_zone = -4; 31 | 32 | //Calulated Time Settings 33 | uint16_t sunrise_minute; 34 | uint16_t sunset_minute; 35 | uint8_t moon_phase_precentage; 36 | bool DST; 37 | 38 | //Last date/time settings 39 | uint32_t last_date_in_days; 40 | uint16_t last_time_in_minutes; 41 | int8_t last_latitude; 42 | int16_t last_longitude; 43 | bool last_daytime; 44 | uint8_t last_moon_phase_precentage; 45 | uint8_t last_servo_angle; 46 | 47 | #define FORCE 1 48 | 49 | 50 | #define UPDATE_INTERVAL 0 51 | #define TFT_RESET_INTERVAL 3600000 52 | long last_update = millis() - UPDATE_INTERVAL; 53 | long last_tft_reset = TFT_RESET_INTERVAL; 54 | 55 | void setup() 56 | { 57 | Serial.begin(115200); 58 | rtc.begin(); 59 | displayoutput.begin(); 60 | 61 | tardis.TimeZone(time_zone * 60); 62 | tardis.Position(latitude, longitude); 63 | 64 | } 65 | 66 | 67 | 68 | /* 69 | Shows current name and value of a variable on the screen 70 | Allow the value to be adjusted via buttons 71 | Returns the new value 72 | 73 | @param title the name of the variable being changed 74 | @param value the current value of the variable 75 | @param val_min the minumum value for this variable 76 | @param val_mxn the maimum value for this variable 77 | @return The new value 78 | */ 79 | int selectValue(String title, int value, int val_min, int val_max) { 80 | displayoutput.drawSun(); 81 | bool hr = false; 82 | if (title.equals("Hour")){ 83 | hr = true; 84 | } 85 | displayoutput.printMenuTitle(title); 86 | displayoutput.printValue(value, hr); 87 | while (!button_left.getSingleDebouncedPress()) { 88 | if (button_up.getSingleDebouncedPress()) { 89 | if (value < val_max) { 90 | value++; 91 | } else { 92 | value = val_min; 93 | } 94 | displayoutput.printValue(value, hr); 95 | } 96 | if (button_down.getSingleDebouncedPress()) { 97 | if (value > val_min) { 98 | value--; 99 | } else { 100 | value = val_max; 101 | } 102 | displayoutput.printValue(value, hr); 103 | } 104 | } 105 | return value; 106 | } 107 | 108 | 109 | void settingsMenu() { 110 | displayoutput.drawSun(); 111 | int8_t new_month = selectValue("Mnth", rtc.now().month(), 1, 12); 112 | int8_t new_day = selectValue("Day", rtc.now().day(), 1, 31); 113 | int16_t new_year = selectValue("Year", rtc.now().year(), 1900, 2100); 114 | int8_t new_hour = selectValue("Hour", rtc.now().hour(), 0, 23); 115 | int8_t new_minute = selectValue("Min", rtc.now().minute(), 0, 59); 116 | rtc.adjust(DateTime(new_year, new_month, new_day, new_hour, new_minute, 30)); 117 | latitude = selectValue("Lat", latitude, -90, 90); 118 | longitude = selectValue("Long", longitude, -180, 180); 119 | tardis.Position(latitude, longitude); 120 | 121 | } 122 | 123 | bool daylightSavings(){ 124 | bool DST = 0; 125 | int year_of_centruy = rtc.now().year() - 2000; 126 | int sunday_offset = (year_of_centruy + year_of_centruy/4 + 2) % 7; 127 | 128 | if(rtc.now().month() == 3 && rtc.now().day() == (14 - sunday_offset) && rtc.now().hour() >= 2){ 129 | DST = 1; 130 | } 131 | if(rtc.now().month() == 3 && rtc.now().day() > (14 - sunday_offset) || rtc.now().month() > 3){ 132 | DST = 1; 133 | } 134 | if(rtc.now().month() == 11 && rtc.now().day() == (7 - sunday_offset) && rtc.now().hour() >= 2){ 135 | DST = 0; 136 | } 137 | if(rtc.now().month() == 11 && rtc.now().day() > (7 - sunday_offset) || rtc.now().month() > 11 || rtc.now().month() < 3){ 138 | DST = 0; 139 | } 140 | return DST; 141 | } 142 | 143 | void calculateDayParams () { 144 | tardis.Position(latitude, longitude); 145 | byte sunrise[] = { 0, 0, 0, rtc.now().day(), rtc.now().month(), rtc.now().year() - 2000}; 146 | tardis.SunRise(sunrise); 147 | sunrise_minute = ((sunrise[tl_hour] - 1 + DST) * 60) + sunrise[tl_minute]; 148 | byte sunset[] = { 0, 0, 0, rtc.now().day(), rtc.now().month(), rtc.now().year() - 2000}; 149 | tardis.SunSet(sunset); 150 | sunset_minute = ((sunset[tl_hour] - 1 + DST) * 60) + sunset[tl_minute]; 151 | byte phase_today[] = { 0, 0, 12, rtc.now().day(), rtc.now().month(), rtc.now().year() - 2000}; 152 | moon_phase_precentage = tardis.MoonPhase(phase_today) * 100; 153 | } 154 | 155 | uint16_t currentTimeInMinutes() { 156 | return rtc.now().hour() * 60 + rtc.now().minute(); 157 | } 158 | 159 | 160 | uint32_t currentDateinDays() { 161 | //rough calc does not account for month lengths 162 | return rtc.now().year() * 365 + rtc.now().month() * 30 + rtc.now().day(); 163 | } 164 | 165 | void displayTime() { 166 | calculateDayParams (); 167 | displayoutput.drawSun(); 168 | displayoutput.printTime(rtc.now().hour(), rtc.now().minute()); 169 | delay(2000); 170 | displayoutput.drawSun(); 171 | displayoutput.printDate(rtc.now().month(), rtc.now().day(), rtc.now().year()); 172 | delay(2000); 173 | displayoutput.drawSun(); 174 | displayoutput.printMenuTitle("Rise"); 175 | displayoutput.printTime(sunrise_minute / 60, sunrise_minute % 60); 176 | delay(2000); 177 | displayoutput.printMenuTitle("Set"); 178 | displayoutput.printTime(sunset_minute / 60, sunset_minute % 60); 179 | delay(2000); 180 | } 181 | 182 | void updateScreen(bool daytime, uint8_t moon_phase_precentage) { 183 | #ifdef DEBUG 184 | Serial.print("Daytime: "); 185 | Serial.println(daytime); 186 | #endif 187 | if (daytime) { 188 | displayoutput.drawSun(); 189 | } else { 190 | displayoutput.updateMoon(moon_phase_precentage); 191 | } 192 | } 193 | 194 | void updateColorDisplay(bool force_update = 0) { 195 | if (latitude != last_latitude || longitude != longitude || last_date_in_days != currentDateinDays() || force_update) { 196 | 197 | calculateDayParams (); 198 | colorstate.transitionTimes(sunrise_minute, sunset_minute); 199 | DST = daylightSavings(); 200 | 201 | last_latitude = latitude; 202 | last_longitude = longitude; 203 | last_date_in_days = currentDateinDays(); 204 | #ifdef DEBUG 205 | Serial.println("Day Update"); 206 | Serial.print("DST: "); 207 | Serial.println(DST); 208 | #endif 209 | 210 | } 211 | 212 | if (last_time_in_minutes != currentTimeInMinutes() || force_update) { 213 | #ifdef DEBUG 214 | Serial.print("Current Time in Minutes: "); 215 | Serial.println(currentTimeInMinutes()); 216 | #endif 217 | colorstate.updateColors(currentTimeInMinutes()); 218 | displayoutput.stripRGB(colorstate.current_colors); 219 | 220 | if (colorstate.current_angle != last_servo_angle || force_update) { 221 | displayoutput.servoMoveTo(colorstate.current_angle, 500); 222 | updateScreen(colorstate.daytime, moon_phase_precentage); 223 | 224 | last_servo_angle = colorstate.current_angle; 225 | } 226 | 227 | last_time_in_minutes = currentTimeInMinutes(); 228 | } 229 | 230 | } 231 | 232 | void fastDayCycle() { 233 | calculateDayParams (); 234 | colorstate.transitionTimes(sunrise_minute, sunset_minute); 235 | int colorex[3][3] = {{255, 255, 255}, {255, 255, 255}, {255, 255, 255}}; 236 | displayoutput.stripRGB(colorex); 237 | uint16_t servo_pos; 238 | int8_t last_transition; 239 | displayoutput.servoAttach(); 240 | for (uint16_t current_time_in_minutes = colorstate.transition_time[NIGHT_END]; current_time_in_minutes < colorstate.transition_time[NIGHT_MID]; current_time_in_minutes = current_time_in_minutes + 5) { 241 | colorstate.updateColors(current_time_in_minutes); 242 | 243 | if (last_transition != colorstate.next_transition && (colorstate.next_transition == RISE_PEAK || colorstate.next_transition == NIGHT_MID)) { 244 | updateScreen(colorstate.daytime, moon_phase_precentage); 245 | } 246 | 247 | 248 | displayoutput.servoDetach(); 249 | displayoutput.stripRGB(colorstate.current_colors); 250 | 251 | displayoutput.servoMoveTo(colorstate.current_angle, 28); 252 | displayoutput.servoAttach(); 253 | last_transition = colorstate.next_transition; 254 | } 255 | for (uint16_t current_time_in_minutes = 1; current_time_in_minutes < colorstate.transition_time[NIGHT_END]; current_time_in_minutes = current_time_in_minutes + 5) { 256 | colorstate.updateColors(current_time_in_minutes); 257 | 258 | if (last_transition != colorstate.next_transition && (colorstate.next_transition == RISE_PEAK || colorstate.next_transition == NIGHT_MID)) { 259 | updateScreen(colorstate.daytime, moon_phase_precentage); 260 | } 261 | 262 | 263 | displayoutput.servoDetach(); 264 | displayoutput.stripRGB(colorstate.current_colors); 265 | 266 | displayoutput.servoMoveTo(colorstate.current_angle, 28); 267 | displayoutput.servoAttach(); 268 | last_transition = colorstate.next_transition; 269 | } 270 | displayoutput.servoDetach(); 271 | } 272 | 273 | void fastMoonCycle() { 274 | for (uint8_t phase = 0; phase <= 100; phase = phase + 1) { 275 | displayoutput.updateMoon(phase); 276 | delay(250); 277 | } 278 | } 279 | void loop() 280 | { 281 | //Settings Menu 282 | if (button_left.getSingleDebouncedPress()) { 283 | 284 | displayoutput.servoMoveTo(90, 500); 285 | settingsMenu(); 286 | updateColorDisplay(FORCE); 287 | } 288 | //Display Time, Date, SuneRise, Sunset 289 | if (button_up.getSingleDebouncedPress()) { 290 | displayTime(); 291 | updateColorDisplay(FORCE); 292 | } 293 | 294 | if (button_down.getSingleDebouncedPress()) { 295 | fastDayCycle(); 296 | // fastMoonCycle(); 297 | updateColorDisplay(FORCE); 298 | 299 | } 300 | 301 | if ( millis() > last_update + UPDATE_INTERVAL) { 302 | updateColorDisplay(); 303 | last_update = millis(); 304 | } 305 | 306 | if ( millis() > last_tft_reset + TFT_RESET_INTERVAL) { 307 | displayoutput.tft.display(false); 308 | delay(1000); 309 | displayoutput.tft.display(true); 310 | last_tft_reset = millis(); 311 | } 312 | 313 | 314 | ; 315 | } 316 | --------------------------------------------------------------------------------