diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml index f3a7483..c59765f 100644 --- a/.github/workflows/push-master.yml +++ b/.github/workflows/push-master.yml @@ -1,4 +1,4 @@ -name: HyperSerialEsp8266 CI Build +name: HyperSerialESP32 CI Build on: [push] diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aca9b5c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "algorithm": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md index b6630e0..c922b94 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,17 @@ Why the data integrity check was introduced which causes incompatibility with ot Recommend to use [esphome-flasher](https://github.com/esphome/esphome-flasher/releases) -For **RGBW LED strip** like RGBW SK6812 NEUTRAL white choose: *firmware_SK6812_RGBW_NEUTRAL.bin* +ESP32-S2 lolin mini requires special firmware version (also provided) + +Generic ESP32: + +For **RGBW LED strip** like RGBW SK6812 NEUTRAL white choose: *firmware_esp32_SK6812_RGBW_NEUTRAL.bin* -For **RGBW LED strip** like RGBW SK6812 COLD white choose: *firmware_SK6812_RGBW_COLD.bin* +For **RGBW LED strip** like RGBW SK6812 COLD white choose: *firmware_esp32_SK6812_RGBW_COLD.bin* -For **RGB LED strip** like WS8212b or RGB SK6812 variant choose: *firmware_WS281x_RGB.bin* +For **RGB LED strip** like WS8212b or RGB SK6812 variant choose: *firmware_esp32_WS281x_RGB.bin* -For **SPI driven RGB LED strip** APA102: *firmware_SPI_APA102_SK9822_HD107.bin*, WS8201: *firmware_SPI_WS2801.bin* +For **SPI driven RGB LED strip** APA102: *firmware_esp32_SPI_APA102_SK9822_HD107.bin*, WS8201: *firmware_esp32_SPI_WS2801.bin* If you want to disable your first LED because it's used as a sacrificial level shifter, please use [HyperHDR v19](https://github.com/awawa-dev/HyperHDR/pull/379) @@ -38,23 +42,64 @@ For the RGBW firmware the white channel is automatically calculated and R,G,B ch # Usage in HyperHDR -Make sure you set "Refresh time" to zero, "Baudrate" to 2000000 and enabled HyperHDR's AWA protocol. -Enabling "White channel calibration" is optional, if you want to fine tune the white channel balance of your sk6812 RGBW LED strip. +Make sure you are using HyperHDR v19beta2 or above. +Set `Refresh time` to zero, `Baudrate` to 2000000 and you enabled `HyperHDR's AWA protocol`. +Enabling `White channel calibration` is optional, if you want to fine tune the white channel balance of your sk6812 RGBW LED strip. +`ESP8266/ESP32 handshake` could help you to properly initialize the ESP device and enables statistics available in the logs (you must stop the LED device first to get them). + +![obraz](https://user-images.githubusercontent.com/69086569/207109594-0493fe58-3530-46bb-a0a3-31a110475ed6.png) -![obraz](https://user-images.githubusercontent.com/69086569/192893595-324cfcf8-e247-438c-88ce-e52a29463121.png) # Compiling Currently we use PlatformIO to compile the project. Install [Visual Studio Code](https://code.visualstudio.com/) and add [PlatformIO plugin](https://platformio.org/). This environment will take care of everything and compile the firmware for you. -But there is also an alternative and an easier way. Just fork the project and enable its Github Action. Use the online editor to make changes to the ```platformio.ini``` file, for example change default pin-outs, and save it. Github Action will compile new firmware automatically in the Artifacts archive. It has never been so easy! +But there is also an alternative and an easier way. Just fork the project and enable its Github Action. Use the online editor to make changes to the ```platformio.ini``` file, for example change default pin-outs/speed or enable multi-segments support, and save it. Github Action will compile new firmware automatically in the Artifacts archive. It has never been so easy! + +Tutorial: https://github.com/awawa-dev/HyperSerialESP32/wiki # Pinout **ESP32:** **LED output (non-SPI):** GPIO 2 -**LED output (SPI):** GPIO 0 for Clock, GPIO 2 for Data +**LED output (SPI):** GPIO 4 for Clock, GPIO 2 for Data + +# Some benchmark results + +ESP32 MH-ET LIVE mini is capable of 4Mb serial port speed and ESP32-S2 lolin mini is capable of 5Mb. But to give equal chances all models were tested using the default speed of 2Mb. + +## Multi-segments can double your large sk6812/ws2812b setup refresh rate for free. All you need is to properly project & construct the LED strip and use HyperSerialESP32 v8. + +| LED strip / Device | ESP32
MH-ET LIVE mini | +|----------------------------------------------------------------------------------|--------------------------| +| 300LEDs
Refresh rate/continues output=100Hz
SECOND_SEGMENT_START_INDEX=150 | 93-97 | +| 600LEDs
Refresh rate/continues output=100Hz
SECOND_SEGMENT_START_INDEX=300 | 78-79 | +| 900LEDs
Refresh rate/continues output=100Hz
SECOND_SEGMENT_START_INDEX=450 | 55-56 | + +## Comparing v6.1 and v8 version (single segment) refresh rate using MH-ET LIVE mini + +| LED strip / Device | ESP32
MH-ET LIVE mini
HyperSerialESP32 v6.1 | ESP32
MH-ET LIVE mini
HyperSerialESP32 v8 | +|------------------------------------------------|---------------------------------------------------|-------------------------------------------------| +| 300LEDs
Refresh rate/continues output=100Hz | 81-83 | 80-83 | +| 600LEDs
Refresh rate/continues output=60Hz | 39-40 | 41-42 | +| 900LEDs
Refresh rate/continues output=40Hz | 21-26 | 26-28 | + +## Comparing v6.1 and v8 version (single segment) refresh rate using generic ESP32 with CH340C + +| LED strip / Device | ESP32 (CH340C)
HyperSerialESP32 v6.1 | ESP32 (CH340C)
HyperSerialESP32 v8 | +|------------------------------------------------|-----------------------------------------|---------------------------------------| +| 300LEDs
Refresh rate/continues output=100Hz | 72-78 | 81-83 | +| 600LEDs
Refresh rate/continues output=60Hz | 33-38 | 39-42 | +| 900LEDs
Refresh rate/continues output=40Hz | 21-25 | 26-28 | + +## ESP32-S2 lolin mini performance + +| LED strip / Device | ESP32-S2 lolin mini
HyperSerialESP32 v8 | +|------------------------------------------------|--------------------------------------------| +| 300LEDs
Refresh rate/continues output=100Hz | 80-84 | +| 600LEDs
Refresh rate/continues output=60Hz | 42 | +| 900LEDs
Refresh rate/continues output=40Hz | 27-28 | # Disclaimer diff --git a/include/README b/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/include/README +++ /dev/null @@ -1,39 +0,0 @@ - -This directory is intended for project header files. - -A header file is a file containing C declarations and macro definitions -to be shared between several project source files. You request the use of a -header file in your project source file (C, C++, etc) located in `src` folder -by including it, with the C preprocessing directive `#include'. - -```src/main.c - -#include "header.h" - -int main (void) -{ - ... -} -``` - -Including a header file produces the same results as copying the header file -into each source file that needs it. Such copying would be time-consuming -and error-prone. With a header file, the related declarations appear -in only one place. If they need to be changed, they can be changed in one -place, and programs that include the header file will automatically use the -new version when next recompiled. The header file eliminates the labor of -finding and changing all the copies as well as the risk that a failure to -find one copy will result in inconsistencies within a program. - -In C, the usual convention is to give header files names that end with `.h'. -It is most portable to use only letters, digits, dashes, and underscores in -header file names, and at most one dot. - -Read more about using header files in official GCC documentation: - -* Include Syntax -* Include Operation -* Once-Only Headers -* Computed Includes - -https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/base.h b/include/base.h new file mode 100644 index 0000000..22b1caa --- /dev/null +++ b/include/base.h @@ -0,0 +1,174 @@ +/* base.h +* +* MIT License +* +* Copyright (c) 2022 awawa-dev +* +* https://github.com/awawa-dev/HyperSerialESP32 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#ifndef BASE_H +#define BASE_H + +#if defined(SECOND_SEGMENT_START_INDEX) + #if !defined(SECOND_SEGMENT_DATA_PIN) + #error "Please define SECOND_SEGMENT_DATA_PIN for second segment" + #elif !defined(SECOND_SEGMENT_CLOCK_PIN) && !defined(NEOPIXEL_RGBW) && !defined(NEOPIXEL_RGB) + #error "Please define SECOND_SEGMENT_CLOCK_PIN and SECOND_SEGMENT_DATA_PIN for second segment" + #endif +#endif + +class Base +{ + // LED strip number + int ledsNumber = 0; + // NeoPixelBusLibrary primary object + LED_DRIVER* ledStrip1 = nullptr; + // NeoPixelBusLibrary second object + LED_DRIVER2* ledStrip2 = nullptr; + // frame is set and ready to render + bool readyToRender = false; + + public: + // static data buffer for the loop + uint8_t buffer[MAX_BUFFER]; + // handle to tasks + TaskHandle_t processTaskHandle; + // current queue position + uint16_t queueCurrent = 0; + // queue end position + volatile uint16_t queueEnd = 0; + + inline int getLedsNumber() + { + return ledsNumber; + } + + inline LED_DRIVER* getLedStrip1() + { + return ledStrip1; + } + + inline LED_DRIVER2* getLedStrip2() + { + return ledStrip2; + } + + void initLedStrip(int count) + { + if (ledStrip1 != nullptr) + { + delete ledStrip1; + ledStrip1 = nullptr; + } + + if (ledStrip2 != nullptr) + { + delete ledStrip2; + ledStrip2 = nullptr; + } + + ledsNumber = count; + + #if defined(SECOND_SEGMENT_START_INDEX) + if (ledsNumber > SECOND_SEGMENT_START_INDEX) + { + #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB) + ledStrip1 = new LED_DRIVER(SECOND_SEGMENT_START_INDEX, DATA_PIN); + ledStrip1->Begin(); + ledStrip2 = new LED_DRIVER2(ledsNumber - SECOND_SEGMENT_START_INDEX, SECOND_SEGMENT_DATA_PIN); + ledStrip2->Begin(); + #else + ledStrip1 = new LED_DRIVER(SECOND_SEGMENT_START_INDEX); + ledStrip1->Begin(CLOCK_PIN, 12, DATA_PIN, 15); + ledStrip2 = new LED_DRIVER2(ledsNumber - SECOND_SEGMENT_START_INDEX); + ledStrip2->Begin(SECOND_SEGMENT_CLOCK_PIN, 12, SECOND_SEGMENT_DATA_PIN, 15); + #endif + } + #endif + + if (ledStrip1 == nullptr) + { + #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB) + ledStrip1 = new LED_DRIVER(ledsNumber, DATA_PIN); + ledStrip1->Begin(); + #else + ledStrip1 = new LED_DRIVER(ledsNumber); + ledStrip1->Begin(CLOCK_PIN, 12, DATA_PIN, 15); + #endif + } + } + + /** + * @brief Check if there is already prepared frame to display + * + * @return true + * @return false + */ + inline bool hasLateFrameToRender() + { + return readyToRender; + } + + inline void renderLeds(bool newFrame) + { + if (newFrame) + readyToRender = true; + + if (readyToRender && + (ledStrip1 != nullptr && ledStrip1->CanShow()) && + !(ledStrip2 != nullptr && !ledStrip2->CanShow())) + { + statistics.increaseShow(); + readyToRender = false; + + // display segments + ledStrip1->Show(false); + if (ledStrip2 != nullptr) + ledStrip2->Show(false); + } + } + + inline bool setStripPixel(uint16_t pix, ColorDefinition &inputColor) + { + if (pix < ledsNumber) + { + #if defined(SECOND_SEGMENT_START_INDEX) + if (pix < SECOND_SEGMENT_START_INDEX) + ledStrip1->SetPixelColor(pix, inputColor); + else + { + #if defined(SECOND_SEGMENT_REVERSED) + ledStrip2->SetPixelColor(ledsNumber - pix - 1, inputColor); + #else + ledStrip2->SetPixelColor(pix - SECOND_SEGMENT_START_INDEX, inputColor); + #endif + } + #else + ledStrip1->SetPixelColor(pix, inputColor); + #endif + } + + return (pix + 1 < ledsNumber); + } +} base; + +#endif \ No newline at end of file diff --git a/include/calibration.h b/include/calibration.h index 71c742e..d520837 100644 --- a/include/calibration.h +++ b/include/calibration.h @@ -25,7 +25,14 @@ * SOFTWARE. */ -#ifndef CALIBRATION_H +#ifdef NEOPIXEL_RGBW + typedef RgbwColor ColorDefinition; +#else + typedef RgbColor ColorDefinition; +#endif + + +#if !defined(CALIBRATION_H) && (defined(NEOPIXEL_RGBW) || defined(HYPERSERIAL_TESTING)) #define CALIBRATION_H #include @@ -33,40 +40,33 @@ #define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom)) -struct { - uint8_t white[256]; - uint8_t red[256]; - uint8_t green[256]; - uint8_t blue[256]; +struct +{ + uint8_t white[256]; + uint8_t red[256]; + uint8_t green[256]; + uint8_t blue[256]; } channelCorrection; -struct { - uint8_t gain = 0xFF; - #ifdef COLD_WHITE - uint8_t red = 0xA0; // adjust red -> white in 0-0xFF range - uint8_t green = 0xA0; // adjust green -> white in 0-0xFF range - uint8_t blue = 0xA0; // adjust blue -> white in 0-0xFF range - #else - uint8_t red = 0xB0; // adjust red -> white in 0-0xFF range - uint8_t green = 0xB0; // adjust green -> white in 0-0xFF range - uint8_t blue = 0x70; // adjust blue -> white in 0-0xFF range - #endif - - void setParams(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue) - { - gain = _gain; - red = _red; - green = _green; - blue = _blue; - } +class CalibrationConfig +{ + // calibration parameters + uint8_t gain = 0xFF; + uint8_t red = 0xA0; + uint8_t green = 0xA0; + uint8_t blue = 0xA0; + /** + * @brief Build the LUT table using provided parameters + * + */ void prepareCalibration() { // prepare LUT calibration table, cold white is much better than "neutral" white for (uint32_t i = 0; i < 256; i++) { // color calibration - uint32_t _gain = uint32_t(gain) * i; // adjust gain + uint32_t _gain = gain * i; // adjust gain uint32_t _red = red * i; // adjust red uint32_t _green = green * i; // adjust green uint32_t _blue = blue * i; // adjust blue @@ -77,6 +77,59 @@ struct { channelCorrection.blue[i] = (uint8_t)std::min(ROUND_DIVIDE(_blue, 0xFF), (uint32_t)0xFF); } } + + public: + CalibrationConfig() + { + prepareCalibration(); + } + + /** + * @brief Compare base calibration settings + * + */ + bool compareCalibrationSettings(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue) + { + return _gain == gain && _red == red && _green == green && _blue == blue; + } + + /** + * @brief Set the parameters that define RGB to RGBW transformation + * + * @param _gain + * @param _red + * @param _green + * @param _blue + */ + void setParamsAndPrepareCalibration(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue) + { + if (gain != _gain || red != _red || green != _green || blue != _blue) + { + gain = _gain; + red = _red; + green = _green; + blue = _blue; + prepareCalibration(); + } + } + + /** + * @brief print RGBW calibration parameters when no data is received + * + */ + void printCalibration() + { + #ifdef SerialPort + SerialPort.write("\r\nRGBW => Gain: "); + SerialPort.print(gain); + SerialPort.write("/255, red: "); + SerialPort.print(red); + SerialPort.write(" , green: "); + SerialPort.print(green); + SerialPort.write(" , blue: "); + SerialPort.print(blue); + #endif + } } calibrationConfig; #endif diff --git a/include/framestate.h b/include/framestate.h new file mode 100644 index 0000000..b5f51bc --- /dev/null +++ b/include/framestate.h @@ -0,0 +1,260 @@ +/* framestate.h +* +* MIT License +* +* Copyright (c) 2022 awawa-dev +* +* https://github.com/awawa-dev/HyperSerialESP32 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#ifndef FRAMESTATE_H +#define FRAMESTATE_H + +/** + * @brief my AWA frame protocol definition + * + */ +enum class AwaProtocol +{ + HEADER_A, + HEADER_w, + HEADER_a, + HEADER_HI, + HEADER_LO, + HEADER_CRC, + VERSION2_GAIN, + VERSION2_RED, + VERSION2_GREEN, + VERSION2_BLUE, + RED, + GREEN, + BLUE, + FLETCHER1, + FLETCHER2, + FLETCHER_EXT +}; + +/** + * @brief Contains current state of the incoming frame + * + */ +class +{ + AwaProtocol state = AwaProtocol::HEADER_A; + bool protocolVersion2 = false; + uint8_t CRC = 0; + uint16_t count = 0; + uint16_t currentLed = 0; + uint16_t fletcher1 = 0; + uint16_t fletcher2 = 0; + uint16_t fletcherExt = 0; + uint8_t position = 0; + + public: + ColorDefinition color; + + /** + * @brief Reset statistics for new frame + * + * @param input + */ + inline void init(byte input) + { + currentLed = 0; + count = input * 0x100; + CRC = input; + fletcher1 = 0; + fletcher2 = 0; + fletcherExt = 0; + position = 0; + } + + /** + * @brief get computed CRC + * + * @return uint8_t + */ + inline uint8_t getCRC() + { + return CRC; + } + + /** + * @brief Get the color count reported by the frame + * + * @return uint16_t + */ + inline uint16_t getCount() + { + return count; + } + + /** + * @brief Get the Fletcher1 total sum + * + * @return uint16_t + */ + inline uint16_t getFletcher1() + { + return fletcher1; + } + + /** + * @brief Get the Fletcher2 total sum + * + * @return uint16_t + */ + inline uint16_t getFletcher2() + { + return fletcher2; + } + + /** + * @brief Get the FletcherExt total sum + * + * @return uint16_t + */ + inline uint16_t getFletcherExt() + { + return (fletcherExt != 0x41) ? fletcherExt : 0xaa; + } + + /** + * @brief Get and increase the current Led index + * + * @return uint16_t + */ + inline uint16_t getCurrentLedIndex() + { + return currentLed++; + } + + /** + * @brief Set if frame protocol version 2 (contains calibration data) + * + * @param newVer + */ + inline void setProtocolVersion2(bool newVer) + { + protocolVersion2 = newVer; + } + + /** + * @brief Verify if frame protocol version 2 (contains calibration data) + * + * @return true + * @return false + */ + inline bool isProtocolVersion2() + { + return protocolVersion2; + } + + /** + * @brief Set new AWA frame state + * + * @param newState + */ + inline void setState(AwaProtocol newState) + { + state = newState; + } + + /** + * @brief Get current AWA frame state + * + * @return AwaProtocol + */ + inline AwaProtocol getState() + { + return state; + } + + /** + * @brief Update CRC based on current and previuos input + * + * @param input + */ + inline void computeCRC(byte input) + { + count += input; + CRC = CRC ^ input ^ 0x55; + } + + /** + * @brief Update Fletcher checksumn for incoming input + * + * @param input + */ + inline void addFletcher(byte input) + { + fletcher1 = (fletcher1 + (uint16_t)input) % 255; + fletcher2 = (fletcher2 + fletcher1) % 255; + fletcherExt = (fletcherExt + (input ^ (position++))) % 255; + } + + /** + * @brief Check if the calibration data was updated and calculate new one + * + */ + inline void updateIncomingCalibration() + { + #ifdef NEOPIXEL_RGBW + if (protocolVersion2) + { + calibrationConfig.setParamsAndPrepareCalibration(calibration.gain, calibration.red, calibration.green, calibration.blue); + } + #endif + } + + + #ifdef NEOPIXEL_RGBW + /** + * @brief Compute && correct the white channel + * + */ + inline void rgb2rgbw() + { + color.W = min(channelCorrection.red[color.R], + min(channelCorrection.green[color.G], + channelCorrection.blue[color.B])); + color.R -= channelCorrection.red[color.W]; + color.G -= channelCorrection.green[color.W]; + color.B -= channelCorrection.blue[color.W]; + color.W = channelCorrection.white[color.W]; + } + #endif + + /** + * @brief Incoming calibration data + * + */ + struct + { + uint8_t gain = 0; + uint8_t red = 0; + uint8_t green = 0; + uint8_t blue = 0; + } calibration; + +} frameState; + +#endif \ No newline at end of file diff --git a/include/main.h b/include/main.h new file mode 100644 index 0000000..fa16422 --- /dev/null +++ b/include/main.h @@ -0,0 +1,276 @@ +/* main.h +* +* MIT License +* +* Copyright (c) 2022 awawa-dev +* +* https://github.com/awawa-dev/HyperSerialESP32 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#ifndef MAIN_H +#define MAIN_H + +#define MAX_BUFFER 6000 +#define HELLO_MESSAGE "\r\nWelcome!\r\nAwa driver 8." + +#include "calibration.h" +#include "statistics.h" +#include "base.h" +#include "framestate.h" + +/** + * @brief separete thread on core 1 for handling serial communication using cyclic buffer + * + */ +void serialTaskHandler() +{ + uint16_t incomingSize = min(SerialPort.available(), MAX_BUFFER); + + if (incomingSize > 0) + { + if (base.queueEnd + incomingSize < MAX_BUFFER) + { + SerialPort.read(&(base.buffer[base.queueEnd]), incomingSize); + base.queueEnd += incomingSize; + } + else + { + int left = MAX_BUFFER - base.queueEnd; + SerialPort.read(&(base.buffer[base.queueEnd]), left); + SerialPort.read(&(base.buffer[0]), incomingSize - left); + base.queueEnd = incomingSize - left; + } + } +} + +/** + * @brief process received data on core 0 + * + */ +void processData() +{ + // update and print statistics + unsigned long currentTime = millis(); + unsigned long deltaTime = currentTime - statistics.getStartTime(); + + if (base.queueCurrent != base.queueEnd && deltaTime >= 1000) + { + statistics.update(currentTime); + } + else if (deltaTime >= 3000) + { + frameState.setState(AwaProtocol::HEADER_A); + statistics.print(currentTime, base.processTaskHandle); + vTaskDelay(50); + } + + // render waiting frame if available + if (base.hasLateFrameToRender() && frameState.getState() == AwaProtocol::HEADER_A) + base.renderLeds(false); + + // process received data + while (base.queueCurrent != base.queueEnd) + { + byte input = base.buffer[base.queueCurrent++]; + + if (base.queueCurrent >= MAX_BUFFER) + base.queueCurrent = 0; + + switch (frameState.getState()) + { + case AwaProtocol::HEADER_A: + // assume it's protocol version 1, verify it later + frameState.setProtocolVersion2(false); + if (input == 'A') + frameState.setState(AwaProtocol::HEADER_w); + break; + + case AwaProtocol::HEADER_w: + if (input == 'w') + frameState.setState(AwaProtocol::HEADER_a); + else + frameState.setState(AwaProtocol::HEADER_A); + break; + + case AwaProtocol::HEADER_a: + // detect protocol version + if (input == 'a') + frameState.setState(AwaProtocol::HEADER_HI); + else if (input == 'A') + { + frameState.setState(AwaProtocol::HEADER_HI); + frameState.setProtocolVersion2(true); + } + else + frameState.setState(AwaProtocol::HEADER_A); + break; + + case AwaProtocol::HEADER_HI: + // initialize new frame properties + statistics.increaseTotal(); + frameState.init(input); + frameState.setState(AwaProtocol::HEADER_LO); + break; + + case AwaProtocol::HEADER_LO: + frameState.computeCRC(input); + frameState.setState(AwaProtocol::HEADER_CRC); + break; + + case AwaProtocol::HEADER_CRC: + // verify CRC and create/update LED driver if neccesery + if (frameState.getCRC() == input) + { + uint16_t ledSize = frameState.getCount() + 1; + + // sanity check + if (ledSize > 4096) + frameState.setState(AwaProtocol::HEADER_A); + else + { + if (ledSize != base.getLedsNumber()) + base.initLedStrip(ledSize); + + frameState.setState(AwaProtocol::RED); + } + } + else if (frameState.getCount() == 0x2aa2 && (input == 0x15 || input == 0x35)) + { + if (input == 0x15) + { + SerialPort.println(HELLO_MESSAGE); + statistics.reset(deltaTime); + } + else + statistics.print(currentTime, base.processTaskHandle); + + frameState.setState(AwaProtocol::HEADER_A); + } + else + frameState.setState(AwaProtocol::HEADER_A); + break; + + case AwaProtocol::RED: + frameState.color.R = input; + frameState.addFletcher(input); + + frameState.setState(AwaProtocol::GREEN); + break; + + case AwaProtocol::GREEN: + frameState.color.G = input; + frameState.addFletcher(input); + + frameState.setState(AwaProtocol::BLUE); + break; + + case AwaProtocol::BLUE: + frameState.color.B = input; + frameState.addFletcher(input); + + #ifdef NEOPIXEL_RGBW + // calculate RGBW from RGB using provided calibration data + frameState.rgb2rgbw(); + #endif + + // set pixel, increase the index and check if it was the last LED color to come + if (base.setStripPixel(frameState.getCurrentLedIndex(), frameState.color)) + { + frameState.setState(AwaProtocol::RED); + } + else + { + if (frameState.isProtocolVersion2()) + frameState.setState(AwaProtocol::VERSION2_GAIN); + else + frameState.setState(AwaProtocol::FLETCHER1); + } + + break; + + case AwaProtocol::VERSION2_GAIN: + frameState.calibration.gain = input; + frameState.addFletcher(input); + + frameState.setState(AwaProtocol::VERSION2_RED); + break; + + case AwaProtocol::VERSION2_RED: + frameState.calibration.red = input; + frameState.addFletcher(input); + + frameState.setState(AwaProtocol::VERSION2_GREEN); + break; + + case AwaProtocol::VERSION2_GREEN: + frameState.calibration.green = input; + frameState.addFletcher(input); + + frameState.setState(AwaProtocol::VERSION2_BLUE); + break; + + case AwaProtocol::VERSION2_BLUE: + frameState.calibration.blue = input; + frameState.addFletcher(input); + + frameState.setState(AwaProtocol::FLETCHER1); + break; + + case AwaProtocol::FLETCHER1: + // initial frame data integrity check + if (input != frameState.getFletcher1()) + frameState.setState(AwaProtocol::HEADER_A); + else + frameState.setState(AwaProtocol::FLETCHER2); + break; + + case AwaProtocol::FLETCHER2: + // initial frame data integrity check + if (input != frameState.getFletcher2()) + frameState.setState(AwaProtocol::HEADER_A); + else + frameState.setState(AwaProtocol::FLETCHER_EXT); + break; + + case AwaProtocol::FLETCHER_EXT: + // final frame data integrity check + if (input == frameState.getFletcherExt()) + { + statistics.increaseGood(); + + base.renderLeds(true); + + #ifdef NEOPIXEL_RGBW + // if received the calibration data, update it now + if (frameState.isProtocolVersion2()) + { + frameState.updateIncomingCalibration(); + } + #endif + } + + frameState.setState(AwaProtocol::HEADER_A); + break; + } + } +} + +#endif \ No newline at end of file diff --git a/include/statistics.h b/include/statistics.h new file mode 100644 index 0000000..ffb1704 --- /dev/null +++ b/include/statistics.h @@ -0,0 +1,159 @@ +/* stats.h +* +* MIT License +* +* Copyright (c) 2022 awawa-dev +* +* https://github.com/awawa-dev/HyperSerialESP32 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#ifndef STATISTICS_H +#define STATISTICS_H + +// statistics (stats sent only when there is no communication) +class +{ + unsigned long startTime = 0; + uint16_t goodFrames = 0; + uint16_t showFrames = 0; + uint16_t totalFrames = 0; + uint16_t finalGoodFrames = 0; + uint16_t finalShowFrames = 0; + uint16_t finalTotalFrames = 0; + + public: + /** + * @brief Get the start time of the current period + * + * @return unsigned long + */ + inline unsigned long getStartTime() + { + return startTime; + } + + /** + * @brief Detected new frame + * + */ + inline void increaseTotal() + { + totalFrames++; + } + + /** + * @brief The frame is received and shown + * + */ + inline void increaseShow() + { + showFrames++; + } + + /** + * @brief The frame is received correctly (not yet displayed) + * + */ + inline void increaseGood() + { + goodFrames++; + } + + /** + * @brief Get number of correctly received frames + * + * @return uint16_t + */ + inline uint16_t getGoodFrames() + { + return goodFrames; + } + + /** + * @brief Period restart, save current statistics ans send them later if there is no incoming communication + * + * @param currentTime + */ + void update(unsigned long currentTime) + { + if (totalFrames > 0) + { + finalShowFrames = showFrames; + finalGoodFrames = std::min(goodFrames, totalFrames); + finalTotalFrames = totalFrames; + } + + startTime = currentTime; + goodFrames = 0; + totalFrames = 0; + showFrames = 0; + } + + /** + * @brief Print last saved statistics to the serial port + * + * @param curTime + * @param taskHandle + */ + void print(unsigned long curTime, TaskHandle_t taskHandle) + { + startTime = curTime; + goodFrames = 0; + totalFrames = 0; + showFrames = 0; + + SerialPort.write("\r\nHyperHDR frames: "); + SerialPort.print(finalShowFrames); + SerialPort.write(" (FPS), receiv.: "); + SerialPort.print(finalTotalFrames); + SerialPort.write(", good: "); + SerialPort.print(finalGoodFrames); + SerialPort.write(", incompl.: "); + SerialPort.print(finalTotalFrames - finalGoodFrames); + SerialPort.write(", mem: "); + SerialPort.print(uxTaskGetStackHighWaterMark(taskHandle)); + SerialPort.write(", heap: "); + SerialPort.print(ESP.getFreeHeap()); + #if defined(NEOPIXEL_RGBW) + calibrationConfig.printCalibration(); + #endif + + } + + /** + * @brief Reset statistics + * + */ + void reset(unsigned long currentTime) + { + startTime = currentTime; + + finalShowFrames = 0; + finalGoodFrames = 0; + finalTotalFrames = 0; + + goodFrames = 0; + totalFrames = 0; + showFrames = 0; + } +} statistics; + +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 8b9dcd1..1b44779 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,40 +1,138 @@ -; PlatformIO Project Configuration File +; Configurable parameters: +; SERIALCOM_SPEED = speed of the serial port (baud), global [env] section +; DATA_PIN = pin/GPIO for the LED strip data channel, specific [board] section +; CLOCK_PIN = pin/GPIO for the LED strip clock channel, specific [board] section ; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html +; MULTI-SEGMENT SUPPORT +; You can define second segment to handle. Add following parameters (with -D prefix to the build_flags sections). +; SECOND_SEGMENT_START_INDEX = start index of the second segment +; SECOND_SEGMENT_CLOCK_PIN = pin/GPIO for the second segment LED strip data channel +; SECOND_SEGMENT_DATA_PIN = pin/GPIO for the second segment LED strip clock channel +; SECOND_SEGMENT_REVERSED = if defined, the segment is reversed, the first LED of the second segment becomes the last +; For example: we have first LED strip with 512 leds and the second LED strip with 400 leds. +; For such configuration you need to set SECOND_SEGMENT_START_INDEX to 512 here, compile and upload the firmware. +; In HyperHDR you should have a single LED strip that contains 912 leds. +; SECOND_SEGMENT_REVERSED could be helpful if you start both segments in the middle of the bottom edge and join again at the top (both ends). +; Example build string for the second segment (append it to the global [env] or to the specific [board] section): +; build_flags = ... -DSECOND_SEGMENT_START_INDEX=512 -DSECOND_SEGMENT_DATA_PIN=4 -DSECOND_SEGMENT_REVERSED + + [platformio] -default_envs = SK6812_RGBW_COLD, SK6812_RGBW_NEUTRAL, WS281x_RGB, SPI_APA102_SK9822_HD107, SPI_WS2801 +default_envs = SK6812_RGBW_COLD, SK6812_RGBW_NEUTRAL, WS281x_RGB, SPI_APA102_SK9822_HD107, SPI_WS2801, s2_mini_SK6812_RGBW_COLD, s2_mini_SK6812_RGBW_NEUTRAL, s2_mini_WS281x_RGB, s2_mini_SPI_APA102_SK9822_HD107, s2_mini_SPI_WS2801 [env] -platform = espressif32@3.5.0 -board = esp32dev framework = arduino -lib_deps = makuna/NeoPixelBus@2.6.9 extra_scripts = pre:extra_script.py +build_flags = -DSERIALCOM_SPEED=2000000 -test_port = COM3 +;========================================================== +; ESP32 board +;========================================================== +[esp32] +platform = espressif32@3.5.0 +lib_deps = makuna/NeoPixelBus@2.6.9 +test_ignore = [env:SK6812_RGBW_COLD] -build_flags = -DNEOPIXEL_RGBW -DCOLD_WHITE -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2 -custom_prog_version = SK6812_RGBW_COLD +build_flags = -DNEOPIXEL_RGBW -DCOLD_WHITE -DDATA_PIN=2 ${env.build_flags} +custom_prog_version = esp32_SK6812_RGBW_COLD + +board = esp32dev +platform = ${esp32.platform} +lib_deps = ${esp32.lib_deps} +test_ignore = ${esp32.test_ignore} [env:SK6812_RGBW_NEUTRAL] -build_flags = -DNEOPIXEL_RGBW -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2 -custom_prog_version = SK6812_RGBW_NEUTRAL +build_flags = -DNEOPIXEL_RGBW -DDATA_PIN=2 ${env.build_flags} +custom_prog_version = esp32_SK6812_RGBW_NEUTRAL + +board = esp32dev +platform = ${esp32.platform} +lib_deps = ${esp32.lib_deps} +test_ignore = ${esp32.test_ignore} [env:WS281x_RGB] -build_flags = -DNEOPIXEL_RGB -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2 -custom_prog_version = WS281x_RGB +build_flags = -DNEOPIXEL_RGB -DDATA_PIN=2 ${env.build_flags} +custom_prog_version = esp32_WS281x_RGB + +board = esp32dev +platform = ${esp32.platform} +lib_deps = ${esp32.lib_deps} +test_ignore = ${esp32.test_ignore} [env:SPI_APA102_SK9822_HD107] -build_flags = -DSPILED_APA102 -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2 -DCLOCK_PIN=0 -custom_prog_version = SPI_APA102_SK9822_HD107 +build_flags = -DSPILED_APA102 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags} +custom_prog_version = esp32_SPI_APA102_SK9822_HD107 + +board = esp32dev +platform = ${esp32.platform} +lib_deps = ${esp32.lib_deps} +test_ignore = ${esp32.test_ignore} [env:SPI_WS2801] -build_flags = -DSPILED_WS2801 -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2 -DCLOCK_PIN=0 -custom_prog_version = SPI_WS2801 +build_flags = -DSPILED_WS2801 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags} +custom_prog_version = esp32_SPI_WS2801 + +board = esp32dev +platform = ${esp32.platform} +lib_deps = ${esp32.lib_deps} +test_ignore = ${esp32.test_ignore} + +;========================================================== +; ESP32-S2 board +;========================================================== +[esp32_lolin_s2_mini] +platform = espressif32@>=4.3.0 +lib_deps = makuna/NeoPixelBus@>=2.6.6 +test_ignore = * + +[env:s2_mini_SK6812_RGBW_COLD] +build_flags = -DNEOPIXEL_RGBW -DCOLD_WHITE -DDATA_PIN=2 ${env.build_flags} +custom_prog_version = esp32_s2_mini_SK6812_RGBW_COLD + +board = lolin_s2_mini +board_build.mcu = esp32s2 +platform = ${esp32_lolin_s2_mini.platform} +lib_deps = ${esp32_lolin_s2_mini.lib_deps} +test_ignore = ${esp32_lolin_s2_mini.test_ignore} + +[env:s2_mini_SK6812_RGBW_NEUTRAL] +build_flags = -DNEOPIXEL_RGBW -DDATA_PIN=2 ${env.build_flags} +custom_prog_version = esp32_s2_mini_SK6812_RGBW_NEUTRAL + +board = lolin_s2_mini +board_build.mcu = esp32s2 +platform = ${esp32_lolin_s2_mini.platform} +lib_deps = ${esp32_lolin_s2_mini.lib_deps} +test_ignore = ${esp32_lolin_s2_mini.test_ignore} + +[env:s2_mini_WS281x_RGB] +build_flags = -DNEOPIXEL_RGB -DDATA_PIN=2 ${env.build_flags} +custom_prog_version = esp32_s2_mini_WS281x_RGB + +board = lolin_s2_mini +board_build.mcu = esp32s2 +platform = ${esp32_lolin_s2_mini.platform} +lib_deps = ${esp32_lolin_s2_mini.lib_deps} +test_ignore = ${esp32_lolin_s2_mini.test_ignore} + +[env:s2_mini_SPI_APA102_SK9822_HD107] +build_flags = -DSPILED_APA102 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags} +custom_prog_version = esp32_s2_mini_SPI_APA102_SK9822_HD107 + +board = lolin_s2_mini +board_build.mcu = esp32s2 +platform = ${esp32_lolin_s2_mini.platform} +lib_deps = ${esp32_lolin_s2_mini.lib_deps} +test_ignore = ${esp32_lolin_s2_mini.test_ignore} + +[env:s2_mini_SPI_WS2801] +build_flags = -DSPILED_WS2801 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags} +custom_prog_version = esp32_s2_mini_SPI_WS2801 + +board = lolin_s2_mini +board_build.mcu = esp32s2 +platform = ${esp32_lolin_s2_mini.platform} +lib_deps = ${esp32_lolin_s2_mini.lib_deps} +test_ignore = ${esp32_lolin_s2_mini.test_ignore} + diff --git a/src/main.cpp b/src/main.cpp index c76275d..cbfb29a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,19 +27,20 @@ #include #include -#ifdef NEOPIXEL_RGBW - #include "calibration.h" - void printCalibration(); -#endif -#if ESP_ARDUINO_VERSION_MAJOR == 2 && ESP_ARDUINO_VERSION_MINOR ==0 && ESP_ARDUINO_VERSION_PATCH <= 5 +#if !defined(ARDUINO_LOLIN_S2_MINI) && ESP_ARDUINO_VERSION_MAJOR == 2 && ESP_ARDUINO_VERSION_MINOR ==0 && ESP_ARDUINO_VERSION_PATCH <= 5 #error "Arduino ESP32 versions 2.0.0-2.0.5 are unsupported." #endif -// DO NOT EDIT THIS FILE. ADJUST THE CONFIGURATION IN THE platformio.ini +/////////////////////////////////////////////////////////////////////////// +// DO NOT EDIT THIS FILE. ADJUST THE CONFIGURATION IN THE platformio.ini // +/////////////////////////////////////////////////////////////////////////// + #define _STR(x) #x #define _XSTR(x) _STR(x) #define VAR_NAME_VALUE(var) #var " = " _XSTR(var) +#define _XSTR2(x,y) _STR(x) _STR(y) +#define VAR_NAME_VALUE2(var) #var " = " _XSTR2(var) #ifdef NEOPIXEL_RGBW #pragma message(VAR_NAME_VALUE(NEOPIXEL_RGBW)) @@ -58,389 +59,111 @@ #endif #pragma message(VAR_NAME_VALUE(SERIALCOM_SPEED)) -#ifdef NEOPIXEL_RGBW - #define LED_DRIVER NeoPixelBus -#elif NEOPIXEL_RGB - #define LED_DRIVER NeoPixelBus -#elif SPILED_APA102 +#if defined(ARDUINO_LOLIN_S2_MINI) + #ifdef NEOPIXEL_RGBW + #define LED_DRIVER NeoPixelBus + #elif NEOPIXEL_RGB + #define LED_DRIVER NeoPixelBus + #endif +#else + #ifdef NEOPIXEL_RGBW + #define LED_DRIVER NeoPixelBus + #elif NEOPIXEL_RGB + #define LED_DRIVER NeoPixelBus + #endif +#endif + +#ifdef SPILED_APA102 #define LED_DRIVER NeoPixelBus #elif SPILED_WS2801 #define LED_DRIVER NeoPixelBus #endif -// STATS (sent only when there is no communication) -struct -{ - unsigned long start = 0; - uint16_t goodFrames = 0; - uint16_t totalFrames = 0; - uint16_t finalGoodFrames = 0; - uint16_t finalTotalFrames = 0; - - void update(unsigned long curTime) - { - if (totalFrames > 0 && totalFrames >= goodFrames) - { - finalGoodFrames = goodFrames; - finalTotalFrames = totalFrames; - } - - start = curTime; - goodFrames = 0; - totalFrames = 0; - } - - void print(unsigned long curTime) - { - start = curTime; - goodFrames = 0; - totalFrames = 0; - - Serial.write("\r\nLast HyperHDR stats. Frames: "); - Serial.print(finalTotalFrames); - Serial.write(", good: "); - Serial.print(finalGoodFrames); - Serial.write("(FPS), incompl.: "); - Serial.print(finalTotalFrames - finalGoodFrames); +#pragma message(VAR_NAME_VALUE(DATA_PIN)) +#ifdef CLOCK_PIN + #pragma message(VAR_NAME_VALUE(CLOCK_PIN)) +#endif +#pragma message(VAR_NAME_VALUE2(LED_DRIVER)) - #if defined(NEOPIXEL_RGBW) - printCalibration(); +#if defined(SECOND_SEGMENT_START_INDEX) + #if defined(ARDUINO_LOLIN_S2_MINI) + #ifdef NEOPIXEL_RGBW + #define LED_DRIVER2 NeoPixelBus + #elif NEOPIXEL_RGB + #define LED_DRIVER2 NeoPixelBus + #elif SPILED_APA102 + #define LED_DRIVER2 NeoPixelBus + #elif SPILED_WS2801 + #define LED_DRIVER2 NeoPixelBus #endif - - } -} stats; - -#define MAX_BUFFER 4096 -struct -{ - // LED strip number - int ledsNumber = 0; - // Makuna NeoPixelBusLibrary object - LED_DRIVER *ledStrip = NULL; - // want to render a frame? - bool wantShow = false; - // static data buffer for the loop - uint8_t buffer[MAX_BUFFER]; - - void initLedStrip(int count) - { - if (ledStrip != NULL) - delete ledStrip; - - ledsNumber = count; - #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB) - ledStrip = new LED_DRIVER(ledsNumber, DATA_PIN); - ledStrip->Begin(); - #else - ledStrip = new LED_DRIVER(ledsNumber); - ledStrip->Begin(CLOCK_PIN, 12, DATA_PIN, 15); + #else + #ifdef NEOPIXEL_RGBW + #define LED_DRIVER2 NeoPixelBus + #elif NEOPIXEL_RGB + #define LED_DRIVER2 NeoPixelBus + #elif SPILED_APA102 + #define LED_DRIVER2 NeoPixelBus + #elif SPILED_WS2801 + #define LED_DRIVER2 NeoPixelBus #endif - - } - - inline void renderLeds() - { - if (wantShow && ledStrip != NULL && ledStrip->CanShow()) - { - stats.goodFrames++; - wantShow = false; - ledStrip->Show(); - } - } -} base; - -// CALIBRATION & COLORSPACE RELATED STUFF -#ifdef NEOPIXEL_RGBW - - RgbwColor inputColor; - - void printCalibration() - { - Serial.write("\r\nRGBW => Gain: "); - Serial.print(calibrationConfig.gain); - Serial.write("/255, red: "); - Serial.print(calibrationConfig.red); - Serial.write(" , green: "); - Serial.print(calibrationConfig.green); - Serial.write(" , blue: "); - Serial.print(calibrationConfig.blue); - } - - inline void setStripPixel(uint16_t pix, RgbwColor &inputColor) - { - if (pix < base.ledsNumber) - { - base.ledStrip->SetPixelColor(pix, inputColor); - } - } - -#else - - RgbColor inputColor; - - inline void setStripPixel(uint16_t pix, RgbColor &inputColor) - { - if (pix < base.ledsNumber) - { - base.ledStrip->SetPixelColor(pix, inputColor); - } - } - + #endif + #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_START_INDEX)) + #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_DATA_PIN)) + #ifdef SECOND_SEGMENT_CLOCK_PIN + #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_CLOCK_PIN)) + #endif + #pragma message(VAR_NAME_VALUE2(LED_DRIVER2)) +#else + class LED_DRIVER2 { + public: + bool CanShow() {return true;} + void Show(bool safe) {} + }; #endif -// PROTOCOL DEFINITION -enum class AwaProtocol -{ - HEADER_A, - HEADER_w, - HEADER_a, - HEADER_HI, - HEADER_LO, - HEADER_CRC, - VERSION2_GAIN, - VERSION2_RED, - VERSION2_GREEN, - VERSION2_BLUE, - RED, - GREEN, - BLUE, - FLETCHER1, - FLETCHER2 -}; +#define SerialPort Serial +#include "main.h" -// AWA FRAME PROPERTIES -struct -{ - AwaProtocol state = AwaProtocol::HEADER_A; - bool protocolVersion2 = false; - uint8_t CRC = 0; - uint16_t count = 0; - uint16_t currentLed = 0; - uint16_t fletcher1 = 0; - uint16_t fletcher2 = 0; - - inline void init(byte input) - { - currentLed = 0; - count = input * 0x100; - CRC = input; - fletcher1 = 0; - fletcher2 = 0; - } - - inline void addFletcher(byte input) - { - fletcher1 = (fletcher1 + (uint16_t)input) % 255; - fletcher2 = (fletcher2 + fletcher1) % 255; - } -} frameState; - -// INCOMING CALIBRATION DATA -struct -{ - uint8_t gain = 0; - uint8_t red = 0; - uint8_t green = 0; - uint8_t blue = 0; -} incoming; - -void readSerialData() +/** + * @brief separete thread for handling incoming data using cyclic buffer + * + * @param parameters + */ +void processTask(void * parameters) { - unsigned long curTime = millis(); - uint16_t bufferPointer = 0; - uint16_t internalIndex = min(Serial.available(), MAX_BUFFER); - - if (internalIndex > 0) - internalIndex = Serial.read(base.buffer, internalIndex); - - if (internalIndex > 0 && curTime - stats.start > 1000) + for(;;) { - stats.update(curTime); - } - else if (curTime - stats.start > 5000) - { - stats.print(curTime); - } - - if (frameState.state == AwaProtocol::HEADER_A) - base.renderLeds(); - - while (bufferPointer < internalIndex) - { - byte input = base.buffer[bufferPointer++]; - switch (frameState.state) - { - case AwaProtocol::HEADER_A: - frameState.protocolVersion2 = false; - if (input == 'A') - frameState.state = AwaProtocol::HEADER_w; - break; - - case AwaProtocol::HEADER_w: - if (input == 'w') - frameState.state = AwaProtocol::HEADER_a; - else - frameState.state = AwaProtocol::HEADER_A; - break; - - case AwaProtocol::HEADER_a: - if (input == 'a') - frameState.state = AwaProtocol::HEADER_HI; - else if (input == 'A') - { - frameState.state = AwaProtocol::HEADER_HI; - frameState.protocolVersion2 = true; - } - else - frameState.state = AwaProtocol::HEADER_A; - break; - - case AwaProtocol::HEADER_HI: - stats.totalFrames++; - frameState.init(input); - frameState.state = AwaProtocol::HEADER_LO; - break; - - case AwaProtocol::HEADER_LO: - frameState.count += input; - frameState.CRC = frameState.CRC ^ input ^ 0x55; - frameState.state = AwaProtocol::HEADER_CRC; - break; - - case AwaProtocol::HEADER_CRC: - if (frameState.CRC == input) - { - if (frameState.count + 1 != base.ledsNumber) - base.initLedStrip(frameState.count + 1); - - frameState.state = AwaProtocol::RED; - } - else - frameState.state = AwaProtocol::HEADER_A; - break; - - case AwaProtocol::RED: - inputColor.R = input; - frameState.addFletcher(input); - - frameState.state = AwaProtocol::GREEN; - break; - - case AwaProtocol::GREEN: - inputColor.G = input; - frameState.addFletcher(input); - - frameState.state = AwaProtocol::BLUE; - break; - - case AwaProtocol::BLUE: - inputColor.B = input; - frameState.addFletcher(input); - - #ifdef NEOPIXEL_RGBW - inputColor.W = min(channelCorrection.red[inputColor.R], - min(channelCorrection.green[inputColor.G], - channelCorrection.blue[inputColor.B])); - inputColor.R -= channelCorrection.red[inputColor.W]; - inputColor.G -= channelCorrection.green[inputColor.W]; - inputColor.B -= channelCorrection.blue[inputColor.W]; - inputColor.W = channelCorrection.white[inputColor.W]; - #endif - - setStripPixel(frameState.currentLed++, inputColor); - - if (frameState.count-- > 0) - frameState.state = AwaProtocol::RED; - else - { - if (frameState.protocolVersion2) - frameState.state = AwaProtocol::VERSION2_GAIN; - else - frameState.state = AwaProtocol::FLETCHER1; - } - - break; - - case AwaProtocol::VERSION2_GAIN: - incoming.gain = input; - frameState.addFletcher(input); - - frameState.state = AwaProtocol::VERSION2_RED; - break; - - case AwaProtocol::VERSION2_RED: - incoming.red = input; - frameState.addFletcher(input); - - frameState.state = AwaProtocol::VERSION2_GREEN; - break; - - case AwaProtocol::VERSION2_GREEN: - incoming.green = input; - frameState.addFletcher(input); - - frameState.state = AwaProtocol::VERSION2_BLUE; - break; - - case AwaProtocol::VERSION2_BLUE: - incoming.blue = input; - frameState.addFletcher(input); - - frameState.state = AwaProtocol::FLETCHER1; - break; - - case AwaProtocol::FLETCHER1: - if (input != frameState.fletcher1) - frameState.state = AwaProtocol::HEADER_A; - else - frameState.state = AwaProtocol::FLETCHER2; - break; - - case AwaProtocol::FLETCHER2: - if (input == frameState.fletcher2) - { - base.wantShow = true; - base.renderLeds(); - - #ifdef NEOPIXEL_RGBW - if (frameState.protocolVersion2) - { - if (calibrationConfig.red != incoming.red || calibrationConfig.green != incoming.green || - calibrationConfig.blue != incoming.blue || calibrationConfig.gain != incoming.gain) - { - calibrationConfig.setParams(incoming.gain, incoming.red, incoming.green, incoming.blue); - calibrationConfig.prepareCalibration(); - } - } - #endif - } - - frameState.state = AwaProtocol::HEADER_A; - break; - } + processData(); + delay(1); } } void setup() { // Init serial port - Serial.setRxBufferSize(2048); + Serial.setRxBufferSize(MAX_BUFFER); + Serial.setTimeout(50); Serial.begin(SERIALCOM_SPEED); - Serial.setTimeout(50); + while (!Serial) continue; // Display config - Serial.println("\r\nWelcome!\r\nAwa driver 7."); + Serial.println(HELLO_MESSAGE); + #if defined(SECOND_SEGMENT_START_INDEX) + SerialPort.write("SECOND_SEGMENT_START_INDEX = "); + SerialPort.println(SECOND_SEGMENT_START_INDEX); + #endif // Colorspace/Led type info #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB) #ifdef NEOPIXEL_RGBW #ifdef COLD_WHITE + calibrationConfig.setParamsAndPrepareCalibration(0xFF, 0xA0, 0xA0, 0xA0); Serial.println("NeoPixelBus SK6812 cold GRBW."); #else + calibrationConfig.setParamsAndPrepareCalibration(0xFF, 0xB0, 0xB0, 0x70); Serial.println("NeoPixelBus SK6812 neutral GRBW."); #endif - calibrationConfig.prepareCalibration(); - printCalibration(); + calibrationConfig.printCalibration(); #else Serial.println("NeoPixelBus ws281x type (GRB)."); #endif @@ -449,9 +172,23 @@ void setup() #elif defined(SPILED_WS2801) Serial.println("SPI WS2801 (RBG)."); #endif + + Serial.flush(); + delay(50); + + // create new task for handling received serial data on core 0 + xTaskCreatePinnedToCore( + processTask, + "processTask", + 4096, + NULL, + 5, + &base.processTaskHandle, + 0); } void loop() { - readSerialData(); + serialTaskHandler(); } + diff --git a/test/README b/test/README deleted file mode 100644 index 9b1e87b..0000000 --- a/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PlatformIO Test Runner and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/test/test_MultiSegment/main.cpp b/test/test_MultiSegment/main.cpp new file mode 100644 index 0000000..9e21e54 --- /dev/null +++ b/test/test_MultiSegment/main.cpp @@ -0,0 +1,419 @@ +/* test_MultiSegment/main.cpp +* +* MIT License +* +* Copyright (c) 2022 awawa-dev +* +* https://github.com/awawa-dev/HyperSerialESP32 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#define NO_GLOBAL_SERIAL +#define HYPERSERIAL_TESTING + +#include +#include +#include +#include "calibration.h" + +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////// AWA PROTOCOL CORRECTNESS TEST ///////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// + +#define TEST_LEDS_NUMBER 1025 +uint8_t _ledBuffer[TEST_LEDS_NUMBER * 3 + 6 + 8]; + +#define LED_DRIVER ProtocolTester +#define LED_DRIVER2 ProtocolTester +#define SECOND_SEGMENT_START_INDEX 513 +#define SECOND_SEGMENT_CLOCK_PIN 100 +#define SECOND_SEGMENT_DATA_PIN 101 + +/** + * @brief Mockup Serial class to simulate the real communition + * + */ + +class SerialTester +{ + int frameSize = 0; + int sent = 0; + + public: + + void createTestFrame(bool _white_channel_calibration, uint8_t _white_channel_limit = 0, + uint8_t _white_channel_red = 0, uint8_t _white_channel_green = 0, + uint8_t _white_channel_blue = 0) + { + _ledBuffer[0] = 'A'; + _ledBuffer[1] = 'w'; + _ledBuffer[2] = (_white_channel_calibration) ? 'A' : 'a'; + _ledBuffer[4] = (TEST_LEDS_NUMBER-1) & 0xff; + _ledBuffer[3] = ((TEST_LEDS_NUMBER-1) >> 8) & 0xff; + _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; + + uint8_t* writer = &(_ledBuffer[6]); + uint8_t* hasher = writer; + + for(int i=0; i < TEST_LEDS_NUMBER; i++) + { + *(writer++)=random(255); + *(writer++)=random(255); + *(writer++)=random(255); + } + + + if (_white_channel_calibration) + { + *(writer++) = _white_channel_limit; + *(writer++) = _white_channel_red; + *(writer++) = _white_channel_green; + *(writer++) = _white_channel_blue; + } + + uint16_t fletcher1 = 0, fletcher2 = 0, fletcherExt = 0; + uint8_t position = 0; + while (hasher < writer) + { + fletcherExt = (fletcherExt + (*(hasher) ^ (position++))) % 255; + fletcher1 = (fletcher1 + *(hasher++)) % 255; + fletcher2 = (fletcher2 + fletcher1) % 255; + } + *(writer++) = (uint8_t)fletcher1; + *(writer++) = (uint8_t)fletcher2; + *(writer++) = (uint8_t)((fletcherExt != 0x41) ? fletcherExt : 0xaa); + + frameSize = (int)(writer - _ledBuffer); + sent = 0; + } + + + inline size_t write(const char * s) + { + return 0; + } + + inline size_t print(unsigned char, int = DEC) + { + return 0; + } + + int available(void) + { + if (sent < frameSize) + { + return std::min(std::max((int)(random(64)), 1), frameSize - sent); + } + + return 0; + } + + int toSend(void) + { + return frameSize - sent; + } + + int getFrameSize() + { + return frameSize; + } + + size_t read(uint8_t *buffer, size_t size) + { + int max = std::min(frameSize - sent, (int)size); + if (max > 0) + { + memcpy(buffer, &(_ledBuffer[sent]), max); + sent += max; + return max; + } + return 0; + } + + void println(const String &s) + { + + } +} SerialPort; + + +/** + * @brief Mockup LED driver to verify correctness of the received LEDs color values + * + */ + +class ProtocolTester { + int ledCount; + int currentIndex; + int lastCount; + bool first; + + public: + ProtocolTester(int _count, int _pin) : ProtocolTester(_count) + { + if (_pin == SECOND_SEGMENT_DATA_PIN) + first = false; + } + + ProtocolTester(int _count) + { + first = true; + ledCount = _count; + currentIndex = 0; + lastCount = 0; + } + + bool CanShow() + { + return true; + } + + void Show(bool safe = true) + { + lastCount = currentIndex; + currentIndex = 0; + } + + void Begin() + { + + } + + void Begin(int _pin1, int _pin2, int _pin3, int _pin4) + { + if (_pin1 == SECOND_SEGMENT_CLOCK_PIN) + first = false; + } + + int getLastCount() + { + return lastCount; + } + + /** + * @brief Very important: verify LED color, compare it to the origin + * + * @param indexPixel + * @param color + */ + #ifdef NEOPIXEL_RGBW + void SetPixelColor(uint16_t indexPixel, RgbwColor color) + { + TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index"); + TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); + + uint8_t *c = (first) ? &(_ledBuffer[6 + indexPixel * 3]) : &(_ledBuffer[6 + (indexPixel + SECOND_SEGMENT_START_INDEX) * 3]); + uint8_t r = *(c++); + uint8_t g = *(c++); + uint8_t b = *(c++); + + uint8_t w = min(channelCorrection.red[r], + min(channelCorrection.green[g], + channelCorrection.blue[b])); + r -= channelCorrection.red[w]; + g -= channelCorrection.green[w]; + b -= channelCorrection.blue[w]; + w = channelCorrection.white[w]; + + TEST_ASSERT_EQUAL_UINT8(r, color.R); + TEST_ASSERT_EQUAL_UINT8(g, color.G); + TEST_ASSERT_EQUAL_UINT8(b, color.B); + TEST_ASSERT_EQUAL_UINT8(w, color.W); + + currentIndex = indexPixel + 1; + lastCount = 0; + } + #else + void SetPixelColor(uint16_t indexPixel, RgbColor color) + { + TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index"); + TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); + + uint8_t *c = (first) ? &(_ledBuffer[6 + indexPixel * 3]) : &(_ledBuffer[6 + (indexPixel + SECOND_SEGMENT_START_INDEX) * 3]); + uint8_t r = *(c++); + uint8_t g = *(c++); + uint8_t b = *(c++); + + TEST_ASSERT_EQUAL_UINT8(r, color.R); + TEST_ASSERT_EQUAL_UINT8(g, color.G); + TEST_ASSERT_EQUAL_UINT8(b, color.B); + + currentIndex = indexPixel + 1; + lastCount = 0; + } + #endif +}; + +#include "main.h" + + + +/** + * @brief Send RGBW calibration data and verify it all (including proper colors rendering) + * + */ +void MultiSegmentTest_SendRgbwCalibration() +{ + // set all calibration values to test + SerialPort.createTestFrame(true, 10, 20, 30, 40); + base.queueCurrent = 0; + base.queueEnd = 0; + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); + + // should not change if the frame doesnt contain calibration data + SerialPort.createTestFrame(false); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); + + // last test + SerialPort.createTestFrame(true, 255, 128, 128, 128); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(255,128,128,128), "Incorrect calibration result"); +} + +/** + * @brief Send 100 RGB/RGBW frames and verify it all (including proper colors rendering) + * + */ +void MultiSegmentTest_Send100Frames() +{ + base.queueCurrent = 0; + base.queueEnd = 0; + + for(int i = 0; i < 100; i++) + { + SerialPort.createTestFrame(false); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + } +} + +/** + * @brief Send 200 RGB/RGBW valid/invalid frames and verify it all (including proper colors rendering) + * + */ +void MultiSegmentTest_Send200UncertainFrames() +{ + base.queueCurrent = 0; + base.queueEnd = 0; + + for(int i = 0; i < 200; i++) + { + SerialPort.createTestFrame(false); + statistics.update(0); + + bool damaged = (random(255) % 2) == 0; + int index; + uint8_t backup; + if (damaged) + { + index = random(SerialPort.getFrameSize()); + backup = _ledBuffer[index]; + _ledBuffer[index] = backup ^ 0xff; + } + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + if (damaged) + { + char buffer[128]; + snprintf(buffer, sizeof(buffer), "Damaged frame was received: [%d]=>%d", index, backup); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), buffer); + base.getLedStrip1()->Show(); + base.getLedStrip2()->Show(); + frameState.setState(AwaProtocol::HEADER_A); + } + else + { + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// UNIT TEST ROUTINES ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// + +void setup() +{ + delay(1000); + randomSeed(analogRead(0)); + UNITY_BEGIN(); + #ifdef NEOPIXEL_RGBW + RUN_TEST(MultiSegmentTest_SendRgbwCalibration); + #endif + RUN_TEST(MultiSegmentTest_Send100Frames); + RUN_TEST(MultiSegmentTest_Send200UncertainFrames); + UNITY_END(); +} + +void loop() +{ +} diff --git a/test/test_MultiSegmentReversed/main.cpp b/test/test_MultiSegmentReversed/main.cpp new file mode 100644 index 0000000..71a0ab5 --- /dev/null +++ b/test/test_MultiSegmentReversed/main.cpp @@ -0,0 +1,424 @@ +/* test_MultiSegmentReversed/main.cpp +* +* MIT License +* +* Copyright (c) 2022 awawa-dev +* +* https://github.com/awawa-dev/HyperSerialESP32 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#define NO_GLOBAL_SERIAL +#define HYPERSERIAL_TESTING + +#include +#include +#include +#include "calibration.h" + +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////// AWA PROTOCOL CORRECTNESS TEST ///////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// + +#define TEST_LEDS_NUMBER 1025 +uint8_t _ledBuffer[TEST_LEDS_NUMBER * 3 + 6 + 8]; + +#define LED_DRIVER ProtocolTester +#define LED_DRIVER2 ProtocolTester +#define SECOND_SEGMENT_START_INDEX 513 +#define SECOND_SEGMENT_CLOCK_PIN 100 +#define SECOND_SEGMENT_DATA_PIN 101 +#define SECOND_SEGMENT_REVERSED + +/** + * @brief Mockup Serial class to simulate the real communition + * + */ + +class SerialTester +{ + int frameSize = 0; + int sent = 0; + + public: + + void createTestFrame(bool _white_channel_calibration, uint8_t _white_channel_limit = 0, + uint8_t _white_channel_red = 0, uint8_t _white_channel_green = 0, + uint8_t _white_channel_blue = 0) + { + _ledBuffer[0] = 'A'; + _ledBuffer[1] = 'w'; + _ledBuffer[2] = (_white_channel_calibration) ? 'A' : 'a'; + _ledBuffer[4] = (TEST_LEDS_NUMBER-1) & 0xff; + _ledBuffer[3] = ((TEST_LEDS_NUMBER-1) >> 8) & 0xff; + _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; + + uint8_t* writer = &(_ledBuffer[6]); + uint8_t* hasher = writer; + + for(int i=0; i < TEST_LEDS_NUMBER; i++) + { + *(writer++)=random(255); + *(writer++)=random(255); + *(writer++)=random(255); + } + + + if (_white_channel_calibration) + { + *(writer++) = _white_channel_limit; + *(writer++) = _white_channel_red; + *(writer++) = _white_channel_green; + *(writer++) = _white_channel_blue; + } + + uint16_t fletcher1 = 0, fletcher2 = 0, fletcherExt = 0; + uint8_t position = 0; + while (hasher < writer) + { + fletcherExt = (fletcherExt + (*(hasher) ^ (position++))) % 255; + fletcher1 = (fletcher1 + *(hasher++)) % 255; + fletcher2 = (fletcher2 + fletcher1) % 255; + } + *(writer++) = (uint8_t)fletcher1; + *(writer++) = (uint8_t)fletcher2; + *(writer++) = (uint8_t)((fletcherExt != 0x41) ? fletcherExt : 0xaa); + + frameSize = (int)(writer - _ledBuffer); + sent = 0; + } + + + inline size_t write(const char * s) + { + return 0; + } + + inline size_t print(unsigned char, int = DEC) + { + return 0; + } + + int available(void) + { + if (sent < frameSize) + { + return std::min(std::max((int)(random(64)), 1), frameSize - sent); + } + + return 0; + } + + int toSend(void) + { + return frameSize - sent; + } + + int getFrameSize() + { + return frameSize; + } + + size_t read(uint8_t *buffer, size_t size) + { + int max = std::min(frameSize - sent, (int)size); + if (max > 0) + { + memcpy(buffer, &(_ledBuffer[sent]), max); + sent += max; + return max; + } + return 0; + } + + void println(const String &s) + { + + } +} SerialPort; + + +/** + * @brief Mockup LED driver to verify correctness of the received LEDs color values + * + */ + +class ProtocolTester { + int ledCount; + int currentIndex; + int lastCount; + bool first; + + public: + ProtocolTester(int _count, int _pin) : ProtocolTester(_count) + { + if (_pin == SECOND_SEGMENT_DATA_PIN) + first = false; + } + + ProtocolTester(int _count) + { + first = true; + ledCount = _count; + currentIndex = 0; + lastCount = 0; + } + + bool CanShow() + { + return true; + } + + void Show(bool safe = true) + { + lastCount = currentIndex; + currentIndex = 0; + } + + void Begin() + { + + } + + void Begin(int _pin1, int _pin2, int _pin3, int _pin4) + { + if (_pin1 == SECOND_SEGMENT_CLOCK_PIN) + first = false; + } + + int getLastCount() + { + return lastCount; + } + + /** + * @brief Very important: verify LED color, compare it to the origin + * + * @param indexPixel + * @param color + */ + #ifdef NEOPIXEL_RGBW + void SetPixelColor(uint16_t indexPixel, RgbwColor color) + { + if (!first) + TEST_ASSERT_EQUAL_INT_MESSAGE(ledCount - currentIndex - 1, indexPixel, "Unexpected LED index"); + TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); + if (!first) + indexPixel = TEST_LEDS_NUMBER - 1 - indexPixel; + uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]); + uint8_t r = *(c++); + uint8_t g = *(c++); + uint8_t b = *(c++); + + uint8_t w = min(channelCorrection.red[r], + min(channelCorrection.green[g], + channelCorrection.blue[b])); + r -= channelCorrection.red[w]; + g -= channelCorrection.green[w]; + b -= channelCorrection.blue[w]; + w = channelCorrection.white[w]; + + TEST_ASSERT_EQUAL_UINT8(r, color.R); + TEST_ASSERT_EQUAL_UINT8(g, color.G); + TEST_ASSERT_EQUAL_UINT8(b, color.B); + TEST_ASSERT_EQUAL_UINT8(w, color.W); + + currentIndex++; + lastCount = 0; + } + #else + void SetPixelColor(uint16_t indexPixel, RgbColor color) + { + if (!first) + TEST_ASSERT_EQUAL_INT_MESSAGE(ledCount - currentIndex - 1, indexPixel, "Unexpected LED index"); + TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); + if (!first) + indexPixel = TEST_LEDS_NUMBER - 1 - indexPixel; + uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]); + uint8_t r = *(c++); + uint8_t g = *(c++); + uint8_t b = *(c++); + + TEST_ASSERT_EQUAL_UINT8(r, color.R); + TEST_ASSERT_EQUAL_UINT8(g, color.G); + TEST_ASSERT_EQUAL_UINT8(b, color.B); + + currentIndex++; + lastCount = 0; + } + #endif +}; + +#include "main.h" + + + +/** + * @brief Send RGBW calibration data and verify it all (including proper colors rendering) + * + */ +void MultiSegmentReversedTest_SendRgbwCalibration() +{ + // set all calibration values to test + SerialPort.createTestFrame(true, 10, 20, 30, 40); + base.queueCurrent = 0; + base.queueEnd = 0; + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); + + // should not change if the frame doesnt contain calibration data + SerialPort.createTestFrame(false); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); + + // last test + SerialPort.createTestFrame(true, 255, 128, 128, 128); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(255,128,128,128), "Incorrect calibration result"); +} + +/** + * @brief Send 100 RGB/RGBW frames and verify it all (including proper colors rendering) + * + */ +void MultiSegmentReversedTest_Send100Frames() +{ + base.queueCurrent = 0; + base.queueEnd = 0; + + for(int i = 0; i < 100; i++) + { + SerialPort.createTestFrame(false); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + } +} + +/** + * @brief Send 200 RGB/RGBW valid/invalid frames and verify it all (including proper colors rendering) + * + */ +void MultiSegmentReversedTest_Send200UncertainFrames() +{ + base.queueCurrent = 0; + base.queueEnd = 0; + + for(int i = 0; i < 200; i++) + { + SerialPort.createTestFrame(false); + statistics.update(0); + + bool damaged = (random(255) % 2) == 0; + int index; + uint8_t backup; + if (damaged) + { + index = random(SerialPort.getFrameSize()); + backup = _ledBuffer[index]; + _ledBuffer[index] = backup ^ 0xff; + } + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + if (damaged) + { + char buffer[128]; + snprintf(buffer, sizeof(buffer), "Damaged frame was received: [%d]=>%d", index, backup); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), buffer); + base.getLedStrip1()->Show(); + base.getLedStrip2()->Show(); + frameState.setState(AwaProtocol::HEADER_A); + } + else + { + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)"); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// UNIT TEST ROUTINES ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// + +void setup() +{ + delay(1000); + randomSeed(analogRead(0)); + UNITY_BEGIN(); + #ifdef NEOPIXEL_RGBW + RUN_TEST(MultiSegmentReversedTest_SendRgbwCalibration); + #endif + RUN_TEST(MultiSegmentReversedTest_Send100Frames); + RUN_TEST(MultiSegmentReversedTest_Send200UncertainFrames); + UNITY_END(); +} + +void loop() +{ +} diff --git a/test/test_SingleSegment/main.cpp b/test/test_SingleSegment/main.cpp new file mode 100644 index 0000000..99c074c --- /dev/null +++ b/test/test_SingleSegment/main.cpp @@ -0,0 +1,506 @@ +/* test_SingleSegment/main.cpp +* +* MIT License +* +* Copyright (c) 2022 awawa-dev +* +* https://github.com/awawa-dev/HyperSerialESP32 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#define NO_GLOBAL_SERIAL +#define HYPERSERIAL_TESTING + +#include +#include +#include +#include "calibration.h" + +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////// WHITE CHANNEL CALIBRATION TEST //////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// + + +// old calibration params +float whiteLimit; +uint8_t rCorrection; +uint8_t gCorrection; +uint8_t bCorrection; + +uint8_t wChannel[256]; +uint8_t rChannel[256]; +uint8_t gChannel[256]; +uint8_t bChannel[256]; + +/** + * @brief Old calibration procedure + * + * @return void + */ +void oldCalibration() +{ + for (uint32_t i = 0; i < 256; i++) + { + // color calibration + float red = rCorrection * i; // adjust red + float green = gCorrection * i; // adjust green + float blue = bCorrection * i; // adjust blue + + wChannel[i] = (uint8_t)round(std::min(whiteLimit * i, 255.0f)); + rChannel[i] = (uint8_t)round(std::min(red / 0xFF, 255.0f)); + gChannel[i] = (uint8_t)round(std::min(green / 0xFF, 255.0f)); + bChannel[i] = (uint8_t)round(std::min(blue / 0xFF, 255.0f)); + } +} + +/** + * @brief Compare old and new calibration result + * + * @return void + */ +void compareLut() +{ + for (uint32_t i = 0; i < 256; i++) + { + int w = std::abs(int(wChannel[i]) - int(channelCorrection.white[i])); + int r = std::abs(int(rChannel[i]) - int(channelCorrection.red[i])); + int g = std::abs(int(gChannel[i]) - int(channelCorrection.green[i])); + int b = std::abs(int(bChannel[i]) - int(channelCorrection.blue[i])); + TEST_ASSERT_LESS_THAN(1, (int)(std::max(w, std::max(r, std::max(g, b))))); + } +} + +/** + * @brief Calculate the calibration table using old and new method and compare the result + * + * @return void + */ +void CommonTest_OldAndNedCalibrationAlgorithm() +{ + // cold white old calibration (full range) + whiteLimit = 1.0f; + rCorrection = 0xA0; + gCorrection = 0xA0; + bCorrection = 0xA0; + oldCalibration(); + // new procedure + calibrationConfig.setParamsAndPrepareCalibration(255, 0xA0, 0xA0, 0xA0); + compareLut(); + + // neutral white old calibration (full range) + whiteLimit = 1.0f; + rCorrection = 0xB0; + gCorrection = 0xB0; + bCorrection = 0x70; + oldCalibration(); + // new procedure + calibrationConfig.setParamsAndPrepareCalibration(255, 0xB0, 0xB0, 0x70); + compareLut(); + + // cold white old calibration (medium range) + whiteLimit = 0.5019607843137255f; + rCorrection = 0xA0; + gCorrection = 0xA0; + bCorrection = 0xA0; + oldCalibration(); + // new procedure + calibrationConfig.setParamsAndPrepareCalibration(128, 0xA0, 0xA0, 0xA0); + compareLut(); + + // neutral white old calibration (medium range) + whiteLimit = 0.5019607843137255f; + rCorrection = 0xB0; + gCorrection = 0xB0; + bCorrection = 0x70; + oldCalibration(); + // new procedure + calibrationConfig.setParamsAndPrepareCalibration(128, 0xB0, 0xB0, 0x70); + compareLut(); +} + +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////// AWA PROTOCOL CORRECTNESS TEST ///////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// + +#define TEST_LEDS_NUMBER 801 +uint8_t _ledBuffer[TEST_LEDS_NUMBER * 3 + 6 + 8]; + +/** + * @brief Mockup Serial class to simulate the real communition + * + */ + +class SerialTester +{ + int frameSize = 0; + int sent = 0; + + public: + + void createTestFrame(bool _white_channel_calibration, uint8_t _white_channel_limit = 0, + uint8_t _white_channel_red = 0, uint8_t _white_channel_green = 0, + uint8_t _white_channel_blue = 0) + { + _ledBuffer[0] = 'A'; + _ledBuffer[1] = 'w'; + _ledBuffer[2] = (_white_channel_calibration) ? 'A' : 'a'; + _ledBuffer[4] = (TEST_LEDS_NUMBER-1) & 0xff; + _ledBuffer[3] = ((TEST_LEDS_NUMBER-1) >> 8) & 0xff; + _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; + + uint8_t* writer = &(_ledBuffer[6]); + uint8_t* hasher = writer; + + for(int i=0; i < TEST_LEDS_NUMBER; i++) + { + *(writer++)=random(255); + *(writer++)=random(255); + *(writer++)=random(255); + } + + if (_white_channel_calibration) + { + *(writer++) = _white_channel_limit; + *(writer++) = _white_channel_red; + *(writer++) = _white_channel_green; + *(writer++) = _white_channel_blue; + } + + uint16_t fletcher1 = 0, fletcher2 = 0, fletcherExt = 0; + uint8_t position = 0; + while (hasher < writer) + { + fletcherExt = (fletcherExt + (*(hasher) ^ (position++))) % 255; + fletcher1 = (fletcher1 + *(hasher++)) % 255; + fletcher2 = (fletcher2 + fletcher1) % 255; + } + *(writer++) = (uint8_t)fletcher1; + *(writer++) = (uint8_t)fletcher2; + *(writer++) = (uint8_t)((fletcherExt != 0x41) ? fletcherExt : 0xaa); + + frameSize = (int)(writer - _ledBuffer); + sent = 0; + } + + + inline size_t write(const char * s) + { + return 0; + } + + inline size_t print(unsigned char, int = DEC) + { + return 0; + } + + int available(void) + { + if (sent < frameSize) + { + return std::min(std::max((int)(random(64)), 1), frameSize - sent); + } + + return 0; + } + + int toSend(void) + { + return frameSize - sent; + } + + int getFrameSize() + { + return frameSize; + } + + size_t read(uint8_t *buffer, size_t size) + { + int max = std::min(frameSize - sent, (int)size); + if (max > 0) + { + memcpy(buffer, &(_ledBuffer[sent]), max); + sent += max; + return max; + } + return 0; + } + + void println(const String &s) + { + + } +} SerialPort; + + +/** + * @brief Mockup LED driver to verify correctness of the received LEDs color values + * + */ + +class ProtocolTester { + int ledCount; + int currentIndex = 0; + int lastCount = 0; + + public: + ProtocolTester(int count, int b) + { + ledCount = count; + + } + + ProtocolTester(int count) + { + ledCount = count; + } + + bool CanShow() + { + return true; + } + + void Show(bool safe = true) + { + lastCount = currentIndex; + currentIndex = 0; + } + + void Begin() + { + + } + + void Begin(int a, int b, int c, int d) + { + + } + + int getLastCount() + { + return lastCount; + } + + /** + * @brief Very important: verify LED color, compare it to the origin + * + * @param indexPixel + * @param color + */ + #ifdef NEOPIXEL_RGBW + void SetPixelColor(uint16_t indexPixel, RgbwColor color) + { + TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index"); + TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); + uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]); + uint8_t r = *(c++); + uint8_t g = *(c++); + uint8_t b = *(c++); + + uint8_t w = min(channelCorrection.red[r], + min(channelCorrection.green[g], + channelCorrection.blue[b])); + r -= channelCorrection.red[w]; + g -= channelCorrection.green[w]; + b -= channelCorrection.blue[w]; + w = channelCorrection.white[w]; + + TEST_ASSERT_EQUAL_UINT8(r, color.R); + TEST_ASSERT_EQUAL_UINT8(g, color.G); + TEST_ASSERT_EQUAL_UINT8(b, color.B); + TEST_ASSERT_EQUAL_UINT8(w, color.W); + + currentIndex = indexPixel + 1; + lastCount = 0; + } + #else + void SetPixelColor(uint16_t indexPixel, RgbColor color) + { + TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index"); + TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); + uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]); + uint8_t r = *(c++); + uint8_t g = *(c++); + uint8_t b = *(c++); + + TEST_ASSERT_EQUAL_UINT8(r, color.R); + TEST_ASSERT_EQUAL_UINT8(g, color.G); + TEST_ASSERT_EQUAL_UINT8(b, color.B); + + currentIndex = indexPixel + 1; + lastCount = 0; + } + #endif +}; + +#define LED_DRIVER ProtocolTester +#define LED_DRIVER2 ProtocolTester +#include "main.h" + + + +/** + * @brief Send RGBW calibration data and verify it all (including proper colors rendering) + * + */ +void SingleSegmentTest_SendRgbwCalibration() +{ + // set all calibration values to test + SerialPort.createTestFrame(true, 10, 20, 30, 40); + base.queueCurrent = 0; + base.queueEnd = 0; + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); + + // should not change if the frame doesnt contain calibration data + SerialPort.createTestFrame(false); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); + + // last test + SerialPort.createTestFrame(true, 255, 128, 128, 128); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); + TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(255,128,128,128), "Incorrect calibration result"); +} + +/** + * @brief Send 100 RGB/RGBW frames and verify it all (including proper colors rendering) + * + */ +void SingleSegmentTest_Send100Frames() +{ + base.queueCurrent = 0; + base.queueEnd = 0; + + for(int i = 0; i < 100; i++) + { + SerialPort.createTestFrame(false); + statistics.update(0); + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); + } +} + +/** + * @brief Send 200 RGB/RGBW valid/invalid frames and verify it all (including proper colors rendering) + * + */ +void SingleSegmentTest_Send200UncertainFrames() +{ + base.queueCurrent = 0; + base.queueEnd = 0; + + for(int i = 0; i < 200; i++) + { + SerialPort.createTestFrame(false); + statistics.update(0); + + bool damaged = (random(255) % 2) == 0; + int index; + uint8_t backup; + if (damaged) + { + index = random(SerialPort.getFrameSize()); + backup = _ledBuffer[index]; + _ledBuffer[index] = backup ^ 0xff; + } + + while(SerialPort.toSend() > 0) + { + serialTaskHandler(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); + processData(); + if (damaged) + { + char buffer[128]; + snprintf(buffer, sizeof(buffer), "Damaged frame was received: [%d]=>%d", index, backup); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), buffer); + base.getLedStrip1()->Show(); + frameState.setState(AwaProtocol::HEADER_A); + } + else + { + TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); + TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// UNIT TEST ROUTINES ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////// + +void setup() +{ + delay(1000); + randomSeed(analogRead(0)); + UNITY_BEGIN(); + #ifdef NEOPIXEL_RGBW + RUN_TEST(CommonTest_OldAndNedCalibrationAlgorithm); + RUN_TEST(SingleSegmentTest_SendRgbwCalibration); + #endif + RUN_TEST(SingleSegmentTest_Send100Frames); + RUN_TEST(SingleSegmentTest_Send200UncertainFrames); + UNITY_END(); +} + +void loop() +{ +} diff --git a/test/test_main.cpp b/test/test_main.cpp deleted file mode 100644 index 40c98ca..0000000 --- a/test/test_main.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* test_main.cpp -* -* MIT License -* -* Copyright (c) 2022 awawa-dev -* -* https://github.com/awawa-dev/HyperSerialESP32 -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. - -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. - */ - -#include -#include -#include "calibration.h" - -// old calibration params -float whiteLimit; -uint8_t rCorrection; -uint8_t gCorrection; -uint8_t bCorrection; - -uint8_t wChannel[256]; -uint8_t rChannel[256]; -uint8_t gChannel[256]; -uint8_t bChannel[256]; - -// old calibration procedure -void oldCalibration() -{ - for (uint32_t i = 0; i < 256; i++) - { - // color calibration - float red = rCorrection * i; // adjust red - float green = gCorrection * i; // adjust green - float blue = bCorrection * i; // adjust blue - - wChannel[i] = (uint8_t)round(std::min(whiteLimit * i, 255.0f)); - rChannel[i] = (uint8_t)round(std::min(red / 0xFF, 255.0f)); - gChannel[i] = (uint8_t)round(std::min(green / 0xFF, 255.0f)); - bChannel[i] = (uint8_t)round(std::min(blue / 0xFF, 255.0f)); - } -} - -void compareLut() -{ - for (uint32_t i = 0; i < 256; i++) - { - int w = std::abs(int(wChannel[i]) - int(channelCorrection.white[i])); - int r = std::abs(int(rChannel[i]) - int(channelCorrection.red[i])); - int g = std::abs(int(gChannel[i]) - int(channelCorrection.green[i])); - int b = std::abs(int(bChannel[i]) - int(channelCorrection.blue[i])); - TEST_ASSERT_LESS_THAN(1, (int)(std::max(w, std::max(r, std::max(g, b))))); - } -} - -void testCalibration() -{ - // cold white old calibration (full range) - whiteLimit = 1.0f; - rCorrection = 0xA0; - gCorrection = 0xA0; - bCorrection = 0xA0; - oldCalibration(); - // new procedure - calibrationConfig.setParams(255, 0xA0, 0xA0, 0xA0); - calibrationConfig.prepareCalibration(); - compareLut(); - - // neutral white old calibration (full range) - whiteLimit = 1.0f; - rCorrection = 0xB0; - gCorrection = 0xB0; - bCorrection = 0x70; - oldCalibration(); - // new procedure - calibrationConfig.setParams(255, 0xB0, 0xB0, 0x70); - calibrationConfig.prepareCalibration(); - compareLut(); - - // cold white old calibration (medium range) - whiteLimit = 0.5019607843137255f; - rCorrection = 0xA0; - gCorrection = 0xA0; - bCorrection = 0xA0; - oldCalibration(); - // new procedure - calibrationConfig.setParams(128, 0xA0, 0xA0, 0xA0); - calibrationConfig.prepareCalibration(); - compareLut(); - - // neutral white old calibration (medium range) - whiteLimit = 0.5019607843137255f; - rCorrection = 0xB0; - gCorrection = 0xB0; - bCorrection = 0x70; - oldCalibration(); - // new procedure - calibrationConfig.setParams(128, 0xB0, 0xB0, 0x70); - calibrationConfig.prepareCalibration(); - compareLut(); -} - -void setup() -{ - delay(1000); - UNITY_BEGIN(); - RUN_TEST(testCalibration); - UNITY_END(); -} - -void loop() -{ -}