diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 10908086..f556146b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,13 +9,13 @@ jobs: steps: - uses: actions/checkout@v2 - name: Check src format - uses: DoozyX/clang-format-lint-action@v0.8 + uses: DoozyX/clang-format-lint-action@v0.9 with: source: './src' extensions: 'h,cpp' clangFormatVersion: 9 - name: Check examples format - uses: DoozyX/clang-format-lint-action@v0.8 + uses: DoozyX/clang-format-lint-action@v0.9 with: source: './examples' extensions: 'h,cpp,ino' diff --git a/README.md b/README.md index c5533061..12a299ba 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ _This is not an official corsair project._ ## Features * Add support of Corsair DIY device protocol to Arduino. * Control LEDs with the [Corsair iCUE software](https://www.corsair.com/icue). -* Easy to use with [FastLED](http://fastled.io/). -* [Supported LED chipsets](https://github.com/FastLED/FastLED/wiki/Overview#chipsets). (e.g. WS2812B, WS2801) +* [Support common LED chipsets](https://github.com/FastLED/FastLED/wiki/Overview#chipsets). (e.g. WS2812B, WS2801) +* Support [FastLED](http://fastled.io/). * Supported platform: Arduino AVR -* Persistent settings for use without a USB connection. +* Hardware Lighting mode. * Use multiple devices at the same time. * Repeat or scale LED channels to arbitrary size. @@ -91,7 +91,9 @@ Now you can create lighting effects in the "Lighting Channel #" tabs. - [API Documentation](https://legion2.github.io/CorsairLightingProtocol/) - [How it works](#how-it-works) - [Use of multiple devices](#use-of-multiple-devices) -- [Repeat or scale LED channel](#repeat-or-scale-led-channel) +- [Repeat or scale LED channels](#repeat-or-scale-led-channels) +- [Increase the Brightness of the LEDs](#increase-the-brightness-of-the-leds) +- [Hardware Lighting mode](#hardware-lighting-mode) ## How it works This library uses the USB HID interface of the ATmega32U4. @@ -128,10 +130,9 @@ Upload the DeviceIDTool sketch and then open the Serial monitor with baudrate 11 The tool displays the current DeviceID, you can type in a new DeviceID that is saved on the Arduino. After that, you can upload another sketch. -## Repeat or scale LED channel +## Repeat or scale LED channels You can repeat or scale LED channel controlled by iCUE onto physical LED strips. -This is very useful if you have very long LED strips that are longer than 60/96/135 LEDs. -This is the maximum number iCUE supports. +This is very useful if you have very long LED strips that are longer than 60/96/135 LEDs, which is the maximum number iCUE supports. To repeat or scale a LED channel you must apply the `CLP::repeat` or the `CLP:scale` function in the update hook of the FastLEDController. See the [RepeatAndScale](examples/RepeatAndScale/RepeatAndScale.ino) example for the complete code. @@ -146,16 +147,24 @@ For both functions it's **important**, that the CRGB arrays have at least the le This means if your LED channel from iCUE has 50 LEDs and you use the `repeat` function to control 100 physical LEDs you MUST declare the CRGB array at least with a length of 100. ## Increase the Brightness of the LEDs -By default iCUE only uses 50% of the LEDs brightness even if you set the brightness to max in the iCUE Device Settings. +When using LS100 or LT100 iCUE only uses 50% of the LEDs brightness even if you set the brightness to max in the iCUE Device Settings. But there are good news, we can increase the brightness with the Arduino so we can use the full brightness of our LEDs. -Add the `CLP::fixIcueBrightness` function to the `onUpdateHook` in the setup function as shown in the [example](examples/AdditionalFeatures/AdditionalFeatures.ino). +Add the `CLP::fixIcueBrightness` function to the `onUpdateHook` in the setup function as shown in the [example](examples/AmbientBacklight/AmbientBacklight.ino). If there are multiple functions called in `onUpdateHook`, `fixIcueBrightness` should be the first. +Only use this function with LS100 and LT100 devices! ```C++ ledController.onUpdateHook(0, []() { CLP::fixIcueBrightness(&ledController, 0); }); ``` +## Hardware Lighting mode +The [Hardware Lighting mode](https://forum.corsair.com/v3/showthread.php?t=182874) can be configured in iCUE. +It allows you the set lighting effects that will be active when iCUE **is not** running. +This is the case when the PC is off, in sleep mode, booting or the user is logged out. +So if you want to have lighing effects in all these situations, use the Hardware Lighting mode. +If you don't want it, configure a static black color. + # License This project is licensed under the Apache 2.0 License. diff --git a/examples/AdditionalFeatures/AdditionalFeatures.ino b/examples/AdditionalFeatures/AdditionalFeatures.ino index 5ba768c6..77b8af60 100644 --- a/examples/AdditionalFeatures/AdditionalFeatures.ino +++ b/examples/AdditionalFeatures/AdditionalFeatures.ino @@ -45,12 +45,6 @@ void setup() { FastLED.addLeds(ledsChannel2, 60); ledController.addLEDs(0, ledsChannel1, 60); ledController.addLEDs(1, ledsChannel2, 60); - - // modify the RGB values before they are shown on the LED strip - ledController.onUpdateHook(0, []() { - // increase the brightness of channel 1 when using iCUE, because iCUE only set brightness to max 50% - CLP::fixIcueBrightness(&ledController, 0); - }); } void loop() { @@ -58,5 +52,6 @@ void loop() { if (ledController.updateLEDs()) { FastLED.show(); + CLP::printFps(5000); } } diff --git a/examples/AmbientBacklight/AmbientBacklight.ino b/examples/AmbientBacklight/AmbientBacklight.ino index 2ba3548e..93fedd65 100644 --- a/examples/AmbientBacklight/AmbientBacklight.ino +++ b/examples/AmbientBacklight/AmbientBacklight.ino @@ -34,6 +34,8 @@ void setup() { ledController.addLEDs(0, ledsChannel1, 84); ledController.addLEDs(1, ledsChannel2, 105); ledController.onUpdateHook(0, []() { + // increase the brightness of channel 1 when using iCUE, because iCUE only set brightness to max 50% + CLP::fixIcueBrightness(&ledController, 0); // gamma correction with gamma value 2.0. Use napplyGamma_video for other gamma values. CLP::gammaCorrection(&ledController, 0); // napplyGamma_video(ledsChannel1, 84, 2.2); diff --git a/examples/DebugSketch/DebugSketch.ino b/examples/DebugSketch/DebugSketch.ino index 12b230bc..1a7281df 100644 --- a/examples/DebugSketch/DebugSketch.ino +++ b/examples/DebugSketch/DebugSketch.ino @@ -48,6 +48,7 @@ void loop() { if (ledController.updateLEDs()) { if (printUpdate) Serial.println(F("updateLEDs")); FastLED.show(); + CLP::printFps(5000); } if (Serial.available()) { diff --git a/examples/UnitTests/UnitTests.ino b/examples/UnitTests/UnitTests.ino index 7704ba5a..57a33458 100644 --- a/examples/UnitTests/UnitTests.ino +++ b/examples/UnitTests/UnitTests.ino @@ -28,6 +28,11 @@ protected: assertEqual(actual.g, expected.g); assertEqual(actual.b, expected.b); } + void assertCRGBArray(const CRGB* const leds, int from, int to, const CRGB& expected) { + for (int i = from; i <= to; i++) { + assertCRGB(leds[i], expected); + } + } }; test(getLEDs) { @@ -47,9 +52,7 @@ testF(FastLEDControllerTest, simpleScaleUp) { fill_solid(leds, 10, CRGB::White); CLP::scale(&ledController, 0, 20); - for (int i = 0; i < 10; i++) { - assertCRGB(leds[i], CRGB::White); - } + assertCRGBArray(leds, 0, 9, CRGB::White); } testF(FastLEDControllerTest, simpleScaleDown) { @@ -61,12 +64,23 @@ testF(FastLEDControllerTest, simpleScaleDown) { fill_solid(leds, 10, CRGB::White); CLP::scale(&ledController, 0, 10); - for (int i = 0; i < 5; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 5; i < 10; i++) { - assertCRGB(leds[i], CRGB::Black); - } + assertCRGBArray(leds, 0, 4, CRGB::White); + assertCRGBArray(leds, 5, 9, CRGB::Black); +} + +testF(FastLEDControllerTest, simpleScaleDownBoundaries) { + CRGB leds[20]; + FastLEDController ledController(false); + fill_solid(leds, 20, CRGB::Black); + ledController.addLEDs(0, leds, 20); + + leds[0] = CRGB::White; + leds[19] = CRGB::Red; + CLP::scale(&ledController, 0, 5); + + assertCRGBArray(leds, 0, 0, CRGB::White); + assertCRGBArray(leds, 1, 3, CRGB::Black); + assertCRGBArray(leds, 4, 4, CRGB::Red); } testF(FastLEDControllerTest, simpleScaleIdentity) { @@ -78,12 +92,20 @@ testF(FastLEDControllerTest, simpleScaleIdentity) { fill_solid(leds, 10, CRGB::White); CLP::scale(&ledController, 0, 10); - for (int i = 0; i < 10; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 10; i < 20; i++) { - assertCRGB(leds[i], CRGB::Black); - } + assertCRGBArray(leds, 0, 9, CRGB::White); + assertCRGBArray(leds, 10, 19, CRGB::Black); +} + +testF(FastLEDControllerTest, scaleLongStrip) { + CRGB leds[41]; + FastLEDController ledController(false); + fill_solid(leds, 41, CRGB::Black); + ledController.addLEDs(0, leds, 27); + + fill_solid(leds, 27, CRGB::White); + CLP::scale(&ledController, 0, 41); + + assertCRGBArray(leds, 0, 40, CRGB::White); } testF(FastLEDControllerTest, LT100) { @@ -97,27 +119,21 @@ testF(FastLEDControllerTest, LT100) { CLP::SegmentScaling segments[2] = {{1, 4}, {26, 26}}; CLP::scaleSegments(&ledController, 0, segments, 2); - for (int i = 0; i < 4; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 4; i < 30; i++) { - assertCRGB(leds[i], CRGB::Blue); - } + assertCRGBArray(leds, 0, 3, CRGB::White); + assertCRGBArray(leds, 4, 29, CRGB::Blue); } testF(FastLEDControllerTest, singleSegmentScaleUp) { CRGB leds[20]; FastLEDController ledController(false); fill_solid(leds, 20, CRGB::Black); - ledController.addLEDs(0, leds, 20); + ledController.addLEDs(0, leds, 10); fill_solid(leds, 10, CRGB::White); CLP::SegmentScaling segments[] = {{10, 20}}; CLP::scaleSegments(&ledController, 0, segments, 1); - for (int i = 0; i < 20; i++) { - assertCRGB(leds[i], CRGB::White); - } + assertCRGBArray(leds, 0, 19, CRGB::White); } testF(FastLEDControllerTest, multiScaleUp) { @@ -130,12 +146,8 @@ testF(FastLEDControllerTest, multiScaleUp) { CLP::SegmentScaling segments[] = {{5, 10}, {5, 20}}; CLP::scaleSegments(&ledController, 0, segments, 2); - for (int i = 0; i < 10; i++) { - assertCRGB(leds[i], CRGB::Black); - } - for (int i = 10; i < 30; i++) { - assertCRGB(leds[i], CRGB::White); - } + assertCRGBArray(leds, 0, 9, CRGB::Black); + assertCRGBArray(leds, 10, 29, CRGB::White); } testF(FastLEDControllerTest, multiScaleDown) { @@ -148,12 +160,8 @@ testF(FastLEDControllerTest, multiScaleDown) { CLP::SegmentScaling segments[] = {{10, 5}, {20, 5}}; CLP::scaleSegments(&ledController, 0, segments, 2); - for (int i = 0; i < 5; i++) { - assertCRGB(leds[i], CRGB::Black); - } - for (int i = 5; i < 10; i++) { - assertCRGB(leds[i], CRGB::White); - } + assertCRGBArray(leds, 0, 4, CRGB::Black); + assertCRGBArray(leds, 5, 9, CRGB::White); } testF(FastLEDControllerTest, singleSegmentScaleDown) { @@ -166,12 +174,8 @@ testF(FastLEDControllerTest, singleSegmentScaleDown) { CLP::SegmentScaling segments[] = {{20, 10}}; CLP::scaleSegments(&ledController, 0, segments, 1); - for (int i = 0; i < 5; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 5; i < 10; i++) { - assertCRGB(leds[i], CRGB::Black); - } + assertCRGBArray(leds, 0, 4, CRGB::White); + assertCRGBArray(leds, 5, 9, CRGB::Black); } testF(FastLEDControllerTest, SegmentScaleOverlap) { @@ -184,12 +188,8 @@ testF(FastLEDControllerTest, SegmentScaleOverlap) { CLP::SegmentScaling segments[] = {{5, 10}, {10, 5}}; CLP::scaleSegments(&ledController, 0, segments, 2); - for (int i = 0; i < 10; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 10; i < 15; i++) { - assertCRGB(leds[i], CRGB::Black); - } + assertCRGBArray(leds, 0, 9, CRGB::White); + assertCRGBArray(leds, 10, 14, CRGB::Black); } testF(FastLEDControllerTest, SegmentScaleOverlapInverted) { @@ -202,12 +202,8 @@ testF(FastLEDControllerTest, SegmentScaleOverlapInverted) { CLP::SegmentScaling segments[] = {{10, 5}, {5, 10}}; CLP::scaleSegments(&ledController, 0, segments, 2); - for (int i = 0; i < 5; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 5; i < 15; i++) { - assertCRGB(leds[i], CRGB::Black); - } + assertCRGBArray(leds, 0, 4, CRGB::White); + assertCRGBArray(leds, 5, 14, CRGB::Black); } testF(FastLEDControllerTest, SegmentScaleMix) { @@ -222,15 +218,9 @@ testF(FastLEDControllerTest, SegmentScaleMix) { CLP::SegmentScaling segments[] = {{5, 10}, {20, 5}, {5, 10}}; CLP::scaleSegments(&ledController, 0, segments, 3); - for (int i = 0; i < 10; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 10; i < 15; i++) { - assertCRGB(leds[i], CRGB::Red); - } - for (int i = 15; i < 25; i++) { - assertCRGB(leds[i], CRGB::Blue); - } + assertCRGBArray(leds, 0, 9, CRGB::White); + assertCRGBArray(leds, 10, 14, CRGB::Red); + assertCRGBArray(leds, 15, 24, CRGB::Blue); } testF(FastLEDControllerTest, SegmentScaleMixInverted) { @@ -245,15 +235,41 @@ testF(FastLEDControllerTest, SegmentScaleMixInverted) { CLP::SegmentScaling segments[] = {{10, 5}, {5, 20}, {10, 5}}; CLP::scaleSegments(&ledController, 0, segments, 3); - for (int i = 0; i < 5; i++) { - assertCRGB(leds[i], CRGB::White); - } - for (int i = 5; i < 25; i++) { - assertCRGB(leds[i], CRGB::Red); - } - for (int i = 25; i < 30; i++) { - assertCRGB(leds[i], CRGB::Blue); - } + assertCRGBArray(leds, 0, 4, CRGB::White); + assertCRGBArray(leds, 5, 24, CRGB::Red); + assertCRGBArray(leds, 25, 29, CRGB::Blue); +} + +testF(FastLEDControllerTest, SegmentScaleMonitor) { + CRGB leds[130]; + FastLEDController ledController(false); + fill_solid(leds, 130, CRGB::Black); + ledController.addLEDs(0, leds, 84); + + fill_solid(leds, 15, CRGB::White); + fill_solid(leds + 15, 27, CRGB::Red); + fill_solid(leds + 42, 15, CRGB::Blue); + fill_solid(leds + 57, 27, CRGB::Green); + CLP::SegmentScaling segments[] = {{15, 24}, {27, 41}, {15, 24}, {27, 41}}; + CLP::scaleSegments(&ledController, 0, segments, 4); + + assertCRGBArray(leds, 0, 23, CRGB::White); + assertCRGBArray(leds, 24, 64, CRGB::Red); + assertCRGBArray(leds, 65, 88, CRGB::Blue); + assertCRGBArray(leds, 89, 129, CRGB::Green); +} + +testF(FastLEDControllerTest, SegmentScaleLongStrip) { + CRGB leds[41]; + FastLEDController ledController(false); + fill_solid(leds, 41, CRGB::Black); + ledController.addLEDs(0, leds, 27); + + fill_solid(leds, 27, CRGB::White); + CLP::SegmentScaling segments[] = {{27, 41}}; + CLP::scaleSegments(&ledController, 0, segments, 1); + + assertCRGBArray(leds, 0, 40, CRGB::White); } void setup() { diff --git a/extra/doxygen.conf b/extra/doxygen.conf index 7df7e6e1..326f5c86 100644 --- a/extra/doxygen.conf +++ b/extra/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Corsair Lighting Protocol" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.14.1 +PROJECT_NUMBER = 0.14.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/library.properties b/library.properties index a3d67dc1..42785776 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Corsair Lighting Protocol -version=0.14.1 +version=0.14.2 author=Leon Kiefer maintainer=Leon Kiefer sentence=Control LED strips via USB from a PC. diff --git a/src/CLPUtils.cpp b/src/CLPUtils.cpp index 5140ddea..fbde0dd6 100644 --- a/src/CLPUtils.cpp +++ b/src/CLPUtils.cpp @@ -47,3 +47,20 @@ void CLP::printDeviceID(const uint8_t* deviceId) { if (i < 3) Serial.print(F(" ")); } } + +void CLP::printFps(const int interval) { + // Create static variables so that the code and variables can + // all be declared inside a function + static unsigned long lastMillis; + static unsigned long frameCount; + + unsigned long now = millis(); + frameCount++; + if (now - lastMillis >= interval) { + double framesPerSecond = (frameCount * 1000.0) / interval; + Serial.print(F("FPS: ")); + Serial.println(framesPerSecond, 1); + frameCount = 0; + lastMillis = now; + } +} diff --git a/src/CLPUtils.h b/src/CLPUtils.h index 43b2788c..005d0f3e 100644 --- a/src/CLPUtils.h +++ b/src/CLPUtils.h @@ -61,4 +61,12 @@ void disableBuildInLEDs(); * @param deviceId the device id to print */ void printDeviceID(const uint8_t* deviceId); + +/* + * Measure and print the framerate at the given interval in milliseconds. The higher this value the more precise the + * result will be. This function should be called after FastLED.show() to count the FPS. + * + * @param interval the measurement interval in milliseconds + */ +void printFps(const int interval); } // namespace CLP diff --git a/src/FastLEDControllerUtils.cpp b/src/FastLEDControllerUtils.cpp index 4f867f85..270f841b 100644 --- a/src/FastLEDControllerUtils.cpp +++ b/src/FastLEDControllerUtils.cpp @@ -34,26 +34,26 @@ void CLP::transformLLFanToStrip(FastLEDController* controller, uint8_t channelIn } /** - * Instead of scaling the zero based index we must scale the one based position. - * So, we first add 1 to the index, scale it, round it up to the next integer - * and then substract 1 to get the index. - * * @param index the index which should be scaled * @param scaleFactor the factor for the scaling * @return the scaled index */ -int scaleIndexAsPosition(int index, const float scaleFactor) { return ceil((index + 1) * scaleFactor) - 1; } +inline int scaleIndex(const int index, const float scaleFactor) { return round(index * scaleFactor); } + +float scaleFactorOf(const int numberLEDsBefore, const int numberLEDsAfter) { + return (float)(numberLEDsBefore - 1) / (numberLEDsAfter - 1); +} void CLP::scale(FastLEDController* controller, uint8_t channelIndex, int scaleToSize) { auto leds = controller->getLEDs(channelIndex); - const float scaleFactor = (float)controller->getLEDCount(channelIndex) / scaleToSize; + const float scaleFactor = scaleFactorOf(controller->getLEDCount(channelIndex), scaleToSize); if (scaleFactor < 1.0f) { for (int ledIndex = scaleToSize - 1; ledIndex >= 0; ledIndex--) { - leds[ledIndex] = leds[scaleIndexAsPosition(ledIndex, scaleFactor)]; + leds[ledIndex] = leds[scaleIndex(ledIndex, scaleFactor)]; } } else { for (int ledIndex = 0; ledIndex < scaleToSize; ledIndex++) { - leds[ledIndex] = leds[scaleIndexAsPosition(ledIndex, scaleFactor)]; + leds[ledIndex] = leds[scaleIndex(ledIndex, scaleFactor)]; } } } @@ -78,11 +78,11 @@ void CLP::scaleSegments(FastLEDController* controller, uint8_t channelIndex, con for (int i = 0; i < segmentsCount; i++) { const int segmentLength = segments[i].segmentLength; const int scaleToSize = min(segments[i].scaleToSize, segmentLength); - const float scaleFactor = (float)segmentLength / scaleToSize; + const float scaleFactor = scaleFactorOf(segmentLength, scaleToSize); for (int ledIndex = 0; ledIndex < scaleToSize; ledIndex++) { leds[ledStripIndexAfterScaling + ledIndex] = - leds[ledStripIndexBeforeScaling + scaleIndexAsPosition(ledIndex, scaleFactor)]; + leds[ledStripIndexBeforeScaling + scaleIndex(ledIndex, scaleFactor)]; } ledStripIndexAfterScaling += scaleToSize; ledStripIndexBeforeScaling += segmentLength; @@ -95,12 +95,12 @@ void CLP::scaleSegments(FastLEDController* controller, uint8_t channelIndex, con ledStripIndexAfterScaling = totalLengthAfterScaling; // scale up segments beginning with the last segment to not override other segments for (int i = segmentsCount - 1; i >= 0; i--) { - const float scaleFactor = (float)downScaledSegments[i].segmentLength / downScaledSegments[i].scaleToSize; + const float scaleFactor = scaleFactorOf(downScaledSegments[i].segmentLength, downScaledSegments[i].scaleToSize); ledStripIndexAfterScaling -= downScaledSegments[i].scaleToSize; ledStripIndexBeforeScaling -= downScaledSegments[i].segmentLength; for (int ledIndex = downScaledSegments[i].scaleToSize - 1; ledIndex >= 0; ledIndex--) { leds[ledStripIndexAfterScaling + ledIndex] = - leds[ledStripIndexBeforeScaling + scaleIndexAsPosition(ledIndex, scaleFactor)]; + leds[ledStripIndexBeforeScaling + scaleIndex(ledIndex, scaleFactor)]; } } } diff --git a/src/FastLEDControllerUtils.h b/src/FastLEDControllerUtils.h index f694331e..54db2e68 100644 --- a/src/FastLEDControllerUtils.h +++ b/src/FastLEDControllerUtils.h @@ -91,8 +91,9 @@ void reverse(FastLEDController* controller, uint8_t channelIndex); void gammaCorrection(FastLEDController* controller, uint8_t channelIndex); /** - * Increase the brightness of a LED channel when using iCUE Software lighting, because iCUE only send the RGB value in - * the range (0 - 127) which is only 50% of max possible brightness. This function doubles the received RGB value. + * Increase the brightness of a LED channel when using LS100 and LT100 with iCUE Software lighting, because iCUE only + * send the RGB value in the range (0 - 127) which is only 50% of max possible brightness. This function doubles the + * received RGB value. Only use this function with LS100 and LT100. * * @param controller the FastLEDController controlling the LEDs * @param channelIndex the index of the channel