#include "led_task.h" #include #include #include "leds.h" #include "stm32l_rtc.h" #include "stm32l_eeprom.h" #include "remote.h" #define STEPSIZE 500 #define STEPTIME 150 #define FADETIME 1000 #define MINBRIGHT 2000 #define ACKTIME 100 static void increase_component(int *c, int *o1, int *o2) { *c += STEPSIZE; if (*c > LED_MAX) { *o1 -= (*c - LED_MAX); *o2 -= (*c - LED_MAX); *c = LED_MAX; } } /* Change the color without changing brightness */ static void set_color(int *r, int *g, int *b, int tr, int tg, int tb) { int brightness = *r + *g + *b; int div = tr + tg + tb; if (div == 0) div = 1; *r = brightness * tr / div; *g = brightness * tg / div; *b = brightness * tb / div; int max = *r; if (*g > max) max = *g; if (*b > max) max = *b; if (max > LED_MAX) { *r = *r * LED_MAX / max; *g = *g * LED_MAX / max; *b = *b * LED_MAX / max; } } /* Adjust the brightness without changing the color */ static void adjust_brightness(int *r, int *g, int *b, int delta) { int tr = *r, tg = *g, tb = *b; if (tr + tg + tb == 0) tr = tg = tb = 1; *r += delta / 3; *g += delta / 3; *b += delta / 3; set_color(r, g, b, tr, tg, tb); } /* Handle the on/off buttons */ static bool handle_on_off(remote_button_t button) { switch (button) { case RM_OFF: fade_leds(0, 0, 0, FADETIME); break; case RM_ON: fade_leds(10000, 7000, 4000, FADETIME); break; default: return false; } return true; } /* Handle the buttons that adjust current color */ static bool handle_color_adjust(remote_button_t button) { int r, g, b; get_leds(&r, &g, &b); switch (button) { case RM_BRIGHTUP: if (r + g + b == 0) { /* If leds were off, set initial color to white */ r = g = b = STEPSIZE / 3; set_color(&r, &g, &b, 10000, 7000, 4000); } else { adjust_brightness(&r, &g, &b, STEPSIZE); } break; case RM_BRIGHTDOWN: adjust_brightness(&r, &g, &b, -STEPSIZE); break; case RM_RED: increase_component(&r, &g, &b); break; case RM_GREEN: increase_component(&g, &r, &b); break; case RM_BLUE: increase_component(&b, &r, &g); break; default: return false; } fade_leds(r, g, b, STEPTIME); return true; } /* Handle the buttons that change to a different color */ static bool handle_color_change(remote_button_t button) { int r, g, b; get_leds(&r, &g, &b); switch (button) { case RM_WHITE: set_color(&r, &g, &b, 10000, 7000, 4000); break; case RM_1: set_color(&r, &g, &b, 10000, 4000, 0); break; case RM_2: set_color(&r, &g, &b, 8000, 10000, 2000); break; case RM_3: set_color(&r, &g, &b, 4000, 4000, 10000); break; case RM_4: set_color(&r, &g, &b, 10000, 3000, 0); break; case RM_5: set_color(&r, &g, &b, 8000, 10000, 4000); break; case RM_6: set_color(&r, &g, &b, 8000, 4000, 10000); break; case RM_7: set_color(&r, &g, &b, 10000, 5000, 0); break; case RM_8: set_color(&r, &g, &b, 8000, 10000, 6000); break; case RM_9: set_color(&r, &g, &b, 10000, 4000, 10000); break; case RM_WEND: set_color(&r, &g, &b, 10000, 6000, 0); break; case RM_0: set_color(&r, &g, &b, 6000, 10000, 8000); break; case RM_WDAY: set_color(&r, &g, &b, 10000, 4000, 5000); break; default: return false; } fade_leds(r, g, b, FADETIME); return true; } /* Adjust the colors to random direction based on system time */ static void fade_colors(int *r, int *g, int *b, int steps_per_sec) { int time_ms = chTimeNow() * 1000 / CH_FREQUENCY; int phase = time_ms * steps_per_sec / LED_MAX / 1000; phase = ((phase * 31337) % 1213) & 63; if (phase & 1) *r += steps_per_sec / 10; if (phase & 2) *g += steps_per_sec / 10; if (phase & 4) *b += steps_per_sec / 10; if (phase & 8) *r -= steps_per_sec / 10; if (phase & 16) *g -= steps_per_sec / 10; if (phase & 32) *b -= steps_per_sec / 10; fade_leds(*r, *g, *b, 100); } /* Handle the special buttons: * strobe: Turn on light until button is released. * fade: Fade slowly between colors until another button is pressed. */ static bool handle_special(remote_button_t button) { int r, g, b; get_leds(&r, &g, &b); switch (button) { case RM_STROBE: fade_leds(10000, 10000, 10000, FADETIME); while (g_remote_button == RM_STROBE) chThdSleepMilliseconds(100); fade_leds(r, g, b, FADETIME); break; case RM_FADE: while (g_remote_button == RM_FADE || g_remote_button == RM_IDLE) fade_colors(&r, &g, &b, 200); break; default: return false; } return true; } /* Blink the lights to acknowledge an input */ static void ack() { int r, g, b; get_leds(&r, &g, &b); if (r + g + b < MINBRIGHT) fade_leds(MINBRIGHT, MINBRIGHT, MINBRIGHT, ACKTIME); else fade_leds(0, 0, 0, ACKTIME); fade_leds(r, g, b, ACKTIME); while (g_remote_button != RM_IDLE) chThdSleepMilliseconds(50); chThdSleepMilliseconds(400); } /* Read a number made of multiple digits. * Returns -1 on abort (off button). */ static int read_number(int digits) { int result = 0; int maxwait = 10000; while (digits) { int digit; remote_button_t button = g_remote_button; switch (button) { case RM_1: digit = 1; break; case RM_2: digit = 2; break; case RM_3: digit = 3; break; case RM_4: digit = 4; break; case RM_5: digit = 5; break; case RM_6: digit = 6; break; case RM_7: digit = 7; break; case RM_8: digit = 8; break; case RM_9: digit = 9; break; case RM_0: digit = 0; break; default: digit = -1; break; } if (digit >= 0) { result = result * 10 + digit; digits--; ack(); while (g_remote_button != RM_IDLE) chThdSleepMilliseconds(50); } else if (button == RM_OFF) { fade_leds(0, 0, 0, FADETIME); return -1; } else { chThdSleepMilliseconds(50); // Wait for some other button.. } if (maxwait-- <= 0) { return -1; } } return result; } // Show a red or green color briefly to inform user of success or failure static void show_status(bool success) { int r, g, b; get_leds(&r, &g, &b); if (success) fade_leds(0, LED_MAX, 0, ACKTIME); else fade_leds(LED_MAX, 0, 0, ACKTIME); chThdSleepMilliseconds(500); fade_leds(r, g, b, ACKTIME); } /* Set the current time (RTC time) */ /* Expects user to type 5 digits: hours, minutes, day of week */ static void handle_rtcset() { int hour = read_number(2); if (hour < 0) return; int minute = read_number(2); if (minute < 0) return; int dow = read_number(1); if (dow < 0) return; if (hour <= 23 && minute <= 59 && dow >= 1 && dow <= 7) { // All good time_t time = {}; time.hour = hour; time.minute = minute; time.second = 0; time.day_of_week = dow; set_rtc(&time); show_status(true); } else { // Something wrong, flash red light show_status(false); } } /* Handle the setting of a wakeup time register in eeprom. */ static void handle_wakeupset(int index) { int time = read_number(4); if (time < 0) return; if (time / 100 <= 23 && time % 100 <= 59) { write_eeprom(index, time); show_status(true); } else { show_status(false); } } static bool handle_setup(remote_button_t button) { if (button == RM_SETUP) { ack(); // Read the setup action int maxwait = 10000; button = g_remote_button; while (button == RM_IDLE && maxwait--) { chThdSleepMilliseconds(50); button = g_remote_button; } switch (button) { case RM_0: ack(); handle_rtcset(); break; case RM_WDAY: ack(); handle_wakeupset(IDX_WDAY_WAKEUP); break; case RM_WEND: ack(); handle_wakeupset(IDX_WEND_WAKEUP); break; case RM_SMOOTH: ack(); handle_wakeupset(IDX_WAKEUP_LEN); break; default: break; } return true; } else { return false; } } typedef struct { int time; int r, g, b; } seq_color_t; #define SUNRISE_SEQUENCE_LEN 10 #define SUNRISE_SEQUENCE_NOMINAL_TIME 3600 static const seq_color_t c_sunrise_sequence[SUNRISE_SEQUENCE_LEN] = { {-3600, 0, 0, 0}, {-3000, 0, 0, 200}, // Dark blue {-2400, 500, 200, 500}, {-1800, 1000, 200, 500}, // Sun begins to rise {-1500, 2000, 1000, 500}, {-1200, 5000, 3000, 500}, // Yellowish { -900, 6000, 4000, 2000}, // White { 0, 10000, 7000, 4000}, // Bright white { 3300, 10000, 7000, 4000}, // Begin to fade off { 3600, 0, 0, 0} }; /* Get number of seconds remaining until given time. * Returns negative values if target happened less than 12 hours ago. */ static int time_delta(const time_t *target) { time_t time; get_rtc(&time); // Convert everything to seconds since midnight int timenow = time.hour * 3600 + time.minute * 60 + time.second; int timetarget = target->hour * 3600 + target->minute * 60 + target->second; // How much time until wakeup? int timeleft = timetarget - timenow; if (timeleft < -43200) timeleft += 86400; return timeleft; } /* Play a color sequence, referred to given time and scaled to given length */ static void play_sequence(const time_t *zero_time, const seq_color_t *sequence, int count, int nominal_time, int scaled_time) { // Any button press will abort the sequence. int i = 1; while (i < count) { int r, g, b; int t = -time_delta(zero_time) * nominal_time / scaled_time; seq_color_t c1 = sequence[i - 1]; seq_color_t c2 = sequence[i]; r = (c2.r - c1.r) * (t - c1.time) / (c2.time - c1.time) + c1.r; g = (c2.g - c1.g) * (t - c1.time) / (c2.time - c1.time) + c1.g; b = (c2.b - c1.b) * (t - c1.time) / (c2.time - c1.time) + c1.b; fade_leds(r, g, b, 1000); if (g_remote_button != RM_IDLE) return; if (sequence[i].time < t) i++; } } /* Convert a decimal time, such as "0830" for 8:30, to number of seconds. */ static int decimal_to_sec(int value) { return (value / 100) * 3600 + (value % 100) * 60; } /* Check if it is time to wake up and do the wakeup color sequence. */ static void handle_wakeup() { /* Get the desired length of the wakeup sequence */ int wakelen = decimal_to_sec(read_eeprom(IDX_WAKEUP_LEN)); if (wakelen == 0) return; // Alarm is disabled /* Figure out the wakeup time for today */ time_t time; get_rtc(&time); int waketime; if (time.day_of_week >= 6) waketime = read_eeprom(IDX_WEND_WAKEUP); else waketime = read_eeprom(IDX_WDAY_WAKEUP); time.hour = waketime / 100; time.minute = waketime % 100; time.second = 0; /* Check if it is time to start the sequence */ int timeleft = time_delta(&time); if (timeleft > wakelen) return; // No need to start yet. if (timeleft < wakelen - 10) return; // Wakeup has been dismissed already /* Maybe the light has been forgotten on? */ { int r, g, b; get_leds(&r, &g, &b); if (r + g + b >= MINBRIGHT) return; // Ok, people are probably awake already. else if (r + g + b != 0) fade_leds(0, 0, 0, 30000); } /* Start the color sequence */ play_sequence(&time, c_sunrise_sequence, SUNRISE_SEQUENCE_LEN, SUNRISE_SEQUENCE_NOMINAL_TIME, wakelen); } /* Fade out the light in sunset simulation when RM_SMOOTH is pressed. */ static bool handle_sunset(remote_button_t button) { if (button == RM_SMOOTH) { ack(); int r, g, b; get_leds(&r, &g, &b); while ((g_remote_button == RM_SMOOTH || g_remote_button == RM_IDLE) && (r + g + b > 0)) { /* Decrease blue and green channel at 1.5x the speed of red channel. * Speed is 15 minutes from the full brightness, less from a dim starting color. */ r -= LED_MAX / 900; g -= LED_MAX / 600; b -= LED_MAX / 600; if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; fade_leds(r, g, b, 1000); } return true; } else { return false; } } static Thread *ledThread = 0; static WORKING_AREA(ledThread_wa, 512); static msg_t led_task(void *arg) { while (true) { remote_button_t button = g_remote_button; if (button != RM_IDLE) { handle_on_off(button); handle_color_adjust(button); handle_color_change(button); handle_special(button); handle_setup(button); handle_sunset(button); } else { handle_wakeup(); } chThdSleepMilliseconds(10); } return 0; } void start_led_task() { ledThread = chThdCreateStatic(ledThread_wa, sizeof(ledThread_wa), NORMALPRIO - 10, led_task, NULL); }