led.cpp
1 #include <led.h> 2 #include <config.h> 3 4 #include <FastLED.h> 5 6 namespace { 7 CRGB pixel[1]; 8 9 CRGB toCRGB(const Color &c) { return CRGB(c.r, c.g, c.b); } 10 Color fromCRGB(const CRGB &c) { return {c.r, c.g, c.b}; } 11 } 12 13 Led LED; 14 15 bool Led::init() { 16 FastLED.addLeds<NEOPIXEL, config::led::GPIO>(pixel, 1) 17 .setCorrection(TypicalSMD5050); 18 FastLED.setBrightness(config::led::BRIGHTNESS); 19 pixel[0] = CRGB::Black; 20 FastLED.show(); 21 return true; 22 } 23 24 void Led::set(const Color &color) { 25 pixel[0] = toCRGB(color); 26 FastLED.show(); 27 } 28 29 void Led::set(uint8_t r, uint8_t g, uint8_t b) { 30 pixel[0] = CRGB(r, g, b); 31 FastLED.show(); 32 } 33 34 void Led::setHSV(uint8_t hue, uint8_t saturation, uint8_t value) { 35 pixel[0] = CHSV(hue, saturation, value); 36 FastLED.show(); 37 } 38 39 void Led::off() { 40 pixel[0] = CRGB::Black; 41 FastLED.show(); 42 } 43 44 void Led::setBrightness(uint8_t b) { 45 FastLED.setBrightness(b); 46 FastLED.show(); 47 } 48 49 uint8_t Led::getBrightness() { 50 return FastLED.getBrightness(); 51 } 52 53 Color Led::getColor() { 54 return fromCRGB(pixel[0]); 55 } 56 57 void Led::glow(uint32_t duration_ms) { 58 uint8_t saved = getBrightness(); 59 uint32_t start = millis(); 60 while (millis() - start < duration_ms) { 61 FastLED.setBrightness(beatsin8(30, 10, 210)); 62 pixel[0] = toCRGB(colors::BlueViolet); 63 FastLED.show(); 64 delay(config::led::FRAME_MS); 65 } 66 FastLED.setBrightness(saved); 67 } 68 69 void Led::fadeIn(const Color &color, uint32_t duration_ms) { 70 uint8_t target = getBrightness(); 71 CRGB c = toCRGB(color); 72 uint32_t start = millis(); 73 while (millis() - start < duration_ms) { 74 uint8_t progress = ((millis() - start) * 255) / duration_ms; 75 FastLED.setBrightness(scale8(ease8InOutQuad(progress), target)); 76 pixel[0] = c; 77 FastLED.show(); 78 delay(config::led::FRAME_MS); 79 } 80 FastLED.setBrightness(target); 81 pixel[0] = c; 82 FastLED.show(); 83 } 84 85 void Led::fadeOut(const Color &color, uint32_t duration_ms) { 86 uint8_t saved = getBrightness(); 87 CRGB c = toCRGB(color); 88 uint32_t start = millis(); 89 while (millis() - start < duration_ms) { 90 uint8_t progress = ((millis() - start) * 255) / duration_ms; 91 FastLED.setBrightness(scale8(ease8InOutQuad(255 - progress), saved)); 92 pixel[0] = c; 93 FastLED.show(); 94 delay(config::led::FRAME_MS); 95 } 96 pixel[0] = CRGB::Black; 97 FastLED.show(); 98 FastLED.setBrightness(saved); 99 } 100 101 #ifdef PIO_UNIT_TESTING 102 103 #include <testing/utils.h> 104 105 static void test_led_init(void) { 106 WHEN("the LED is initialized"); 107 TEST_ASSERT_TRUE_MESSAGE(LED.init(), "device: LED init must succeed"); 108 Color color = LED.getColor(); 109 TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, &color.r, 3, 110 "device: LED must start black"); 111 } 112 113 static void test_led_set_named(void) { 114 WHEN("the LED is set to a named color"); 115 LED.set(colors::Red); 116 Color color = LED.getColor(); 117 Color expected = colors::Red; 118 TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(&expected.r, &color.r, 3, 119 "device: color should match Red"); 120 } 121 122 static void test_led_set_rgb(void) { 123 WHEN("the LED is set with explicit RGB values"); 124 LED.set(100, 200, 50); 125 Color color = LED.getColor(); 126 Color expected = {100, 200, 50}; 127 TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(&expected.r, &color.r, 3, 128 "device: color should match {100, 200, 50}"); 129 } 130 131 static void test_led_set_hsv(void) { 132 WHEN("the LED is set via HSV"); 133 LED.setHSV(0, 255, 255); 134 Color color = LED.getColor(); 135 TEST_ASSERT_EQUAL_UINT8_MESSAGE(255, color.r, "device: hue 0 should be red"); 136 TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(10, color.g, "device: green should be near zero for red hue"); 137 TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(10, color.b, "device: blue should be near zero for red hue"); 138 } 139 140 static void test_led_off(void) { 141 GIVEN("the LED is set to white"); 142 LED.set(colors::White); 143 144 WHEN("the LED is turned off"); 145 LED.off(); 146 Color color = LED.getColor(); 147 TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, &color.r, 3, 148 "device: LED must be black after off"); 149 } 150 151 static void test_led_brightness(void) { 152 WHEN("brightness is adjusted"); 153 LED.setBrightness(128); 154 TEST_ASSERT_EQUAL_UINT8_MESSAGE(128, LED.getBrightness(), 155 "device: brightness must match after set"); 156 LED.setBrightness(255); 157 TEST_ASSERT_EQUAL_UINT8_MESSAGE(255, LED.getBrightness(), 158 "device: brightness must restore to max"); 159 } 160 161 static void test_led_fade_in(void) { 162 WHEN("fadeIn is called with Gold"); 163 LED.setBrightness(config::led::BRIGHTNESS); 164 LED.fadeIn(colors::Gold, 100); 165 Color color = LED.getColor(); 166 TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(200, color.r, "device: red after fadeIn"); 167 TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(150, color.g, "device: green after fadeIn"); 168 TEST_ASSERT_EQUAL_UINT8_MESSAGE(config::led::BRIGHTNESS, LED.getBrightness(), 169 "device: brightness must restore after fadeIn"); 170 } 171 172 static void test_led_fade_out(void) { 173 GIVEN("the LED is set to white"); 174 LED.set(colors::White); 175 176 WHEN("fadeOut is called"); 177 LED.fadeOut(colors::White, 100); 178 Color color = LED.getColor(); 179 TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, &color.r, 3, 180 "device: LED must be black after fadeOut"); 181 TEST_ASSERT_EQUAL_UINT8_MESSAGE(config::led::BRIGHTNESS, LED.getBrightness(), 182 "device: brightness must restore after fadeOut"); 183 } 184 185 void programs::led::test(void) { 186 MODULE("LED"); 187 RUN_TEST(test_led_init); 188 RUN_TEST(test_led_set_named); 189 RUN_TEST(test_led_set_rgb); 190 RUN_TEST(test_led_set_hsv); 191 RUN_TEST(test_led_off); 192 RUN_TEST(test_led_brightness); 193 RUN_TEST(test_led_fade_in); 194 RUN_TEST(test_led_fade_out); 195 } 196 197 #endif