/ firmware / src / programs / led.cpp
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