diff --git a/firmware/components/led-manager/CMakeLists.txt b/firmware/components/led-manager/CMakeLists.txt index 0ee439c..fe9d4f9 100644 --- a/firmware/components/led-manager/CMakeLists.txt +++ b/firmware/components/led-manager/CMakeLists.txt @@ -1,4 +1,5 @@ idf_component_register(SRCS + src/color.c src/led_status.c src/led_strip_ws2812.c INCLUDE_DIRS "include" diff --git a/firmware/components/led-manager/include/color.h b/firmware/components/led-manager/include/color.h index dae761d..85b2f99 100644 --- a/firmware/components/led-manager/include/color.h +++ b/firmware/components/led-manager/include/color.h @@ -9,4 +9,14 @@ typedef struct uint8_t blue; } rgb_t; -void interpolate_color(const rgb_t start_color, const rgb_t end_color, float fraction, rgb_t *out_color); +typedef struct +{ + uint8_t h; + uint8_t s; + uint8_t v; +} hsv_t; + +rgb_t interpolate_color_rgb(rgb_t start, rgb_t end, float factor); +rgb_t interpolate_color_hsv(rgb_t start, rgb_t end, float factor); +hsv_t rgb_to_hsv(rgb_t rgb); +rgb_t hsv_to_rgb(hsv_t hsv); diff --git a/firmware/components/led-manager/src/color.c b/firmware/components/led-manager/src/color.c index 6bbaecb..28ae1ac 100644 --- a/firmware/components/led-manager/src/color.c +++ b/firmware/components/led-manager/src/color.c @@ -1,8 +1,183 @@ #include "color.h" +#include -void interpolate_color(const rgb_t start_color, const rgb_t end_color, float fraction, rgb_t *out_color) +rgb_t interpolate_color_rgb(rgb_t start, rgb_t end, float factor) { - out_color->r = start_color.r + (end_color.r - start_color.r) * fraction; - out_color->g = start_color.g + (end_color.g - start_color.g) * fraction; - out_color->b = start_color.b + (end_color.b - start_color.b) * fraction; -} \ No newline at end of file + // Clamp factor to [0, 1] + if (factor > 1.0f) + factor = 1.0f; + if (factor < 0.0f) + factor = 0.0f; + + rgb_t result; + result.red = (uint8_t)(start.red + (end.red - start.red) * factor); + result.green = (uint8_t)(start.green + (end.green - start.green) * factor); + result.blue = (uint8_t)(start.blue + (end.blue - start.blue) * factor); + + return result; +} + +rgb_t interpolate_color_hsv(rgb_t start, rgb_t end, float factor) +{ + // Clamp factor to [0, 1] + if (factor > 1.0f) + factor = 1.0f; + if (factor < 0.0f) + factor = 0.0f; + + // Convert RGB to HSV + hsv_t start_hsv = rgb_to_hsv(start); + hsv_t end_hsv = rgb_to_hsv(end); + + // Handle hue interpolation carefully (circular) + double h1 = start_hsv.h; + double h2 = end_hsv.h; + double diff = h2 - h1; + + if (diff > 180.0) + { + h1 += 360.0; + } + else if (diff < -180.0) + { + h2 += 360.0; + } + + // Interpolate HSV values + hsv_t interpolated_hsv; + interpolated_hsv.h = fmod(h1 + (h2 - h1) * factor, 360.0); + if (interpolated_hsv.h < 0) + { + interpolated_hsv.h += 360.0; + } + interpolated_hsv.s = start_hsv.s + (end_hsv.s - start_hsv.s) * factor; + interpolated_hsv.v = start_hsv.v + (end_hsv.v - start_hsv.v) * factor; + + // Convert back to RGB + return hsv_to_rgb(interpolated_hsv); +} + +hsv_t rgb_to_hsv(rgb_t rgb) +{ + hsv_t hsv; + uint8_t max = rgb.red; + uint8_t min = rgb.red; + + if (rgb.green > max) + max = rgb.green; + if (rgb.blue > max) + max = rgb.blue; + if (rgb.green < min) + min = rgb.green; + if (rgb.blue < min) + min = rgb.blue; + + uint8_t delta = max - min; + + // Value berechnen + hsv.v = max; + + // Saturation berechnen + if (max != 0) + { + hsv.s = (delta * 255) / max; + } + else + { + // Schwarz (r = g = b = 0) + hsv.s = 0; + hsv.h = 0; + return hsv; + } + + // Hue berechnen + if (delta != 0) + { + int16_t hue; + + if (rgb.red == max) + { + // Zwischen Gelb und Magenta + hue = ((int16_t)(rgb.green - rgb.blue) * 30) / delta; + if (hue < 0) + hue += 180; + } + else if (rgb.green == max) + { + // Zwischen Cyan und Gelb + hue = 60 + ((int16_t)(rgb.blue - rgb.red) * 30) / delta; + } + else + { + // Zwischen Magenta und Cyan + hue = 120 + ((int16_t)(rgb.red - rgb.green) * 30) / delta; + } + + hsv.h = (uint8_t)hue; + } + else + { + // Graustufe + hsv.h = 0; + } + + return hsv; +} + +rgb_t hsv_to_rgb(hsv_t hsv) +{ + rgb_t rgb; + + if (hsv.s == 0) + { + // Graustufe + rgb.red = hsv.v; + rgb.green = hsv.v; + rgb.blue = hsv.v; + } + else + { + uint16_t region = hsv.h / 30; + uint16_t remainder = (hsv.h - (region * 30)) * 6; + + uint8_t p = (hsv.v * (255 - hsv.s)) / 255; + uint8_t q = (hsv.v * (255 - ((hsv.s * remainder) / 180))) / 255; + uint8_t t = (hsv.v * (255 - ((hsv.s * (180 - remainder)) / 180))) / 255; + + switch (region) + { + case 0: + rgb.red = hsv.v; + rgb.green = t; + rgb.blue = p; + break; + case 1: + rgb.red = q; + rgb.green = hsv.v; + rgb.blue = p; + break; + case 2: + rgb.red = p; + rgb.green = hsv.v; + rgb.blue = t; + break; + case 3: + rgb.red = p; + rgb.green = q; + rgb.blue = hsv.v; + break; + case 4: + rgb.red = t; + rgb.green = p; + rgb.blue = hsv.v; + break; + default: // case 5: + rgb.red = hsv.v; + rgb.green = p; + rgb.blue = q; + break; + } + } + + return rgb; +} diff --git a/firmware/components/simulator/include/simulator.h b/firmware/components/simulator/include/simulator.h index 05585d6..36fa0f5 100644 --- a/firmware/components/simulator/include/simulator.h +++ b/firmware/components/simulator/include/simulator.h @@ -12,7 +12,8 @@ typedef struct __BEGIN_DECLS char *get_time(void); -esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue); +esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue, uint8_t white, + uint8_t brightness, uint8_t saturation); void cleanup_light_items(void); void start_simulate_day(void); void start_simulate_night(void); diff --git a/firmware/components/simulator/src/simulator.c b/firmware/components/simulator/src/simulator.c index 68ee606..90d52f9 100644 --- a/firmware/components/simulator/src/simulator.c +++ b/firmware/components/simulator/src/simulator.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -37,12 +38,36 @@ typedef struct light_item_node_t static light_item_node_t *head = NULL; static light_item_node_t *tail = NULL; +// Interpolation mode selection +typedef enum +{ + INTERPOLATION_RGB, + INTERPOLATION_HSV +} interpolation_mode_t; + +// You can change this to test different interpolation methods +static const interpolation_mode_t interpolation_mode = INTERPOLATION_RGB; + char *get_time(void) { return time; } -esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue) +// Main interpolation function that selects the appropriate method +static rgb_t interpolate_color(rgb_t start, rgb_t end, float factor) +{ + switch (interpolation_mode) + { + case INTERPOLATION_RGB: + return interpolate_color_rgb(start, end, factor); + case INTERPOLATION_HSV: + default: + return interpolate_color_hsv(start, end, factor); + } +} + +esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue, uint8_t white, + uint8_t brightness, uint8_t saturation) { // Allocate memory for a new node in PSRAM. light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_SPIRAM); @@ -52,11 +77,17 @@ esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t return ESP_FAIL; } + rgb_t color = {.red = red, .green = green, .blue = blue}; + hsv_t hsv = rgb_to_hsv(color); + hsv.v = brightness; + hsv.s = saturation; + rgb_t adjusted_color = hsv_to_rgb(hsv); + // Initialize the data of the new node. memcpy(new_node->time, time, sizeof(new_node->time)); - new_node->red = red; - new_node->green = green; - new_node->blue = blue; + new_node->red = adjusted_color.red; + new_node->green = adjusted_color.green; + new_node->blue = adjusted_color.blue; new_node->next = NULL; // Append the new node to the end of the list. @@ -131,16 +162,62 @@ static light_item_node_t *find_best_light_item_for_time(int hhmm) if (best_item == NULL) { - ESP_LOGW(TAG, "No suitable light item found for time up to %04d", hhmm); - } - else - { - ESP_LOGD(TAG, "Best light item for time %04d is %s", hhmm, best_item->time); + // If no item is found for the given time (e.g., before the first item of the day), + // find the last item of the previous day. + best_time = -1; + current = head; + while (current != NULL) + { + int current_time = atoi(current->time); + if (current_time > best_time) + { + best_time = current_time; + best_item = current; + } + current = current->next; + } } return best_item; } +static light_item_node_t *find_next_light_item_for_time(int hhmm) +{ + light_item_node_t *current = head; + light_item_node_t *next_item = NULL; + int next_time = 9999; // Initialize with a value larger than any possible time + + // First pass: find the soonest time after hhmm + while (current != NULL) + { + int current_time = atoi(current->time); + if (current_time > hhmm && current_time < next_time) + { + next_time = current_time; + next_item = current; + } + current = current->next; + } + + // If no item is found for the rest of the day, wrap around to the beginning of the next day + if (next_item == NULL) + { + current = head; + next_time = 9999; + while (current != NULL) + { + int current_time = atoi(current->time); + if (current_time < next_time) + { + next_time = current_time; + next_item = current; + } + current = current->next; + } + } + return next_item; +} + void start_simulate_day(void) { initialize_light_items(); @@ -186,7 +263,6 @@ void simulate_cycle(void *args) cycle_duration_minutes, delay_ms); int current_minute_of_day = 0; - light_item_node_t *last_item = NULL; while (1) { @@ -196,15 +272,47 @@ void simulate_cycle(void *args) time = time_to_string(hhmm); light_item_node_t *current_item = find_best_light_item_for_time(hhmm); + light_item_node_t *next_item = find_next_light_item_for_time(hhmm); - if (current_item != NULL && current_item != last_item) + if (current_item != NULL && next_item != NULL) { - ESP_LOGI(TAG, "Simulating time: %02d:%02d -> Closest schedule is %s. R:%d, G:%d, B:%d", hours, minutes, - current_item->time, current_item->red, current_item->green, current_item->blue); + int current_item_time_min = (atoi(current_item->time) / 100) * 60 + (atoi(current_item->time) % 100); + int next_item_time_min = (atoi(next_item->time) / 100) * 60 + (atoi(next_item->time) % 100); + + if (next_item_time_min < current_item_time_min) + { + next_item_time_min += total_minutes_in_day; + } + + int minutes_since_current_item_start = current_minute_of_day - current_item_time_min; + if (minutes_since_current_item_start < 0) + { + minutes_since_current_item_start += total_minutes_in_day; + } + + int interval_duration = next_item_time_min - current_item_time_min; + if (interval_duration == 0) + { + interval_duration = 1; + } + + float interpolation_factor = (float)minutes_since_current_item_start / (float)interval_duration; + + // Prepare colors for interpolation + rgb_t start_rgb = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue}; + rgb_t end_rgb = {.red = next_item->red, .green = next_item->green, .blue = next_item->blue}; + + // Use the interpolation function + rgb_t final_rgb = interpolate_color(start_rgb, end_rgb, interpolation_factor); + + led_strip_update(LED_STATE_SIMULATION, final_rgb); + } + else if (current_item != NULL) + { + // No next item, just use current led_strip_update( LED_STATE_SIMULATION, (rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue}); - last_item = current_item; } vTaskDelay(pdMS_TO_TICKS(delay_ms)); diff --git a/firmware/components/simulator/src/storage.c b/firmware/components/simulator/src/storage.c index 8cf3939..5078e81 100644 --- a/firmware/components/simulator/src/storage.c +++ b/firmware/components/simulator/src/storage.c @@ -11,10 +11,10 @@ static const char *TAG = "storage"; void initialize_storage() { esp_vfs_spiffs_conf_t conf = { - .base_path = "/spiffs", // Der Basispfad, unter dem das Dateisystem gemountet wird - .partition_label = NULL, // NULL, um die erste gefundene SPIFFS-Partition zu verwenden - .max_files = 5, // Maximale Anzahl gleichzeitig geöffneter Dateien - .format_if_mount_failed = false // Partition formatieren, wenn das Mounten fehlschlägt + .base_path = "/spiffs", + .partition_label = NULL, + .max_files = 5, + .format_if_mount_failed = false, }; esp_err_t ret = esp_vfs_spiffs_register(&conf); @@ -33,7 +33,7 @@ void initialize_storage() { ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); } - return; // Oder entsprechende Fehlerbehandlung + return; } } @@ -47,24 +47,30 @@ void load_file(const char *filename) return; } - char line[128]; // Puffer für eine Zeile, vergrößert für mehr Sicherheit + char line[128]; + uint8_t line_number = 0; while (fgets(line, sizeof(line), f)) { - // Entferne möglichen Zeilenumbruch am Ende char *pos = strchr(line, '\n'); if (pos) { *pos = '\0'; } - char time[5] = {0}; // 4 Zeichen + Nullterminator - int red, green, blue; + char time[10] = {0}; + int red, green, blue, white, brightness, saturation; - // Parse die Zeile im Format "HHMM,R,G,B" - int items_scanned = sscanf(line, "%4[^,],%d,%d,%d", time, &red, &green, &blue); - if (items_scanned == 4) + int items_scanned = sscanf(line, "%d,%d,%d,%d,%d,%d", &red, &green, &blue, &white, &brightness, &saturation); + if (items_scanned == 6) { - add_light_item(time, (uint8_t)red, (uint8_t)green, (uint8_t)blue); + int total_minutes = line_number * 30; + int hours = total_minutes / 60; + int minutes = total_minutes % 60; + + snprintf(time, sizeof(time), "%02d%02d", hours, minutes); + + add_light_item(time, red, green, blue, white, brightness, saturation); + line_number++; } else { @@ -74,4 +80,4 @@ void load_file(const char *filename) fclose(f); ESP_LOGI(TAG, "Finished loading file."); -} \ No newline at end of file +} diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index bd98993..eca0a79 100755 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -21,4 +21,4 @@ idf_component_register(SRCS rmaker_common ) -spiffs_create_partition_image(storage ../spiffs_image FLASH_IN_PROJECT) +spiffs_create_partition_image(storage ../storage FLASH_IN_PROJECT) diff --git a/firmware/spiffs_image/schema_01.csv b/firmware/spiffs_image/schema_01.csv deleted file mode 100644 index f6bf6ef..0000000 --- a/firmware/spiffs_image/schema_01.csv +++ /dev/null @@ -1,48 +0,0 @@ -0000,25,25,112 -0030,25,25,112 -0100,25,25,112 -0130,25,25,112 -0200,25,25,112 -0230,25,25,112 -0300,25,25,112 -0330,25,25,112 -0400,25,25,112 -0430,140,25,112 -0500,255,130,112 -0530,255,155,112 -0600,255,177,115 -0630,255,200,135 -0700,255,219,170 -0730,255,234,205 -0800,255,249,240 -0830,255,249,250 -0900,239,245,255 -0930,224,240,255 -1000,215,235,255 -1030,212,234,255 -1100,210,233,255 -1130,208,232,255 -1200,207,231,255 -1230,205,230,255 -1300,204,229,255 -1330,204,229,255 -1400,206,230,255 -1430,208,231,255 -1500,213,232,255 -1530,219,234,255 -1600,229,239,255 -1630,236,246,255 -1700,255,252,251 -1730,255,243,236 -1800,255,225,202 -1830,255,203,174 -1900,255,178,129 -1930,255,146,85 -2000,255,93,38 -2030,140,55,70 -2100,25,25,112 -2130,25,25,112 -2200,25,25,112 -2230,25,25,112 -2300,25,25,112 -2330,25,25,112 diff --git a/firmware/spiffs_image/schema_02.csv b/firmware/spiffs_image/schema_02.csv deleted file mode 100644 index a4d7fc0..0000000 --- a/firmware/spiffs_image/schema_02.csv +++ /dev/null @@ -1,48 +0,0 @@ -0000,25,25,112 -0030,25,25,112 -0100,25,25,112 -0130,25,25,112 -0200,25,25,112 -0230,25,25,112 -0300,25,25,112 -0330,62,25,95 -0400,102,60,78 -0430,140,78,61 -0500,178,95,44 -0530,214,113,27 -0600,255,130,10 -0630,255,139,22 -0700,255,147,34 -0730,255,155,46 -0800,255,163,58 -0830,255,172,70 -0900,255,180,82 -0930,255,189,93 -1000,255,197,105 -1030,255,205,117 -1100,255,213,129 -1130,255,222,141 -1200,255,230,153 -1230,255,222,143 -1300,255,214,133 -1330,255,206,124 -1400,255,198,114 -1430,255,191,104 -1500,255,183,94 -1530,255,175,84 -1600,255,167,74 -1630,255,159,64 -1700,255,151,55 -1730,255,143,45 -1800,255,135,35 -1830,214,116,45 -1900,178,98,61 -1930,140,80,74 -2000,102,62,86 -2030,63,44,99 -2100,25,25,112 -2130,25,25,112 -2200,25,25,112 -2230,25,25,112 -2300,25,25,112 -2330,25,25,112 diff --git a/firmware/storage/schema_01.csv b/firmware/storage/schema_01.csv new file mode 100644 index 0000000..445a11b --- /dev/null +++ b/firmware/storage/schema_01.csv @@ -0,0 +1,48 @@ +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +140,25,112,0,100,250 +255,130,112,0,130,250 +255,155,112,0,150,250 +255,177,115,0,170,250 +255,200,135,0,190,250 +255,219,170,0,210,250 +255,234,205,0,217,250 +255,249,240,0,223,250 +255,249,250,0,227,250 +239,245,255,0,234,250 +224,240,255,0,239,250 +215,235,255,0,244,250 +212,234,255,0,248,250 +210,233,255,0,250,250 +208,232,255,0,252,250 +207,231,255,0,255,250 +205,230,255,0,252,250 +204,229,255,0,250,250 +204,229,255,0,247,250 +206,230,255,0,245,250 +208,231,255,0,243,250 +213,232,255,0,240,250 +219,234,255,0,237,250 +229,239,255,0,235,250 +236,246,255,0,233,250 +255,252,251,0,230,250 +255,243,236,0,225,250 +255,225,202,0,220,250 +255,203,174,0,205,250 +255,178,129,0,190,250 +255,146,85,0,165,250 +255,93,38,0,140,250 +140,55,70,0,120,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 diff --git a/firmware/storage/schema_02.csv b/firmware/storage/schema_02.csv new file mode 100644 index 0000000..03e1de6 --- /dev/null +++ b/firmware/storage/schema_02.csv @@ -0,0 +1,48 @@ +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +62,25,95,0,100,250 +102,60,78,0,100,250 +140,78,61,0,115,250 +178,95,44,0,130,250 +214,113,27,0,150,250 +255,130,10,0,170,250 +255,139,22,0,190,250 +255,147,34,0,210,250 +255,155,46,0,215,250 +255,163,58,0,220,250 +255,172,70,0,225,250 +255,180,82,0,230,250 +255,189,93,0,235,250 +255,197,105,0,240,250 +255,205,117,0,245,250 +255,213,129,0,250,250 +255,222,141,0,252,250 +255,230,153,0,255,250 +255,222,143,0,252,250 +255,214,133,0,250,250 +255,206,124,0,247,250 +255,198,114,0,245,250 +255,191,104,0,242,250 +255,183,94,0,240,250 +255,175,84,0,237,250 +255,167,74,0,235,250 +255,159,64,0,233,250 +255,151,55,0,230,250 +255,143,45,0,225,250 +255,135,35,0,220,250 +214,116,45,0,205,250 +178,98,61,0,190,250 +140,80,74,0,165,250 +102,62,86,0,140,250 +63,44,99,0,120,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 +25,25,112,0,100,250 diff --git a/firmware/storage/schema_03.csv b/firmware/storage/schema_03.csv new file mode 100644 index 0000000..b367ec0 --- /dev/null +++ b/firmware/storage/schema_03.csv @@ -0,0 +1,48 @@ +25,24,112,0,100,250 +25,23,106,0,100,250 +25,22,100,0,100,250 +25,21,95,0,100,250 +25,19,89,0,100,250 +25,18,84,0,100,250 +25,17,78,0,100,250 +62,19,72,0,100,250 +102,51,67,0,100,250 +140,61,48,0,115,250 +178,55,25,0,130,250 +214,50,12,0,150,250 +255,44,3,0,170,250 +255,46,7,0,190,250 +255,48,11,0,210,250 +255,50,15,0,215,250 +255,52,18,0,220,250 +255,54,22,0,225,250 +255,56,25,0,230,250 +255,58,28,0,235,250 +255,60,32,0,240,250 +255,62,35,0,245,250 +255,64,38,0,250,250 +255,65,41,0,252,250 +255,67,45,0,255,250 +255,69,44,0,252,250 +255,71,44,0,250,250 +255,73,44,0,247,250 +255,75,43,0,245,250 +255,77,42,0,242,250 +255,79,40,0,240,250 +255,81,39,0,237,250 +255,83,36,0,235,250 +255,85,34,0,233,250 +255,87,31,0,230,250 +255,88,27,0,225,250 +255,90,23,0,220,250 +214,92,36,0,205,250 +178,94,58,0,190,250 +140,96,89,0,165,250 +102,71,98,0,140,250 +63,44,100,0,120,250 +25,22,102,0,100,250 +25,23,104,0,100,250 +25,23,106,0,100,250 +25,24,108,0,100,250 +25,24,110,0,100,250 +25,24,112,0,100,250 diff --git a/firmware/version.txt b/firmware/version.txt index 6c6aa7c..6da28dd 100644 --- a/firmware/version.txt +++ b/firmware/version.txt @@ -1 +1 @@ -0.1.0 \ No newline at end of file +0.1.1 \ No newline at end of file