From 66be6e444b753041e4ea050a09a0af71d8082421 Mon Sep 17 00:00:00 2001 From: Kurt LaVacque Date: Thu, 8 Jan 2026 10:38:09 +0100 Subject: [PATCH 1/2] Enhance device responsiveness by preventing empty colorsets and ensuring minimum brightness. Added checks to avoid invisible patterns and added visual indicators during wake/sleep transitions. Updated LED brightness handling to prevent zero values. --- Helios/Colorset.cpp | 6 + Helios/Helios.cpp | 40 +++++- Helios/Led.cpp | 11 ++ Helios/Led.h | 2 +- HeliosCLI/color_analysis.py | 236 +++++++++++++++++++++++++++++++ HeliosCLI/menu_color_analysis.py | 158 +++++++++++++++++++++ 6 files changed, 451 insertions(+), 2 deletions(-) create mode 100644 HeliosCLI/color_analysis.py create mode 100644 HeliosCLI/menu_color_analysis.py diff --git a/Helios/Colorset.cpp b/Helios/Colorset.cpp index c3d1d6a9..5c497db4 100644 --- a/Helios/Colorset.cpp +++ b/Helios/Colorset.cpp @@ -175,6 +175,12 @@ void Colorset::removeColor(uint8_t index) if (index >= m_numColors) { return; } + // Prevent removing the last color to avoid creating a disabled pattern + // A pattern with 0 colors will be disabled and show no light, making the + // device appear unresponsive + if (m_numColors <= 1) { + return; // Don't allow empty colorset + } for (uint8_t i = index; i < (m_numColors - 1); ++i) { m_palette[i] = m_palette[i + 1]; } diff --git a/Helios/Helios.cpp b/Helios/Helios.cpp index 835f945b..453c8332 100644 --- a/Helios/Helios.cpp +++ b/Helios/Helios.cpp @@ -148,6 +148,14 @@ void Helios::enter_sleep() DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB4); // wakeup here, re-init init_components(); + // Brief visual "I'm alive" indicator to show device is responsive + // This helps users know the device woke up even if brightness is low + // or pattern is disabled + Led::set(RGB_WHITE_BRI_LOW); + Led::update(); + Time::delayMilliseconds(50); + Led::clear(); + Led::update(); #else cur_state = STATE_SLEEP; // enable the sleep bool @@ -173,6 +181,14 @@ void Helios::wakeup() cur_state = STATE_MODES; // turn off the sleeping flag that only CLI has sleeping = false; + // Brief visual "I'm alive" indicator to show device is responsive + // This helps users know the device woke up even if brightness is low + // or pattern is disabled + Led::set(RGB_WHITE_BRI_LOW); + Led::update(); + Time::delayMilliseconds(50); + Led::clear(); + Led::update(); #endif } @@ -193,6 +209,17 @@ void Helios::load_cur_mode() // try to write it out because storage was corrupt Storage::write_pattern(cur_mode, pat); } + // Validate pattern isn't disabled - this prevents invisible patterns that + // make the device appear unresponsive. A pattern is disabled if: + // 1. It has no colors (numColors == 0), OR + // 2. It has no on duration AND no dash duration (both are 0) + PatternArgs args = pat.getArgs(); + if (pat.colorset().numColors() == 0 || + (args.on_dur == 0 && args.dash_dur == 0)) { + // Pattern is disabled, restore defaults + Patterns::make_default(cur_mode, pat); + Storage::write_pattern(cur_mode, pat); + } // then re-initialize the pattern pat.init(); } @@ -296,6 +323,12 @@ void Helios::handle_state() } break; #endif + default: + // Recovery: invalid state detected (could be due to RAM corruption or bug) + // Reset to known good state to prevent device from appearing unresponsive + cur_state = STATE_MODES; + load_cur_mode(); + break; } } @@ -772,7 +805,7 @@ void Helios::handle_state_set_global_brightness() } // show different levels of green for each selection uint8_t col = 0; - uint8_t brightness = 0; + uint8_t brightness = BRIGHTNESS_LOWEST; // Safe default instead of 0 switch (menu_selection) { case 0: col = 0xFF; @@ -791,6 +824,11 @@ void Helios::handle_state_set_global_brightness() brightness = BRIGHTNESS_LOWEST; break; } + // Additional guard: ensure brightness is never 0 (would make LED invisible) + // This protects against edge cases like menu_selection corruption + if (brightness == 0) { + brightness = BRIGHTNESS_LOWEST; + } Led::set(0, col, 0); // when the user long clicks a selection if (Button::onLongClick()) { diff --git a/Helios/Led.cpp b/Helios/Led.cpp index 13052f1e..bb921bc6 100644 --- a/Helios/Led.cpp +++ b/Helios/Led.cpp @@ -122,6 +122,17 @@ void Led::setPWM(uint8_t pwmPin, uint8_t pwmValue, volatile uint8_t &controlRegi #endif } +void Led::setBrightness(uint8_t brightness) +{ + // Prevent brightness from being set to 0 (invisible LED) + // This protects against EEPROM corruption or edge cases that could + // make the device appear completely unresponsive + if (brightness == 0) { + brightness = BRIGHTNESS_LOWEST; // Use lowest visible brightness instead + } + m_brightness = brightness; +} + void Led::update() { #ifdef HELIOS_EMBEDDED diff --git a/Helios/Led.h b/Helios/Led.h index 34c5faad..8e2658f7 100644 --- a/Helios/Led.h +++ b/Helios/Led.h @@ -42,7 +42,7 @@ class Led // global brightness static uint8_t getBrightness() { return m_brightness; } - static void setBrightness(uint8_t brightness) { m_brightness = brightness; } + static void setBrightness(uint8_t brightness); // actually update the LEDs and show the changes static void update(); diff --git a/HeliosCLI/color_analysis.py b/HeliosCLI/color_analysis.py new file mode 100644 index 00000000..2bfc7a83 --- /dev/null +++ b/HeliosCLI/color_analysis.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +""" +Analyze ColorConstants.h to check for color collisions at different bit depths +""" + +def rgb24_to_rgb16(r, g, b): + """Convert 24-bit RGB to 16-bit RGB565""" + r5 = (r >> 3) & 0x1F # 5 bits for red + g6 = (g >> 2) & 0x3F # 6 bits for green + b5 = (b >> 3) & 0x1F # 5 bits for blue + return (r5 << 11) | (g6 << 5) | b5 + +def rgb24_to_rgb8(r, g, b): + """Convert 24-bit RGB to 8-bit RGB332""" + r3 = (r >> 5) & 0x07 # 3 bits for red + g3 = (g >> 5) & 0x07 # 3 bits for green + b2 = (b >> 6) & 0x03 # 2 bits for blue + return (r3 << 5) | (g3 << 2) | b2 + +def parse_rgb(hex_val): + """Parse RGB hex value to components""" + r = (hex_val >> 16) & 0xFF + g = (hex_val >> 8) & 0xFF + b = hex_val & 0xFF + return r, g, b + +# Define all colors from ColorConstants.h +colors = { + # Basic colors + "RGB_OFF": 0x000000, + "RGB_WHITE": 0xFFFFFF, + "RGB_RED": 0xFF0000, + "RGB_CORAL_ORANGE": 0xFF1E00, + "RGB_ORANGE": 0xFF3C00, + "RGB_YELLOW": 0xFF7800, + "RGB_LIME_GREEN": 0x59FF00, + "RGB_GREEN": 0x00FF00, + "RGB_SEAFOAM": 0x00FF3C, + "RGB_TURQUOISE": 0x00FFD1, + "RGB_ICE_BLUE": 0x00A7FF, + "RGB_LIGHT_BLUE": 0x0047FF, + "RGB_BLUE": 0x0000FF, + "RGB_ROYAL_BLUE": 0x1D00FF, + "RGB_PURPLE": 0x8300FF, + "RGB_PINK": 0xD200FF, + "RGB_HOT_PINK": 0xFF00B4, + "RGB_MAGENTA": 0xFF003C, + "RGB_CREAM": 0xFF932A, + "RGB_CORAL": 0xFF2813, + "RGB_CYAN": 0x00E5FA, + "RGB_MINT": 0x9DFF8C, + "RGB_LUNA": 0x4050A0, + + # Medium brightness + "RGB_WHITE_BRI_MEDIUM": 0x787878, + "RGB_RED_BRI_MEDIUM": 0x780000, + "RGB_CORAL_ORANGE_BRI_MEDIUM": 0x780E00, + "RGB_ORANGE_BRI_MEDIUM": 0x781C00, + "RGB_YELLOW_BRI_MEDIUM": 0x783800, + "RGB_LIME_GREEN_BRI_MEDIUM": 0x297800, + "RGB_GREEN_BRI_MEDIUM": 0x007800, + "RGB_SEAFOAM_BRI_MEDIUM": 0x00781C, + "RGB_TURQUOISE_BRI_MEDIUM": 0x007862, + "RGB_ICE_BLUE_BRI_MEDIUM": 0x004E78, + "RGB_LIGHT_BLUE_BRI_MEDIUM": 0x002178, + "RGB_BLUE_BRI_MEDIUM": 0x000078, + "RGB_ROYAL_BLUE_BRI_MEDIUM": 0x0D0078, + "RGB_PURPLE_BRI_MEDIUM": 0x3D0078, + "RGB_PINK_BRI_MEDIUM": 0x620078, + "RGB_HOT_PINK_BRI_MEDIUM": 0x780054, + "RGB_MAGENTA_BRI_MEDIUM": 0x78001C, + + # Low brightness + "RGB_WHITE_BRI_LOW": 0x3C3C3C, + "RGB_RED_BRI_LOW": 0x3C0000, + "RGB_CORAL_ORANGE_BRI_LOW": 0x3C0700, + "RGB_ORANGE_BRI_LOW": 0x3C0E00, + "RGB_YELLOW_BRI_LOW": 0x3C1C00, + "RGB_LIME_GREEN_BRI_LOW": 0x143C00, + "RGB_GREEN_BRI_LOW": 0x003C00, + "RGB_SEAFOAM_BRI_LOW": 0x003C0E, + "RGB_TURQUOISE_BRI_LOW": 0x003C31, + "RGB_ICE_BLUE_BRI_LOW": 0x00273C, + "RGB_LIGHT_BLUE_BRI_LOW": 0x00103C, + "RGB_BLUE_BRI_LOW": 0x00003C, + "RGB_ROYAL_BLUE_BRI_LOW": 0x06003C, + "RGB_PURPLE_BRI_LOW": 0x1E003C, + "RGB_PINK_BRI_LOW": 0x31003C, + "RGB_HOT_PINK_BRI_LOW": 0x3C002A, + "RGB_MAGENTA_BRI_LOW": 0x3C000E, + + # Lowest brightness + "RGB_WHITE_BRI_LOWEST": 0x0A0A0A, + "RGB_RED_BRI_LOWEST": 0x0A0000, + "RGB_CORAL_ORANGE_BRI_LOWEST": 0x0A0100, + "RGB_ORANGE_BRI_LOWEST": 0x0A0200, + "RGB_YELLOW_BRI_LOWEST": 0x0A0400, + "RGB_LIME_GREEN_BRI_LOWEST": 0x030A00, + "RGB_GREEN_BRI_LOWEST": 0x000A00, + "RGB_SEAFOAM_BRI_LOWEST": 0x000A02, + "RGB_TURQUOISE_BRI_LOWEST": 0x000A08, + "RGB_ICE_BLUE_BRI_LOWEST": 0x00060A, + "RGB_LIGHT_BLUE_BRI_LOWEST": 0x00020A, + "RGB_BLUE_BRI_LOWEST": 0x00000A, + "RGB_ROYAL_BLUE_BRI_LOWEST": 0x01000A, + "RGB_PURPLE_BRI_LOWEST": 0x05000A, + "RGB_PINK_BRI_LOWEST": 0x08000A, + "RGB_HOT_PINK_BRI_LOWEST": 0x0A0007, + "RGB_MAGENTA_BRI_LOWEST": 0x0A0002, + + # Medium saturation + "RGB_RED_SAT_MEDIUM": 0xFF2222, + "RGB_CORAL_ORANGE_SAT_MEDIUM": 0xFF3C22, + "RGB_ORANGE_SAT_MEDIUM": 0xFF5622, + "RGB_YELLOW_SAT_MEDIUM": 0xFF8A22, + "RGB_LIME_GREEN_SAT_MEDIUM": 0x6FFF22, + "RGB_GREEN_SAT_MEDIUM": 0x22FF22, + "RGB_SEAFOAM_SAT_MEDIUM": 0x22FF56, + "RGB_TURQUOISE_SAT_MEDIUM": 0x22FFD7, + "RGB_ICE_BLUE_SAT_MEDIUM": 0x22B3FF, + "RGB_LIGHT_BLUE_SAT_MEDIUM": 0x2260FF, + "RGB_BLUE_SAT_MEDIUM": 0x2222FF, + "RGB_ROYAL_BLUE_SAT_MEDIUM": 0x3C22FF, + "RGB_PURPLE_SAT_MEDIUM": 0x9422FF, + "RGB_PINK_SAT_MEDIUM": 0xD822FF, + "RGB_HOT_PINK_SAT_MEDIUM": 0xFF22BE, + "RGB_MAGENTA_SAT_MEDIUM": 0xFF2256, + + # Low saturation + "RGB_RED_SAT_LOW": 0xFF5555, + "RGB_CORAL_ORANGE_SAT_LOW": 0xFF6955, + "RGB_ORANGE_SAT_LOW": 0xFF7D55, + "RGB_YELLOW_SAT_LOW": 0xFFA555, + "RGB_LIME_GREEN_SAT_LOW": 0x90FF55, + "RGB_GREEN_SAT_LOW": 0x55FF55, + "RGB_SEAFOAM_SAT_LOW": 0x55FF7D, + "RGB_TURQUOISE_SAT_LOW": 0x55FFE0, + "RGB_ICE_BLUE_SAT_LOW": 0x55C4FF, + "RGB_LIGHT_BLUE_SAT_LOW": 0x5584FF, + "RGB_BLUE_SAT_LOW": 0x5555FF, + "RGB_ROYAL_BLUE_SAT_LOW": 0x6855FF, + "RGB_PURPLE_SAT_LOW": 0xAC55FF, + "RGB_PINK_SAT_LOW": 0xE055FF, + "RGB_HOT_PINK_SAT_LOW": 0xFF55CD, + "RGB_MAGENTA_SAT_LOW": 0xFF557D, + + # Lowest saturation + "RGB_RED_SAT_LOWEST": 0xFF7D7D, + "RGB_CORAL_ORANGE_SAT_LOWEST": 0xFF8C7D, + "RGB_ORANGE_SAT_LOWEST": 0xFF9B7D, + "RGB_YELLOW_SAT_LOWEST": 0xFFBA7D, + "RGB_LIME_GREEN_SAT_LOWEST": 0xAAFF7D, + "RGB_GREEN_SAT_LOWEST": 0x7DFF7D, + "RGB_SEAFOAM_SAT_LOWEST": 0x7DFF9B, + "RGB_TURQUOISE_SAT_LOWEST": 0x7DFFE7, + "RGB_ICE_BLUE_SAT_LOWEST": 0x7DD2FF, + "RGB_LIGHT_BLUE_SAT_LOWEST": 0x7DA1FF, + "RGB_BLUE_SAT_LOWEST": 0x7D7DFF, + "RGB_ROYAL_BLUE_SAT_LOWEST": 0x8B7DFF, + "RGB_PURPLE_SAT_LOWEST": 0xBF7DFF, + "RGB_PINK_SAT_LOWEST": 0xE87DFF, + "RGB_HOT_PINK_SAT_LOWEST": 0xFF7DD8, + "RGB_MAGENTA_SAT_LOWEST": 0xFF7D9B, +} + +def analyze_collisions(): + print("=" * 80) + print("COLOR COLLISION ANALYSIS") + print("=" * 80) + + # Build maps for each bit depth + rgb24_map = {} + rgb16_map = {} + rgb8_map = {} + + for name, hex_val in colors.items(): + r, g, b = parse_rgb(hex_val) + + # Store 24-bit + rgb24_map[name] = (hex_val, r, g, b) + + # Convert and store 16-bit + rgb16_val = rgb24_to_rgb16(r, g, b) + if rgb16_val not in rgb16_map: + rgb16_map[rgb16_val] = [] + rgb16_map[rgb16_val].append(name) + + # Convert and store 8-bit + rgb8_val = rgb24_to_rgb8(r, g, b) + if rgb8_val not in rgb8_map: + rgb8_map[rgb8_val] = [] + rgb8_map[rgb8_val].append(name) + + # Find collisions in 16-bit + print("\n16-BIT RGB565 COLLISIONS:") + print("-" * 80) + collisions_16 = [colors for colors in rgb16_map.values() if len(colors) > 1] + if collisions_16: + for group in sorted(collisions_16, key=lambda x: len(x), reverse=True): + print(f"\n{len(group)} colors map to same 16-bit value:") + for color_name in group: + hex_val, r, g, b = rgb24_map[color_name] + print(f" {color_name:40s} = 0x{hex_val:06X} ({r:3d}, {g:3d}, {b:3d})") + print(f"\nTotal: {len(collisions_16)} collision groups affecting {sum(len(g) for g in collisions_16)} colors") + else: + print("NO COLLISIONS - All colors remain unique at 16-bit depth") + + # Find collisions in 8-bit + print("\n\n8-BIT RGB332 COLLISIONS:") + print("-" * 80) + collisions_8 = [colors for colors in rgb8_map.values() if len(colors) > 1] + if collisions_8: + for group in sorted(collisions_8, key=lambda x: len(x), reverse=True): + print(f"\n{len(group)} colors map to same 8-bit value:") + for color_name in group: + hex_val, r, g, b = rgb24_map[color_name] + print(f" {color_name:40s} = 0x{hex_val:06X} ({r:3d}, {g:3d}, {b:3d})") + print(f"\nTotal: {len(collisions_8)} collision groups affecting {sum(len(g) for g in collisions_8)} colors") + else: + print("NO COLLISIONS - All colors remain unique at 8-bit depth") + + # Summary statistics + print("\n" + "=" * 80) + print("SUMMARY") + print("=" * 80) + print(f"Total colors defined: {len(colors)}") + print(f"Unique at 24-bit: {len(colors)}") + print(f"Unique at 16-bit: {len(rgb16_map)}") + print(f"Unique at 8-bit: {len(rgb8_map)}") + print(f"Color loss at 16-bit: {len(colors) - len(rgb16_map)} ({100*(len(colors)-len(rgb16_map))/len(colors):.1f}%)") + print(f"Color loss at 8-bit: {len(colors) - len(rgb8_map)} ({100*(len(colors)-len(rgb8_map))/len(colors):.1f}%)") + print("=" * 80) + +if __name__ == "__main__": + analyze_collisions() + diff --git a/HeliosCLI/menu_color_analysis.py b/HeliosCLI/menu_color_analysis.py new file mode 100644 index 00000000..f404175f --- /dev/null +++ b/HeliosCLI/menu_color_analysis.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +""" +Analyze the specific colors used in Helios color_menu_data to see if they remain unique at 8-bit +""" + +def rgb24_to_rgb8(r, g, b): + """Convert 24-bit RGB to 8-bit RGB332""" + r3 = (r >> 5) & 0x07 # 3 bits for red + g3 = (g >> 5) & 0x07 # 3 bits for green + b2 = (b >> 6) & 0x03 # 2 bits for blue + return (r3 << 5) | (g3 << 2) | b2 + +def hue_to_rgb(hue): + """Convert HSV to RGB (assuming full saturation and value)""" + # This is a simplified conversion for the Helios hue values + hue_normalized = hue / 255.0 + + region = int(hue_normalized * 6) + remainder = (hue_normalized * 6) - region + + if region == 0: + r, g, b = 255, int(255 * remainder), 0 + elif region == 1: + r, g, b = int(255 * (1 - remainder)), 255, 0 + elif region == 2: + r, g, b = 0, 255, int(255 * remainder) + elif region == 3: + r, g, b = 0, int(255 * (1 - remainder)), 255 + elif region == 4: + r, g, b = int(255 * remainder), 0, 255 + else: + r, g, b = 255, 0, int(255 * (1 - remainder)) + + return r, g, b + +# The 16 hues from color_menu_data in Helios.cpp +menu_hues = { + "HUE_RED": 0, + "HUE_CORAL_ORANGE": 5, + "HUE_ORANGE": 10, + "HUE_YELLOW": 20, + "HUE_LIME_GREEN": 70, + "HUE_GREEN": 85, + "HUE_SEAFOAM": 95, + "HUE_TURQUOISE": 120, + "HUE_ICE_BLUE": 142, + "HUE_LIGHT_BLUE": 158, + "HUE_BLUE": 170, + "HUE_ROYAL_BLUE": 175, + "HUE_PURPLE": 192, + "HUE_PINK": 205, + "HUE_HOT_PINK": 225, + "HUE_MAGENTA": 245, +} + +# The corresponding RGB values from ColorConstants.h +menu_colors = { + "HUE_RED": 0xFF0000, # 255, 0, 0 + "HUE_CORAL_ORANGE": 0xFF1E00, # 255, 30, 0 + "HUE_ORANGE": 0xFF3C00, # 255, 60, 0 + "HUE_YELLOW": 0xFF7800, # 255, 120, 0 + "HUE_LIME_GREEN": 0x59FF00, # 89, 255, 0 + "HUE_GREEN": 0x00FF00, # 0, 255, 0 + "HUE_SEAFOAM": 0x00FF3C, # 0, 255, 60 + "HUE_TURQUOISE": 0x00FFD1, # 0, 255, 209 + "HUE_ICE_BLUE": 0x00A7FF, # 0, 167, 255 + "HUE_LIGHT_BLUE": 0x0047FF, # 0, 71, 255 + "HUE_BLUE": 0x0000FF, # 0, 0, 255 + "HUE_ROYAL_BLUE": 0x1D00FF, # 29, 0, 255 + "HUE_PURPLE": 0x8300FF, # 131, 0, 255 + "HUE_PINK": 0xD200FF, # 210, 0, 255 + "HUE_HOT_PINK": 0xFF00B4, # 255, 0, 180 + "HUE_MAGENTA": 0xFF003C, # 255, 0, 60 +} + +print("=" * 80) +print("HELIOS MENU COLOR ANALYSIS - 8-BIT COLOR DEPTH") +print("=" * 80) + +# Convert all menu colors to 8-bit +color_map_8bit = {} +for name, rgb24 in menu_colors.items(): + r = (rgb24 >> 16) & 0xFF + g = (rgb24 >> 8) & 0xFF + b = rgb24 & 0xFF + + rgb8 = rgb24_to_rgb8(r, g, b) + + if rgb8 not in color_map_8bit: + color_map_8bit[rgb8] = [] + color_map_8bit[rgb8].append((name, rgb24, r, g, b)) + +# Display the color groups +print("\nColor Groups (colors with same 8-bit value):") +print("-" * 80) + +group_num = 1 +collisions_found = False + +for rgb8_val, colors in sorted(color_map_8bit.items()): + if len(colors) > 1: + collisions_found = True + print(f"\nGroup {group_num}: {len(colors)} colors map to 8-bit value 0x{rgb8_val:02X}") + for name, rgb24, r, g, b in colors: + print(f" {name:20s} = 0x{rgb24:06X} ({r:3d}, {g:3d}, {b:3d})") + group_num += 1 + +if not collisions_found: + print("\n✓ NO COLLISIONS - All 16 menu colors remain unique at 8-bit!") +else: + print(f"\n✗ COLLISIONS DETECTED") + +# Show the color groups organized by menu structure +print("\n" + "=" * 80) +print("COLOR GROUPS AS ORGANIZED IN MENU") +print("=" * 80) + +groups = [ + ["HUE_RED", "HUE_CORAL_ORANGE", "HUE_ORANGE", "HUE_YELLOW"], + ["HUE_LIME_GREEN", "HUE_GREEN", "HUE_SEAFOAM", "HUE_TURQUOISE"], + ["HUE_ICE_BLUE", "HUE_LIGHT_BLUE", "HUE_BLUE", "HUE_ROYAL_BLUE"], + ["HUE_PURPLE", "HUE_PINK", "HUE_HOT_PINK", "HUE_MAGENTA"], +] + +for i, group in enumerate(groups, 1): + print(f"\nGroup {i}:") + rgb8_values = [] + for hue_name in group: + rgb24 = menu_colors[hue_name] + r = (rgb24 >> 16) & 0xFF + g = (rgb24 >> 8) & 0xFF + b = rgb24 & 0xFF + rgb8 = rgb24_to_rgb8(r, g, b) + rgb8_values.append(rgb8) + print(f" {hue_name:20s} = 0x{rgb24:06X} → 8-bit: 0x{rgb8:02X}") + + # Check for duplicates within this group + if len(set(rgb8_values)) < len(rgb8_values): + print(" ⚠️ WARNING: Colors in this group collide at 8-bit!") + else: + print(" ✓ All unique at 8-bit") + +# Summary +print("\n" + "=" * 80) +print("SUMMARY") +print("=" * 80) +print(f"Total menu colors: {len(menu_colors)}") +print(f"Unique at 24-bit: {len(menu_colors)}") +print(f"Unique at 8-bit: {len(color_map_8bit)}") + +if len(color_map_8bit) < len(menu_colors): + print(f"Color loss: {len(menu_colors) - len(color_map_8bit)} colors lost") + print(f"Percentage retained: {100 * len(color_map_8bit) / len(menu_colors):.1f}%") +else: + print("✓ All menu colors remain unique at 8-bit depth!") + +print("=" * 80) + From 32f3cb059e08f92a576e5c79529d520452bf7941 Mon Sep 17 00:00:00 2001 From: Kurt LaVacque Date: Thu, 8 Jan 2026 17:11:50 +0100 Subject: [PATCH 2/2] Remove visual indicators during wake/sleep transitions and simplify brightness handling by allowing zero values. Adjust state recovery logic to fall through to known good state for unexpected values. --- Helios/Helios.cpp | 29 +++-------------------------- Helios/Led.cpp | 6 ------ 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/Helios/Helios.cpp b/Helios/Helios.cpp index 453c8332..ad46b98b 100644 --- a/Helios/Helios.cpp +++ b/Helios/Helios.cpp @@ -148,14 +148,6 @@ void Helios::enter_sleep() DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB4); // wakeup here, re-init init_components(); - // Brief visual "I'm alive" indicator to show device is responsive - // This helps users know the device woke up even if brightness is low - // or pattern is disabled - Led::set(RGB_WHITE_BRI_LOW); - Led::update(); - Time::delayMilliseconds(50); - Led::clear(); - Led::update(); #else cur_state = STATE_SLEEP; // enable the sleep bool @@ -181,14 +173,6 @@ void Helios::wakeup() cur_state = STATE_MODES; // turn off the sleeping flag that only CLI has sleeping = false; - // Brief visual "I'm alive" indicator to show device is responsive - // This helps users know the device woke up even if brightness is low - // or pattern is disabled - Led::set(RGB_WHITE_BRI_LOW); - Led::update(); - Time::delayMilliseconds(50); - Led::clear(); - Led::update(); #endif } @@ -324,10 +308,7 @@ void Helios::handle_state() break; #endif default: - // Recovery: invalid state detected (could be due to RAM corruption or bug) - // Reset to known good state to prevent device from appearing unresponsive - cur_state = STATE_MODES; - load_cur_mode(); + // Fallthrough to STATE_MODES for any unexpected state value break; } } @@ -805,8 +786,9 @@ void Helios::handle_state_set_global_brightness() } // show different levels of green for each selection uint8_t col = 0; - uint8_t brightness = BRIGHTNESS_LOWEST; // Safe default instead of 0 + uint8_t brightness = 0; switch (menu_selection) { + default: case 0: col = 0xFF; brightness = BRIGHTNESS_HIGH; @@ -824,11 +806,6 @@ void Helios::handle_state_set_global_brightness() brightness = BRIGHTNESS_LOWEST; break; } - // Additional guard: ensure brightness is never 0 (would make LED invisible) - // This protects against edge cases like menu_selection corruption - if (brightness == 0) { - brightness = BRIGHTNESS_LOWEST; - } Led::set(0, col, 0); // when the user long clicks a selection if (Button::onLongClick()) { diff --git a/Helios/Led.cpp b/Helios/Led.cpp index bb921bc6..023369a5 100644 --- a/Helios/Led.cpp +++ b/Helios/Led.cpp @@ -124,12 +124,6 @@ void Led::setPWM(uint8_t pwmPin, uint8_t pwmValue, volatile uint8_t &controlRegi void Led::setBrightness(uint8_t brightness) { - // Prevent brightness from being set to 0 (invisible LED) - // This protects against EEPROM corruption or edge cases that could - // make the device appear completely unresponsive - if (brightness == 0) { - brightness = BRIGHTNESS_LOWEST; // Use lowest visible brightness instead - } m_brightness = brightness; }