#include #include #include #include #include // Define the array of leds CRGB leds[LED_COUNT]; Timezone CopenhagenTime; // Used for hue shift each minute // https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors int hue = 0; int max_brightness = 255; // Hold state if an led is on, to check if it should be faded on / off, or if it already is in desired state bool on_state[SEGMENT_COUNT * DIGIT_COUNT] = {false}; // Segment naming scheme, corresponds to LED strip soldering order, standardized on Wikipeadia // https://en.wikipedia.org/wiki/Seven-segment_display#/media/File:7_Segment_Display_with_Labeled_Segments.svg // Last bit unused typedef enum : uint8_t { seg_a = 1<<0, // 00000001 seg_b = 1<<1, // 00000010 seg_c = 1<<2, // 00000100 seg_d = 1<<3, // ... seg_e = 1<<4, seg_f = 1<<5, seg_g = 1<<6 } segments; // uint8_t symbols[] = { /* 0 */ seg_a | seg_b | seg_c | seg_d | seg_e | seg_f, // 00111111 /* 1 */ seg_b | seg_c, // 00000110 /* 2 */ seg_a | seg_b | seg_g | seg_e | seg_d, // ... /* 3 */ seg_a | seg_b | seg_g | seg_c | seg_d, /* 4 */ seg_f | seg_g | seg_c | seg_b, /* 5 */ seg_a | seg_f | seg_g | seg_c | seg_d, /* 6 */ seg_a | seg_f | seg_g | seg_c | seg_d | seg_e, /* 7 */ seg_a | seg_b | seg_c, /* 8 */ seg_a | seg_b | seg_c | seg_d | seg_e | seg_f | seg_g, /* 9 */ seg_g | seg_f | seg_a | seg_b | seg_c | seg_d, /* ° */ seg_a | seg_b | seg_g | seg_f, /* C */ seg_a | seg_f | seg_e | seg_d }; // Which segments should change color in this frame of the animation uint8_t spin_animation[] = { seg_a, seg_b, seg_c, seg_d, seg_e, seg_f, }; uint8_t dual_spin_animation[] = { seg_a | seg_d, seg_b | seg_e, seg_c | seg_f, seg_d | seg_a, seg_e | seg_b, seg_f | seg_c, }; // Fades all at once void display_symbols(uint8_t *symbols_mask) { // Loop over each brightness state and apply it to all elements if it's not already the desired state for (int brightness = 0; brightness <= max_brightness; brightness ++) { for (uint8_t digit_index = 0; digit_index < DIGIT_COUNT; digit_index++) { // Counts up by bitshifting uint8_t seg_counter = 1; for (int segment_index = 0; segment_index < SEGMENT_COUNT; segment_index++) { int digit_offset = digit_index * SEGMENT_COUNT; // LEDS can be offsed by START_INDEX, state array isn't int led_raw_index = digit_offset + segment_index; int led_index = START_OFFSET + led_raw_index; // Get this LEDs previous state to see if it should be changed bool previously_on = on_state[led_raw_index]; // Compare the current segment bit with the mask for the symbol bool turn_on = symbols_mask[digit_index] & seg_counter; if (turn_on) { // Turn on LED if(previously_on){ // Previously on, allow hue change leds[led_index] = CHSV(hue, SATURATION, max_brightness); } else { // Previously off, gradually increase brightness with loop e.g. 0-255 leds[led_index] = CHSV(hue, SATURATION, brightness); } } else { // Turn off LED if(previously_on) { // Previously on, gradually decrease brightness with a negative of the loop e.g. 255-0 leds[led_index] = CHSV(hue, SATURATION, max_brightness - brightness); } } // Last loop itteration, save current LED state if (brightness == max_brightness) { on_state[led_raw_index] = turn_on; } // Increment segment counter used with bit mask comparison by bitshifting seg_counter = seg_counter<<1; } } // Push changes of entire LED array to LEDs FastLED.show(); delay(2); } } // Show a four digit number, by fading on LEDs void display_number(int number) { // Build up entire bit mask, so all can be faded on at once uint8_t symbols_mask[DIGIT_COUNT]; // Start from the last digit for (int digit_index = DIGIT_COUNT - 1; digit_index >= 0; digit_index--) { // Get only last digit symbols_mask[digit_index] = symbols[number % 10]; // Pop last digit number /= 10; } display_symbols(symbols_mask); } // Instantly changes. Binary, no fade void display_symbol(int digit_index, uint8_t symbol) { // Counts up by bitshifting uint8_t seg_counter = 1; for (int segment_index = 0; segment_index < SEGMENT_COUNT; segment_index++) { int offset = digit_index * SEGMENT_COUNT; int led_index = START_OFFSET + offset + segment_index; // Compare the current segment bit with the bit mask for the symbol if (symbol & seg_counter) { // Turn on LED leds[led_index] = CHSV(hue, SATURATION, max_brightness); FastLED.show(); } // Increment segment counter used with bit mask comparison by bitshifting seg_counter = seg_counter<<1; } } // Change hue of 0-7 segments per frame each digit displays the same void play_animation(uint8_t *animation, int animation_size, int segment_delay) { for (int i = 0; i < animation_size; i++){ for (int digit_index = 0; digit_index < 4; digit_index++) { display_symbol(digit_index, animation[i]); } hue += 16; // Increment HSV hue a lot for rainbow vomit FastLED.show(); delay(segment_delay); } } // Check the time, and display it on the clock void update_clock() { // Set hue to a value derived from the time of day, doing 1 loop per day int hour = CopenhagenTime.dateTime("G").toInt(); int minute = CopenhagenTime.dateTime("i").toInt(); int minute_of_day = (hour * 60) + minute; hue = map(minute_of_day, 0, 1440, 0, 255); Serial.println("Minute of day:"); Serial.println(minute_of_day); Serial.println("Hue:"); Serial.println(hue); // Format the time as a 4-digit integer int timeInt = CopenhagenTime.dateTime("Hi").toInt(); // Sadly string format display_number(timeInt); //hue++; // Increment HSV hue } void setup() { // Setup LEDs FastLED.addLeds(leds, LED_COUNT).setCorrection( TypicalLEDStrip ); FastLED.addLeds(leds, LED_COUNT); // GRB ordering is assumed // Turn off all LEDs FastLED.clear(); Serial.begin(9600); setServer(NTP_SERVER); WiFi.setHostname(HOSTNAME); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting"); while (WiFi.status() != WL_CONNECTED) { // Play animation untill connected to WiFi, or animation timeout is reached // Will continue attempting to connect to WiFi if (millis() < WIFI_CONN_ANI_TIMEOUT) { play_animation(spin_animation, sizeof(spin_animation), 100); } else { // Turn off all LEDs FastLED.clear(); } } delay(100); hue = 0; // Turn off all LEDs FastLED.clear(); Serial.println(); Serial.print("Connected, IP address: "); Serial.println(WiFi.localIP()); waitForSync(); Serial.println("UTC: " + UTC.dateTime()); CopenhagenTime.setLocation("Europe/Copenhagen"); Serial.println("Copenhagen time: " + CopenhagenTime.dateTime()); update_clock(); } /* // Test each segment int i = 0; void loop() { leds[i] = CRGB::Red; FastLED.show(); delay(500); leds[i] = CRGB::Black; FastLED.show(); delay(500); i++; if (i > LED_COUNT - 1) { i = 0; } } */ void loop() { // Only update on each hole minute to avoid flickering int seconds = CopenhagenTime.dateTime("s").toInt(); int minutes = CopenhagenTime.dateTime("i").toInt(); // Play animation 5 seconds before hour change if(minutes == 59 && seconds >= 55) { play_animation(dual_spin_animation, sizeof(spin_animation), 50); FastLED.clear(); } // Do nothing untill minute change if(seconds != 0) { delay(10); return; } // Minute changed update_clock(); // Wait untill minute change check won't be triggered again delay(1000); }