From 6db9bd85a5360dee0669171463876685edef4c6c Mon Sep 17 00:00:00 2001 From: Jakob Gegeniger Date: Sun, 15 Dec 2024 22:46:44 +0100 Subject: [PATCH] including light effects from different project --- components/lightEffect/CMakeLists.txt | 4 + components/lightEffect/color.hpp | 202 +++++++++++++++ components/lightEffect/include/Color.hpp | 202 +++++++++++++++ components/lightEffect/include/ExactIndex.hpp | 60 +++++ components/lightEffect/include/Macro.hpp | 95 +++++++ components/lightEffect/include/Processor.hpp | 53 ++++ .../lightEffect/include/StateMachine.hpp | 118 +++++++++ components/lightEffect/include/Waveform.hpp | 27 ++ components/lightEffect/include/Waveforms.hpp | 38 +++ components/lightEffect/src/Color.cpp | 27 ++ components/lightEffect/src/Macro.cpp | 71 ++++++ components/lightEffect/src/Processor.cpp | 58 +++++ components/lightEffect/src/StateMachine.cpp | 117 +++++++++ components/lightEffect/src/Waveforms.cpp | 100 ++++++++ components/lightEffect/test/CMakeLists.txt | 5 + components/lightEffect/test/testUtils.cpp | 15 ++ components/lightEffect/uTest/CMakeLists.txt | 1 + components/lightEffect/uTest/utilsTest.cpp | 59 +++++ main/watchMain.cpp | 240 ++++++++++++++---- main/watchMain.hpp | 84 ++++++ 20 files changed, 1533 insertions(+), 43 deletions(-) create mode 100644 components/lightEffect/CMakeLists.txt create mode 100644 components/lightEffect/color.hpp create mode 100644 components/lightEffect/include/Color.hpp create mode 100644 components/lightEffect/include/ExactIndex.hpp create mode 100644 components/lightEffect/include/Macro.hpp create mode 100644 components/lightEffect/include/Processor.hpp create mode 100644 components/lightEffect/include/StateMachine.hpp create mode 100644 components/lightEffect/include/Waveform.hpp create mode 100644 components/lightEffect/include/Waveforms.hpp create mode 100644 components/lightEffect/src/Color.cpp create mode 100644 components/lightEffect/src/Macro.cpp create mode 100644 components/lightEffect/src/Processor.cpp create mode 100644 components/lightEffect/src/StateMachine.cpp create mode 100644 components/lightEffect/src/Waveforms.cpp create mode 100644 components/lightEffect/test/CMakeLists.txt create mode 100644 components/lightEffect/test/testUtils.cpp create mode 100644 components/lightEffect/uTest/CMakeLists.txt create mode 100644 components/lightEffect/uTest/utilsTest.cpp create mode 100644 main/watchMain.hpp diff --git a/components/lightEffect/CMakeLists.txt b/components/lightEffect/CMakeLists.txt new file mode 100644 index 0000000..e02288d --- /dev/null +++ b/components/lightEffect/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRC_DIRS "src" + INCLUDE_DIRS "include" +) diff --git a/components/lightEffect/color.hpp b/components/lightEffect/color.hpp new file mode 100644 index 0000000..841db41 --- /dev/null +++ b/components/lightEffect/color.hpp @@ -0,0 +1,202 @@ +/** + * @file color.hpp + * @author Gustice + * @brief + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include +#include +#include +#include + +/// No Waveform definition +extern uint8_t const *const noWave; + +/** + * @brief Color class + * + * @details Defines color data and common operations with color objects + * \li Supports RGB with overlapping white channel (e.g. SK6812-LED) + * \li Supports Operator overloading with '+' and '*' (Combined operation with MixWith) + * \li Return formatted data stream that can be directly sent to an LED + */ +class Color { + private: + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t white; + + public: + struct Rgbw { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t white; + }; + + struct Rgb { + uint8_t red; + uint8_t green; + uint8_t blue; + }; + + /// @brief Construct a new Color object + /// @details Default color will be Black \ref _ctBlack + Color(void) { + set(0, 0, 0, 0); + }; + + /// @brief Copy-Construct a new Color object + Color(const Color & c) { + red = c.red; + green = c.green; + blue = c.blue; + white = c.white; + }; + + /// @brief Construct a new Color object according to rgb color-channel-values + /// @details This constructor also takes an overlapping white channel + /// @param r Red channel + /// @param g Green channel + /// @param b Blue channel + /// @param w White channel (zero by default if not assigned) + Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { set(r, g, b, w); }; + + /// @brief Set the Color object according to rgb color-channel values + /// @param r Red channel + /// @param g Green channel + /// @param b Blue channel + /// @param w White channel (zero by default if not assigned) + void set(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { + red = r; + green = g; + blue = b; + white = w; + } + + /// @brief Returns Color as Color_t structure + /// @return Color_t Color + template + T getColor(void) const { + if constexpr (std::is_same::value) { + return {red, green, blue, white}; + } else if constexpr (std::is_same::value) { + return {red, green, blue}; + } else { + throw "wrong type"; + } + } + + uint8_t getRed() { return red; }; + uint8_t getGreen() { return green; }; + uint8_t getBlue() { return blue; }; + uint8_t getWhite() { return white; }; + + /// @brief Scaling and overlaying with given color. + /// @param c2 Color 2 + /// @param k scaling factor + /// @return Overlayed Color + Color mixWith(const Color c2, uint8_t k = 0x7F) { + auto t(c2); + *this = (*this * (UINT8_MAX - k)) + (t * k); + return *this; + } + + /// @brief Writes Color as formatted byte stream to location + /// @param color + template + void writeByteStreamTo(uint8_t raw[sizeof(T)]) const { + if constexpr (std::is_same::value) { + raw[0] = blue; + raw[1] = green; + raw[2] = red; + raw[3] = white; + } else if constexpr (std::is_same::value) { + raw[0] = blue; + raw[1] = green; + raw[2] = red; + } else { + throw "wrong type"; + } + } + + /// @brief Assign overload + Color operator=(const Color &c2) { + red = c2.red; + green = c2.green; + blue = c2.blue; + white = c2.white; + return *this; + } + + /// @brief Checks if both color values are equal + bool operator==(Color const &c2) const { + return ((blue == c2.blue) && (green == c2.green) && + (red == c2.red) && (white == c2.white)); + } + + /// @brief Checks if both color values are not equal + bool operator!=(Color const &c2) const { + return ((blue != c2.blue) || (green != c2.green) || + (red != c2.red) || (white != c2.white)); + } + + /// @brief Scale color by scaling each color-channel by given factor + /// @param c1 Color + /// @param k Scale-factor uint8_t.Max is full-scale + /// @return Scaled Color + Color operator*(uint8_t k) { + red = (uint8_t)((uint16_t)red * k / 0xFF); + green = (uint8_t)((uint16_t)green * k / 0xFF); + blue = (uint8_t)((uint16_t)blue * k / 0xFF); + white = (uint8_t)((uint16_t)white * k / 0xFF); + return *this; + } + + /// @brief Overlaying to colors my adding each color channel value + /// @note Colors channels should not overlap since there is no automatic ratio correction. + /// Use *-Operator first in order to scale each color value a a way that no overflow can occur + /// @param c2 Color 2 + /// @return Overlayed Color + Color operator+(Color c2) { + red = red + c2.red; + green = green + c2.green; + blue = blue + c2.blue; + white = white + c2.white; + return *this; + } +}; + +static_assert(sizeof(Color) == 4 && "Color Type is to big"); + +/// Null color object +extern const Color *noColor; +/// Used old color agreement (if no color pointer) +extern const Color *oldColor; + +/// Black color object constant +extern const Color CBlack; +/// Red color object constant +extern const Color CRed; +/// Green color object constant +extern const Color CGreen; +/// Blue color object constant +extern const Color CBlue; +/// Magenta color object constant +extern const Color CMagenta; +/// Cyan color object constant +extern const Color CCyan; +/// Yellow color object constant +extern const Color CYellow; +/// White color object constant +extern const Color CWhite; +/// Warm white color object constant (with higher red channel value) +extern const Color CWarmWhite; +/// Black color object constant (with higher green channel value) +extern const Color CColdWhite; +/// Black color object constant (with higher blue channel value) +extern const Color CSpookyWhite; diff --git a/components/lightEffect/include/Color.hpp b/components/lightEffect/include/Color.hpp new file mode 100644 index 0000000..841db41 --- /dev/null +++ b/components/lightEffect/include/Color.hpp @@ -0,0 +1,202 @@ +/** + * @file color.hpp + * @author Gustice + * @brief + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include +#include +#include +#include + +/// No Waveform definition +extern uint8_t const *const noWave; + +/** + * @brief Color class + * + * @details Defines color data and common operations with color objects + * \li Supports RGB with overlapping white channel (e.g. SK6812-LED) + * \li Supports Operator overloading with '+' and '*' (Combined operation with MixWith) + * \li Return formatted data stream that can be directly sent to an LED + */ +class Color { + private: + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t white; + + public: + struct Rgbw { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t white; + }; + + struct Rgb { + uint8_t red; + uint8_t green; + uint8_t blue; + }; + + /// @brief Construct a new Color object + /// @details Default color will be Black \ref _ctBlack + Color(void) { + set(0, 0, 0, 0); + }; + + /// @brief Copy-Construct a new Color object + Color(const Color & c) { + red = c.red; + green = c.green; + blue = c.blue; + white = c.white; + }; + + /// @brief Construct a new Color object according to rgb color-channel-values + /// @details This constructor also takes an overlapping white channel + /// @param r Red channel + /// @param g Green channel + /// @param b Blue channel + /// @param w White channel (zero by default if not assigned) + Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { set(r, g, b, w); }; + + /// @brief Set the Color object according to rgb color-channel values + /// @param r Red channel + /// @param g Green channel + /// @param b Blue channel + /// @param w White channel (zero by default if not assigned) + void set(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { + red = r; + green = g; + blue = b; + white = w; + } + + /// @brief Returns Color as Color_t structure + /// @return Color_t Color + template + T getColor(void) const { + if constexpr (std::is_same::value) { + return {red, green, blue, white}; + } else if constexpr (std::is_same::value) { + return {red, green, blue}; + } else { + throw "wrong type"; + } + } + + uint8_t getRed() { return red; }; + uint8_t getGreen() { return green; }; + uint8_t getBlue() { return blue; }; + uint8_t getWhite() { return white; }; + + /// @brief Scaling and overlaying with given color. + /// @param c2 Color 2 + /// @param k scaling factor + /// @return Overlayed Color + Color mixWith(const Color c2, uint8_t k = 0x7F) { + auto t(c2); + *this = (*this * (UINT8_MAX - k)) + (t * k); + return *this; + } + + /// @brief Writes Color as formatted byte stream to location + /// @param color + template + void writeByteStreamTo(uint8_t raw[sizeof(T)]) const { + if constexpr (std::is_same::value) { + raw[0] = blue; + raw[1] = green; + raw[2] = red; + raw[3] = white; + } else if constexpr (std::is_same::value) { + raw[0] = blue; + raw[1] = green; + raw[2] = red; + } else { + throw "wrong type"; + } + } + + /// @brief Assign overload + Color operator=(const Color &c2) { + red = c2.red; + green = c2.green; + blue = c2.blue; + white = c2.white; + return *this; + } + + /// @brief Checks if both color values are equal + bool operator==(Color const &c2) const { + return ((blue == c2.blue) && (green == c2.green) && + (red == c2.red) && (white == c2.white)); + } + + /// @brief Checks if both color values are not equal + bool operator!=(Color const &c2) const { + return ((blue != c2.blue) || (green != c2.green) || + (red != c2.red) || (white != c2.white)); + } + + /// @brief Scale color by scaling each color-channel by given factor + /// @param c1 Color + /// @param k Scale-factor uint8_t.Max is full-scale + /// @return Scaled Color + Color operator*(uint8_t k) { + red = (uint8_t)((uint16_t)red * k / 0xFF); + green = (uint8_t)((uint16_t)green * k / 0xFF); + blue = (uint8_t)((uint16_t)blue * k / 0xFF); + white = (uint8_t)((uint16_t)white * k / 0xFF); + return *this; + } + + /// @brief Overlaying to colors my adding each color channel value + /// @note Colors channels should not overlap since there is no automatic ratio correction. + /// Use *-Operator first in order to scale each color value a a way that no overflow can occur + /// @param c2 Color 2 + /// @return Overlayed Color + Color operator+(Color c2) { + red = red + c2.red; + green = green + c2.green; + blue = blue + c2.blue; + white = white + c2.white; + return *this; + } +}; + +static_assert(sizeof(Color) == 4 && "Color Type is to big"); + +/// Null color object +extern const Color *noColor; +/// Used old color agreement (if no color pointer) +extern const Color *oldColor; + +/// Black color object constant +extern const Color CBlack; +/// Red color object constant +extern const Color CRed; +/// Green color object constant +extern const Color CGreen; +/// Blue color object constant +extern const Color CBlue; +/// Magenta color object constant +extern const Color CMagenta; +/// Cyan color object constant +extern const Color CCyan; +/// Yellow color object constant +extern const Color CYellow; +/// White color object constant +extern const Color CWhite; +/// Warm white color object constant (with higher red channel value) +extern const Color CWarmWhite; +/// Black color object constant (with higher green channel value) +extern const Color CColdWhite; +/// Black color object constant (with higher blue channel value) +extern const Color CSpookyWhite; diff --git a/components/lightEffect/include/ExactIndex.hpp b/components/lightEffect/include/ExactIndex.hpp new file mode 100644 index 0000000..48abdc2 --- /dev/null +++ b/components/lightEffect/include/ExactIndex.hpp @@ -0,0 +1,60 @@ +/** + * @file ExactIndex.h + * @author Gustice + * @brief Index-Class with fix-point arithmetic + */ +#pragma once + +#include +#include + +/** + * @brief Index-Class with fix-point arithmetic for exact stepping though data-points + * @details Allows stepping through data-points by quotient step-sizes + */ +class ExactIndex { + public: + /// @brief Construct Index-object according to number of data points and steps to be taken + /// @param length + /// @param steps + ExactIndex(unsigned length, uint16_t steps) : Length(std::max(1u, length)), Steps(steps) { + _delta = Length << 8; + _delta = _delta / steps; + _index = unsigned(0u - _delta); + } + + /// @brief Get current index-position as floor value to never pass upper bound + uint16_t GetIdx(void) { return ((_index) >> 8); }; + + /// @brief Move index by quotient step forward + ExactIndex &operator++() { + _index = _index + _delta; + return *this; + } + + /// @brief Move index by quotient step backward + ExactIndex &operator--() { + _index = _index - _delta; + return *this; + } + + /// @brief Get current index-position as floor value to never pass upper bound + operator int() const { return ((_index) >> 8); } + + /// @brief Get size of data set (number of data points) + unsigned GetLength() { return Length; } + /// @brief Get number of steps that are to be taken though data set + uint16_t GetSteps() { return Steps; } + + private: + /// @brief Number of data points + unsigned Length; + /// @brief Steps to be taken through data-set + uint16_t Steps; + + /// @brief Current index in fixed-point representation + unsigned _index = 0; + + /// @brief Step-with in fixed-point representation + unsigned _delta; +}; \ No newline at end of file diff --git a/components/lightEffect/include/Macro.hpp b/components/lightEffect/include/Macro.hpp new file mode 100644 index 0000000..04637d1 --- /dev/null +++ b/components/lightEffect/include/Macro.hpp @@ -0,0 +1,95 @@ +/** + * @file Macro.h + * @author Gustice + * @brief Predefined effect macros + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include "Color.hpp" +#include "Waveform.hpp" +#include +#include + +namespace Effect { + + /// Collection of process values to maintain processing state machine + struct StepProcessVars { + /// @brief Reference of current color + const Color &pCurentColor; + /// @brief Number of color entries + uint8_t ColorLen; + /// @brief Idle value for intensity + uint8_t IdleIntensity; + /// @brief Full value for intensity + uint8_t FullIntensity; + /// @brief Dynamic range for effects + uint8_t DynamicRange; + /// @brief Index in current waveform + uint16_t waveIdx; + /// @brief Reference to processed waveform + const WaveForm_t *const pWave; + }; + + /** + * @brief Effect implementation for State-Machine + * @details State-Machine invokes referred effect-implementation during processing + */ + typedef Color (*EffectImplementation)(StepProcessVars &pcsParam); + + /** + * @brief Definition of Effect-Macro + */ + struct Macro_t { + /// @brief Effect type + const EffectImplementation pImplementation; + /// @brief Effect duration waveforms will be compressed / stretched to fit the duration + const uint8_t Duration; + /// @brief Next SubEffect + const int8_t Next; + /// @brief waveform reference + const WaveForm_t *pWave = emptyWave; + /// @brief Full-Scale-intensity + const uint8_t FsIntensity; + /// @brief Num of Repeats + const int8_t Repeats; + + Macro_t(uint8_t duration, int8_t next, EffectImplementation state, uint8_t intensity, + int8_t repeats = 0) + : pImplementation(state), Duration(duration), Next(next), FsIntensity(intensity), + Repeats(repeats) {} + + Macro_t(uint8_t duration, int8_t next, WaveForm_t const *wave, EffectImplementation state, + uint8_t intensity, int8_t repeats = 0) + : pImplementation(state), Duration(duration), Next(next), pWave(wave), + FsIntensity(intensity), Repeats(repeats) {} + + private: + /// @brief Null-pointer to represent no change in color + static constexpr Color *pOldColor = nullptr; + + /// @brief Null-pointer to represent no actual waveform-representation + static constexpr WaveForm_t *pNoWave = nullptr; + + /// @brief Default implemented Waveform with no profile + static const constexpr WaveForm_t *emptyWave = &EmptyWave; + }; + using Macro = std::vector; + + /// @brief Dark - Nothing to see here + extern const Macro macDark; + /// @brief Continuous idle + extern const Macro macIdle; + /// @brief Continuous flicker + extern const Macro macStartFlicker; + /// @brief Smooth turn on and then remaining idle + extern const Macro macStartIdle; + /// @brief Smooth turn on to full color + extern const Macro macStartFull; + /// @brief Slow pulse starting from idle intensity. Appears like breathing + extern const Macro macStdPulse; + /// @brief Short pulses starting from idle intensity. Appears nervously. + extern const Macro macNervousPulse; + +} // namespace Effect \ No newline at end of file diff --git a/components/lightEffect/include/Processor.hpp b/components/lightEffect/include/Processor.hpp new file mode 100644 index 0000000..36d7230 --- /dev/null +++ b/components/lightEffect/include/Processor.hpp @@ -0,0 +1,53 @@ +/** + * @file EffectProcessor.h + * @author Gustice + * @brief Effect-Processor-Class for processing effect scenarios and switching actions + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include "Color.hpp" +#include "Macro.hpp" +#include "StateMachine.hpp" +#include +#include + +namespace Effect { + + /** + * @brief EffectProcessor Class + * @details Maintains State-Machines, Processes effect-switching, and invokes generation + * of raw data stream for LEDs. + * \li If effect scenarios are changed a cross-fading is executed + * \li Raw-data-stream for each Color object is generated + */ + class EffectProcessor { + public: + ///@brief Constructor + ///@param fadeSteps Steps count for cross fade + EffectProcessor(uint8_t const fadeSteps = 8); + + /// @brief Set next effect sequence + /// @param sequence reference to sequence that has to be processed next + /// @param sColor Start color. Use noColor to start with default color + /// @param intens Idle intensity for effect. Use NULL to start with default intensity + void setEffect(const Macro &sequence, Color const *sColor = noColor, uint8_t delay = 0, uint8_t intens = gu8_idleIntensity); + + /// @brief Execute step of effect processor + /// @return Reference to color result + Color tick(void); + + private: + StateMachine::Parameter _parameter; + std::array _effectMachines; + uint8_t _fadeSteps; + uint8_t _fadingCnt; + StateMachine *_EffPV; + StateMachine *_EffPV_old; + + Color crossFadeColors(Color &cNew, Color &cOld, uint8_t k); + EffectProcessor(); + }; + +} // namespace Effect diff --git a/components/lightEffect/include/StateMachine.hpp b/components/lightEffect/include/StateMachine.hpp new file mode 100644 index 0000000..d2a2f82 --- /dev/null +++ b/components/lightEffect/include/StateMachine.hpp @@ -0,0 +1,118 @@ +/** + * @file EffectStateMachine.h + * @author Gustice + * @brief Effect-State-Machine-Class for processing Effect Macros + * @version 0.6 + * @date 2019-10-01 + * + * @copyright Copyright (c) 2019 + * + */ +#pragma once + +#include "Color.hpp" +#include "Macro.hpp" +#include +#include "ExactIndex.hpp" + +namespace Effect { + + /** + * @brief Effect State Machine Class + * @details Processes Effect macro array Macro entry subsequently + * Processing is executed in following manner: + * \li Index-incrementing each Tick + * \li ticks until duration \ref EffMacro is finished + * \li each tick the index incremented by templateLength/duration + * \li Repeats-processing + * \li Each repeat the tick and the index are set back to start + * \li Switches to next Macro enty + * \li Next entry is given by 'next' index + * \li Color changes can be executed optionally + */ + class StateMachine { + public: + /// @brief Collection of object configuration at startup + struct Parameter { + /// @brief Idle intensity as background color + uint8_t idleIntens; + /// @brief dynamic range as variation around background color + uint8_t dynamicRange; + /// @brief Fade steps for color changes + uint8_t fadeSteps; + /// @brief Count of target LEDs + uint8_t targetCount; + }; + + ///@brief Construct a new Effect State Machine object + ///@param templateLength Length of waveforms that are used to display + StateMachine(Parameter ¶meter); + + ///@brief Sets the effect state machine to process given effect macro + ///@param sequence reference to sequence + ///@param startColor Start color. Use noColor to invoke with default color + ///@param initialDelay Number of steps that are waited before effect is started + void setEffect(const Macro &sequence, Color const *startColor = noColor, uint8_t initialDelay = 0); + + /// @brief Sets the effect state machine to process given effect macro + /// @param sequence reference to sequence + /// @param startColor Start color. Use noColor to invoke with default color + /// @param intens Idle intensity for effect. Use nullptr to start with default intensity + /// @param delayedStart Number of steps that are waited before effect is started + void setEffect(const Macro &sequence, Color const *startColor, const uint8_t *intens, + const uint8_t delayedStart); + + /// @brief Execute step of state machine + /// @details Each tick the tick variable is decremented. After the limit is reached, + /// the current macro line is either repeated or the next macro line is started (with + /// an optional color change).\n + /// @note Tick must be called regularly. For standard light applications all 40 ms seems to be + /// a convenient values. + /// @return Color const* Reference to color result + virtual Color tick(void); + + /// Black out lights + static Color UpdateBlank(StepProcessVars &pcsParam); + /// Continuous illumination in idle intensity + static Color UpdateIdle(StepProcessVars &pcsParam); + /// Keep latest state on hold + static Color UpdateFreeze(StepProcessVars &pcsParam); + /// Step through a defined waveform + static Color UpdateWave(StepProcessVars &pcsParam); + /// Step through a defined waveform backwards + static Color UpdateRevWave(StepProcessVars &pcsParam); + /// Show flicker effect with idle intensity as static part with dynamic flicker-range + static Color UpdateFlicker(StepProcessVars &pcsParam); + + protected: + /// Collection of process values to maintain processing state machine + struct ProcessValues { + /// Decrementing tick, counting duration + uint8_t tick; + /// Decrementing repeats of current Effect-part + uint8_t repeats; + /// Index to macro element + uint8_t macroIdx; + /// Current index to waveform + uint16_t waveIdx; + /// Index step each tick according to duration and template length. + uint16_t waveStep; + }; + + /// @brief Concentrated parameter values of instance + Parameter &_params; + + Color const * _curentColor; + ProcessValues _prcValues; + void SetIndexes(void); + + Macro const *_p_effSeq; + Macro_t const *_p_effMac; + ExactIndex _idx; + + // Color& (*apF_Effects)(StateMachine * SM); + typedef Color const *pEffPrc(StateMachine *); + + }; + +} // namespace Effect \ No newline at end of file diff --git a/components/lightEffect/include/Waveform.hpp b/components/lightEffect/include/Waveform.hpp new file mode 100644 index 0000000..f557061 --- /dev/null +++ b/components/lightEffect/include/Waveform.hpp @@ -0,0 +1,27 @@ +/** + * @file WaveForm.h + * @author Gustice + * @brief C++ encapsulated waveform definition + */ + +#pragma once + +#include "Waveforms.hpp" +#include + +namespace Effect { + +/** + * @brief Waveform definition based on c-definitions + */ +struct WaveForm_t { + /// @brief Reference to waveform + const uint8_t *const pWave; + /// @brief Length of waveform + const unsigned Length; +}; + +/// @brief Empty waveform with defined pointer to meaningless data to avoid problems +extern const WaveForm_t EmptyWave; + +} // namespace Effect diff --git a/components/lightEffect/include/Waveforms.hpp b/components/lightEffect/include/Waveforms.hpp new file mode 100644 index 0000000..2a9753a --- /dev/null +++ b/components/lightEffect/include/Waveforms.hpp @@ -0,0 +1,38 @@ +/** + * @file EffectWaveforms.h + * @author Gustice + * @brief Predefined Waveforms that can be put together to a complex Waveform + * @version 0.6 + * @date 2019-10-01 + * + * @copyright Copyright (c) 2019 + * + */ +#pragma once +#include + +namespace Effect { + +/// Length of all subsequent Template definitions +extern const uint16_t cu16_TemplateLength; +/// Idle intensity for Slopes and Pulses +extern const uint8_t gu8_idleIntensity; +/// Idle intensity for Slopes and Pulses +extern const uint8_t gu8_fullIntensity; +/// Headroom for Effects on top of idle brightness +extern const uint8_t gu8_dynamicRange; +/// Standard cross fade steps +extern const uint8_t gu8_fadeSteps; + +/// Starts from idle \ref gu8_idleIntensity and represents a gaussian impulse \ref gu8_fullIntensity +extern const uint8_t gau8_offsetPulse[]; +/// Starts at zero and rises to idle intensity \ref gu8_idleIntensity +extern const uint8_t gau8_initSlope[]; +/// Starts from idle \ref gu8_idleIntensity and rises up to full intensity \ref gu8_fullIntensity +extern const uint8_t gau8_offsetSlope[]; +/// Starts at zero and rises to full intensity \ref gu8_fullIntensity +extern const uint8_t gau8_fullSlope[]; +/// Starts at zero and represents a gaussian impulse to full intensity \ref gu8_fullIntensity +extern const uint8_t gau8_fullPulse[]; + +} // namespace Effect diff --git a/components/lightEffect/src/Color.cpp b/components/lightEffect/src/Color.cpp new file mode 100644 index 0000000..74f8aa1 --- /dev/null +++ b/components/lightEffect/src/Color.cpp @@ -0,0 +1,27 @@ +/** + * @file color.cpp + * @author Gustice + * @brief Implementation of Color-Class + * + * @copyright Copyright (c) 2024 + */ + +#include "Color.hpp" + +uint8_t const * const noWave = nullptr; + +const Color *noColor = nullptr; +const Color *oldColor = nullptr; + +const Color CBlack(0x00, 0x00, 0x00, 0x00); +const Color CRed(0xFF, 0x00, 0x00, 0x00); +const Color CGreen(0x00, 0xFF, 0x00, 0x00); +const Color CBlue(0x00, 0x00, 0xFF, 0x00); +const Color CMagenta(0x80, 0x00, 0x80, 0x00); +const Color CCyan(0x00, 0x80, 0x80, 0x00); +const Color CYellow(0x80, 0x80, 0x00, 0x00); +const Color CWhite(0x55, 0x55, 0x55, 0x00); +const Color CWarmWhite(0x7F, 0x4F, 0x4F, 0x00); +const Color CColdWhite(0x4F, 0x4F, 0x7F, 0x00); +const Color CSpookyWhite(0x4F, 0x7F, 0x4F, 0x00); + diff --git a/components/lightEffect/src/Macro.cpp b/components/lightEffect/src/Macro.cpp new file mode 100644 index 0000000..8fbde3c --- /dev/null +++ b/components/lightEffect/src/Macro.cpp @@ -0,0 +1,71 @@ +/** + * @file EffectMacro.cpp + * @author Gustice + * @brief Predefined Macros + * @version 0.6 + * @date 2021-03-23 + * + * @copyright Copyright (c) 2021 + */ + +#include "Macro.hpp" +#include "Color.hpp" +#include "Waveforms.hpp" +#include "StateMachine.hpp" + +namespace Effect { + const uint8_t Empty[4] = {0, 0, 0, 0}; + + const WaveForm_t EmptyWave{Empty, 4}; + const WaveForm_t OffsetPulseWave{gau8_offsetPulse, cu16_TemplateLength}; + const WaveForm_t InitSlopeWave{gau8_initSlope, cu16_TemplateLength}; + const WaveForm_t OffsetSlopeWave{gau8_offsetSlope, cu16_TemplateLength}; + const WaveForm_t FullSlopeWave{gau8_fullSlope, cu16_TemplateLength}; + const WaveForm_t FullPulseWave{gau8_fullPulse, cu16_TemplateLength}; + + const Macro macDark = { + {32, 0, StateMachine::UpdateBlank, 0, 0} + }; + + const Macro macIdle = { + {32, 1, StateMachine::UpdateIdle, 0xFF}, + {32, 1, StateMachine::UpdateIdle, 0xFF}, + }; + + const Macro macStartIdle = { + {64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF}, + {32, 1, StateMachine::UpdateIdle, 0xFF}, + }; + + const Macro macStartFlicker = { + {64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF}, + {32, 1, StateMachine::UpdateFlicker, 0xFF}, + }; + + const Macro macStartFull = { + {24, 1, &FullSlopeWave, StateMachine::UpdateWave, 0xFF}, + {32, 1, StateMachine::UpdateIdle, 0xFF}, + }; + + const Macro macStdPulse = { + {32, 1, StateMachine::UpdateIdle, 0}, + {32, 2, &OffsetPulseWave, StateMachine::UpdateWave, 0xFF}, + {32, 0, StateMachine::UpdateIdle, 0}, + }; + + const Macro macNervousPulse = { + {8, 1, StateMachine::UpdateIdle, 0}, + {16, 2, &OffsetPulseWave, StateMachine::UpdateWave, 0xFF}, + {8, 0, StateMachine::UpdateIdle, 0}, + }; + + const Macro macStdWipe = { + {32, 1, StateMachine::UpdateIdle, 0}, + + {32, 2, &OffsetSlopeWave, StateMachine::UpdateWave, 0xFF}, + {32, 3, StateMachine::UpdateFreeze, 0}, + {32, 4, &OffsetSlopeWave, StateMachine::UpdateRevWave, 0xFF}, + {32, 1, StateMachine::UpdateFreeze, 0}, + }; + +} // namespace Effect diff --git a/components/lightEffect/src/Processor.cpp b/components/lightEffect/src/Processor.cpp new file mode 100644 index 0000000..bce8eaa --- /dev/null +++ b/components/lightEffect/src/Processor.cpp @@ -0,0 +1,58 @@ +/** + * @file Processor.cpp + * @author Gustice + * @brief Implementation of Effect-Processor-Class EffectProcessor.h + * @version 0.6 + * @date 2019-10-03 + * + * @copyright Copyright (c) 2019 + */ + +#include "Processor.hpp" +#include "Macro.hpp" +#include "Waveforms.hpp" +#include +#include +#include + +namespace Effect { + + EffectProcessor::EffectProcessor(uint8_t const fadeSteps) + : _parameter{0x20, 0x20, 0, 1}, + _effectMachines{StateMachine{_parameter}, StateMachine{_parameter}} { + _EffPV = &_effectMachines[0]; + _EffPV_old = &_effectMachines[1]; + _EffPV->setEffect(macDark, noColor); + _fadeSteps = fadeSteps; + } + + void EffectProcessor::setEffect(const Macro &sequence, Color const *sColor, uint8_t delay, uint8_t intens) { + _fadingCnt = _fadeSteps; + std::swap(_EffPV, _EffPV_old); + _EffPV->setEffect(sequence, sColor, &intens, delay); + } + + Color EffectProcessor::tick(void) { + uint8_t k; + auto cN = (_EffPV->tick()); + + if (_fadingCnt > 0) { // always process soft cross dissolve between different macros + _fadingCnt--; + k = (uint16_t)_fadingCnt * 0xFF / _fadeSteps; + + auto cO = (_EffPV_old->tick()); + cN = crossFadeColors(cN, cO, k); + } + + return cN; + } + + Color EffectProcessor::crossFadeColors(Color &cNew, Color &cOld, uint8_t k) { + cOld = cOld * k; + cNew = cNew * (0xFF - k); + cNew = cNew + cOld; + + return cNew; + } + +} // namespace Effect \ No newline at end of file diff --git a/components/lightEffect/src/StateMachine.cpp b/components/lightEffect/src/StateMachine.cpp new file mode 100644 index 0000000..bd8473e --- /dev/null +++ b/components/lightEffect/src/StateMachine.cpp @@ -0,0 +1,117 @@ +/** + * @file StateMachine.cpp + * @author Gustice + * @brief Implementation of Effect-State-Machine-Class EffectStateMachine.h + * @version 0.6 + * @date 2019-10-01 + * + * @copyright Copyright (c) 2019 + * + */ +#include "StateMachine.hpp" +#include "Waveforms.hpp" +#include +#include + +namespace Effect { + + /// Optional prequel for delayed start. + /// \li Duration must be != 0 + /// \li Next entry will be first entry in given Effect sequence + Macro delayPrequel = { + {1, 0, StateMachine::UpdateBlank, 0, 0} + }; + + StateMachine::StateMachine(Parameter ¶meter) + : _params(parameter), _idx(/*pseudo values*/ 1, 1) {} + + void StateMachine::setEffect(const Macro &sequence, Color const *startColor, uint8_t initialDelay) { + setEffect(sequence, startColor, &_params.idleIntens, initialDelay); + } + + void StateMachine::setEffect(const Macro &sequence, Color const *startColor, const uint8_t *intens, + const uint8_t delayedStart) { + if (startColor != noColor) { + _curentColor = startColor; + } + if (intens != nullptr) { + _params.idleIntens = *intens; + } + + _p_effSeq = &sequence; + if (delayedStart > 0) { + _p_effMac = &delayPrequel[0]; + _prcValues.tick = delayedStart; + } else { + _p_effMac = &sequence[0]; + _prcValues.tick = _p_effMac->Duration; + } + + _prcValues.tick++; + _prcValues.repeats = _p_effMac->Repeats; + _idx = ExactIndex(_p_effMac->pWave->Length, _p_effMac->Duration); + } + + Color StateMachine::tick(void) { + // tick-increment + if (--_prcValues.tick == 0) { + // repeats-decrement + if (_prcValues.repeats-- == 0) { + _p_effMac = &(*_p_effSeq)[_p_effMac->Next]; + _prcValues.repeats = _p_effMac->Repeats; + } + + _prcValues.tick = _p_effMac->Duration; + _idx = ExactIndex(_p_effMac->pWave->Length, _p_effMac->Duration); + } + + ++_idx; + + StepProcessVars pv{ + .pCurentColor = *_curentColor, + .ColorLen = _params.targetCount, + .IdleIntensity = _params.idleIntens, + .FullIntensity = _p_effMac->FsIntensity, + .DynamicRange = _params.dynamicRange, + .waveIdx = _idx.GetIdx(), + .pWave = _p_effMac->pWave, + }; + return _p_effMac->pImplementation(pv); + } + + Color StateMachine::UpdateBlank(StepProcessVars &pcsParam) { + return CBlack; + } + + Color StateMachine::UpdateIdle(StepProcessVars &pcsParam) { + auto c = pcsParam.pCurentColor; + return c * pcsParam.IdleIntensity; + } + + Color StateMachine::UpdateFreeze(StepProcessVars &pcsParam) { + return pcsParam.pCurentColor; + } + + Color StateMachine::UpdateWave(StepProcessVars &pcsParam) { + Color temp = pcsParam.pCurentColor; + temp = temp * ((float)(pcsParam.pWave->pWave[pcsParam.waveIdx]) / 0xFF * pcsParam.FullIntensity); + return temp; + } + + Color StateMachine::UpdateRevWave(StepProcessVars &pcsParam) { + unsigned lastIndex = pcsParam.pWave->Length - 1; + Color temp = pcsParam.pCurentColor; + temp = temp * ((float)(pcsParam.pWave->pWave[lastIndex - pcsParam.waveIdx]) / 0xFF * + pcsParam.FullIntensity); + return temp; + } + + Color StateMachine::UpdateFlicker(StepProcessVars &pcsParam) { + uint8_t k = rand(); + k = pcsParam.IdleIntensity + ((int16_t)k * (pcsParam.DynamicRange) / 0xFF); + auto c = pcsParam.pCurentColor; + c = c * ((float)(k)); + return c; + } + +} // namespace Effect \ No newline at end of file diff --git a/components/lightEffect/src/Waveforms.cpp b/components/lightEffect/src/Waveforms.cpp new file mode 100644 index 0000000..6ddcf06 --- /dev/null +++ b/components/lightEffect/src/Waveforms.cpp @@ -0,0 +1,100 @@ +/** + * @file Waveforms.cpp + * @author Gustice + * @brief Implementation of defined Waveforms + * @version 0.6 + * @date 2019-10-01 + * + * @copyright Copyright (c) 2019 + * @note The defined waveforms are generated with a the WaveformGenerator + */ +#include "Waveforms.hpp" + +namespace Effect { + +const uint16_t cu16_TemplateLength = 128; +const uint8_t gu8_idleIntensity = 0x55; +const uint8_t gu8_fullIntensity = 0xFF; +const uint8_t gu8_dynamicRange = gu8_fullIntensity - gu8_idleIntensity; +const uint8_t gu8_fadeSteps = 10; + +// clang-format off +// Offset Pulse: Gaussian pulse from background illumination with full scale intensity +const uint8_t gau8_offsetPulse[cu16_TemplateLength * 2] = { + 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x5A, 0x5B, 0x5C, + 0x5D, 0x5E, 0x5F, 0x61, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6F, 0x71, 0x74, 0x77, 0x7B, 0x7E, + 0x81, 0x85, 0x89, 0x8E, 0x92, 0x97, 0x9B, 0xA0, 0xA5, 0xAA, 0xAF, 0xB5, 0xBA, 0xBF, 0xC4, 0xC9, + 0xCF, 0xD3, 0xD8, 0xDD, 0xE1, 0xE6, 0xEA, 0xED, 0xF1, 0xF4, 0xF7, 0xF9, 0xFB, 0xFD, 0xFD, 0xFE, + 0xFF, 0xFE, 0xFD, 0xFD, 0xFB, 0xF9, 0xF7, 0xF4, 0xF1, 0xED, 0xEA, 0xE6, 0xE1, 0xDD, 0xD8, 0xD3, + 0xCF, 0xC9, 0xC4, 0xBF, 0xBA, 0xB5, 0xAF, 0xAA, 0xA5, 0xA0, 0x9B, 0x97, 0x92, 0x8E, 0x89, 0x85, + 0x81, 0x7E, 0x7B, 0x77, 0x74, 0x71, 0x6F, 0x6C, 0x6A, 0x68, 0x66, 0x64, 0x62, 0x61, 0x5F, 0x5E, + 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x59, 0x58, 0x58, 0x57, 0x57, 0x57, 0x56, 0x56, 0x56, 0x55, 0x55, + + 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x5A, 0x5B, 0x5C, + 0x5D, 0x5E, 0x5F, 0x61, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6F, 0x71, 0x74, 0x77, 0x7B, 0x7E, + 0x81, 0x85, 0x89, 0x8E, 0x92, 0x97, 0x9B, 0xA0, 0xA5, 0xAA, 0xAF, 0xB5, 0xBA, 0xBF, 0xC4, 0xC9, + 0xCF, 0xD3, 0xD8, 0xDD, 0xE1, 0xE6, 0xEA, 0xED, 0xF1, 0xF4, 0xF7, 0xF9, 0xFB, 0xFD, 0xFD, 0xFE, + 0xFF, 0xFE, 0xFD, 0xFD, 0xFB, 0xF9, 0xF7, 0xF4, 0xF1, 0xED, 0xEA, 0xE6, 0xE1, 0xDD, 0xD8, 0xD3, + 0xCF, 0xC9, 0xC4, 0xBF, 0xBA, 0xB5, 0xAF, 0xAA, 0xA5, 0xA0, 0x9B, 0x97, 0x92, 0x8E, 0x89, 0x85, + 0x81, 0x7E, 0x7B, 0x77, 0x74, 0x71, 0x6F, 0x6C, 0x6A, 0x68, 0x66, 0x64, 0x62, 0x61, 0x5F, 0x5E, + 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x59, 0x58, 0x58, 0x57, 0x57, 0x57, 0x56, 0x56, 0x56, 0x55, 0x55, +}; + +// Full Pulse: Gaussian pulse from dark to full scale intensity +const uint8_t gau8_fullPulse[cu16_TemplateLength * 2] = { + 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, + 0x0D, 0x0E, 0x10, 0x12, 0x14, 0x17, 0x1A, 0x1D, 0x20, 0x23, 0x27, 0x2B, 0x2F, 0x34, 0x39, 0x3E, + 0x43, 0x49, 0x4F, 0x56, 0x5C, 0x63, 0x6A, 0x71, 0x79, 0x80, 0x88, 0x90, 0x98, 0x9F, 0xA7, 0xAF, + 0xB7, 0xBE, 0xC5, 0xCC, 0xD3, 0xDA, 0xE0, 0xE5, 0xEA, 0xEF, 0xF3, 0xF6, 0xF9, 0xFC, 0xFD, 0xFE, + 0xFF, 0xFE, 0xFD, 0xFC, 0xF9, 0xF6, 0xF3, 0xEF, 0xEA, 0xE5, 0xE0, 0xDA, 0xD3, 0xCC, 0xC5, 0xBE, + 0xB7, 0xAF, 0xA7, 0x9F, 0x98, 0x90, 0x88, 0x80, 0x79, 0x71, 0x6A, 0x63, 0x5C, 0x56, 0x4F, 0x49, + 0x43, 0x3E, 0x39, 0x34, 0x2F, 0x2B, 0x27, 0x23, 0x20, 0x1D, 0x1A, 0x17, 0x14, 0x12, 0x10, 0x0E, + 0x0D, 0x0B, 0x0A, 0x08, 0x07, 0x06, 0x05, 0x05, 0x04, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, + + 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, + 0x0D, 0x0E, 0x10, 0x12, 0x14, 0x17, 0x1A, 0x1D, 0x20, 0x23, 0x27, 0x2B, 0x2F, 0x34, 0x39, 0x3E, + 0x43, 0x49, 0x4F, 0x56, 0x5C, 0x63, 0x6A, 0x71, 0x79, 0x80, 0x88, 0x90, 0x98, 0x9F, 0xA7, 0xAF, + 0xB7, 0xBE, 0xC5, 0xCC, 0xD3, 0xDA, 0xE0, 0xE5, 0xEA, 0xEF, 0xF3, 0xF6, 0xF9, 0xFC, 0xFD, 0xFE, + 0xFF, 0xFE, 0xFD, 0xFC, 0xF9, 0xF6, 0xF3, 0xEF, 0xEA, 0xE5, 0xE0, 0xDA, 0xD3, 0xCC, 0xC5, 0xBE, + 0xB7, 0xAF, 0xA7, 0x9F, 0x98, 0x90, 0x88, 0x80, 0x79, 0x71, 0x6A, 0x63, 0x5C, 0x56, 0x4F, 0x49, + 0x43, 0x3E, 0x39, 0x34, 0x2F, 0x2B, 0x27, 0x23, 0x20, 0x1D, 0x1A, 0x17, 0x14, 0x12, 0x10, 0x0E, + 0x0D, 0x0B, 0x0A, 0x08, 0x07, 0x06, 0x05, 0x05, 0x04, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, +}; + +// Init Slope: Gaussian slope from dark to background brightness +const uint8_t gau8_initSlope[cu16_TemplateLength * 2] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04, + 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x12, 0x13, 0x14, 0x16, 0x17, 0x19, 0x1A, 0x1C, 0x1D, 0x1F, 0x21, 0x22, 0x24, 0x26, 0x27, 0x29, + 0x2B, 0x2D, 0x2E, 0x30, 0x32, 0x33, 0x35, 0x37, 0x38, 0x3A, 0x3B, 0x3D, 0x3E, 0x40, 0x41, 0x42, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4C, 0x4D, 0x4E, 0x4E, 0x4F, 0x4F, 0x50, + 0x50, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54, 0x54, + 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, +}; + +// Offset Pulse: Gaussian slope from background illumination to full scale brightness +const uint8_t gau8_offsetSlope[cu16_TemplateLength] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x5A, 0x5A, 0x5B, 0x5C, 0x5D, + 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x65, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72, 0x74, 0x76, + 0x79, 0x7B, 0x7E, 0x81, 0x84, 0x87, 0x8A, 0x8D, 0x90, 0x93, 0x97, 0x9A, 0x9D, 0xA1, 0xA4, 0xA8, + 0xAB, 0xAF, 0xB2, 0xB6, 0xB9, 0xBC, 0xC0, 0xC3, 0xC6, 0xC9, 0xCC, 0xCF, 0xD2, 0xD5, 0xD8, 0xDA, + 0xDD, 0xDF, 0xE1, 0xE3, 0xE5, 0xE7, 0xE9, 0xEB, 0xED, 0xEE, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, + 0xF6, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, +}; + +// Full Slope: Gaussian slope from dark to full scale brightness +const uint8_t gau8_fullSlope[cu16_TemplateLength] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, + 0x0D, 0x0F, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1D, 0x1F, 0x22, 0x25, 0x28, 0x2B, 0x2F, 0x32, + 0x36, 0x3A, 0x3E, 0x42, 0x46, 0x4B, 0x50, 0x54, 0x59, 0x5E, 0x63, 0x68, 0x6D, 0x72, 0x77, 0x7C, + 0x82, 0x87, 0x8C, 0x91, 0x96, 0x9B, 0xA0, 0xA5, 0xAA, 0xAE, 0xB3, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, + 0xCC, 0xCF, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE1, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE, 0xEF, 0xF1, + 0xF2, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF8, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, +}; + +}// clang-format on diff --git a/components/lightEffect/test/CMakeLists.txt b/components/lightEffect/test/CMakeLists.txt new file mode 100644 index 0000000..276a64a --- /dev/null +++ b/components/lightEffect/test/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." + INCLUDE_DIRS "." + REQUIRES cmock utils +) diff --git a/components/lightEffect/test/testUtils.cpp b/components/lightEffect/test/testUtils.cpp new file mode 100644 index 0000000..e5cd888 --- /dev/null +++ b/components/lightEffect/test/testUtils.cpp @@ -0,0 +1,15 @@ +/** + * @file testUtils.cpp + * @author Gustice + * @brief Hardware bound unit-tests + * + * @copyright Copyright (c) 2024 + */ + +#include "testable.h" +#include "unity.h" +#include + +TEST_CASE("No relevant test case", "[utils]") { + TEST_ASSERT_EQUAL(0, 0); +} diff --git a/components/lightEffect/uTest/CMakeLists.txt b/components/lightEffect/uTest/CMakeLists.txt new file mode 100644 index 0000000..96653f2 --- /dev/null +++ b/components/lightEffect/uTest/CMakeLists.txt @@ -0,0 +1 @@ +unitTestsFrame() diff --git a/components/lightEffect/uTest/utilsTest.cpp b/components/lightEffect/uTest/utilsTest.cpp new file mode 100644 index 0000000..3a4cc88 --- /dev/null +++ b/components/lightEffect/uTest/utilsTest.cpp @@ -0,0 +1,59 @@ +/** + * @file TestableUTest.cpp + * @author Gustice + * @brief + * + * @copyright Copyright (c) 2024 + */ + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "utils.hpp" +#include "doctest/doctest.h" +#include + +namespace SingleActiveTests { + + class Foo : public SingleActive { + public: + int test{}; + + Foo() : SingleActive() {} + }; + template <> + bool Foo::SingleActive::active{}; + + class Bar : public SingleActive { + public: + int test{}; + + Bar() : SingleActive() {} + }; + template <> + bool Bar::SingleActive::active{}; + + TEST_CASE("Sweet path") { + { // first instances + Foo dut; + } // destruction + + { // second instances + Foo dut; + } // destruction + + { // third instance + Foo dut1; + Bar dut2; // First instance of second class + } // destruction + + CHECK(true); + } + + TEST_CASE("Too many instances") { + { + Foo dut1; + Foo *pDut2; + CHECK_THROWS_WITH(pDut2 = new Foo, "Instance already active"); + } + CHECK(true); + } +} diff --git a/main/watchMain.cpp b/main/watchMain.cpp index 056b9cb..45643b7 100644 --- a/main/watchMain.cpp +++ b/main/watchMain.cpp @@ -1,42 +1,12 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +/** + * @file watchMain.cpp + * @author Gustice + * @brief ChaosWatch Application * - * SPDX-License-Identifier: Unlicense OR CC0-1.0 + * @copyright Copyright (c) 2024 */ -/* eth2ap (Ethernet to Wi-Fi AP packet forwarding) Example - This example code is in the Public Domain (or CC0 licensed, at your option.) - - Unless required by applicable law or agreed to in writing, this - software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. -*/ -#include -#include -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "esp_event.h" -#include "esp_log.h" -#include "esp_eth_driver.h" -#include "esp_wifi.h" -#include "nvs_flash.h" -#include "esp_private/wifi.h" -#include "ethernet_init.h" -#include "esp_netif.h" -#include "esp_eth.h" - -// Component header -#include "Task.hpp" -#include "PwmExpanderPca9685.hpp" -#include "StepperDrv8833.hpp" -#include "stepperMotor.hpp" -#include "cliWrapper.hpp" -#include "udpSockets.hpp" -#include "tcpSockets.hpp" -#include "Queue.hpp" -#include "Artnet.h" +#include "watchMain.hpp" static const char *TAG = "Watch"; static esp_eth_handle_t s_eth_handle = NULL; @@ -46,12 +16,6 @@ extern "C" { // This switch allows the ROS C-implementation to find this main void app_main(void); } -struct Color { - uint8_t r; - uint8_t g; - uint8_t b; -}; - struct DeviceState { int jumps; int pictogram; @@ -150,7 +114,7 @@ static void telnetSocketTask(int addrFamily) { constexpr uint32_t DmxUniverse = 0; -constexpr int DmxChannelCount = 9; // +constexpr int DmxChannelCount = 9; uint8_t scaledRgbChannel(uint8_t value, float scale) { return static_cast(value * scale); @@ -344,6 +308,30 @@ static void initialize_ethernet(void) ESP_ERROR_CHECK(esp_eth_start(s_eth_handle)); } +namespace Effect { + extern const WaveForm_t OffsetPulseWave{gau8_offsetPulse, cu16_TemplateLength}; + extern const WaveForm_t InitSlopeWave{gau8_initSlope, cu16_TemplateLength}; + extern const WaveForm_t OffsetSlopeWave{gau8_offsetSlope, cu16_TemplateLength}; + extern const WaveForm_t FullSlopeWave{gau8_fullSlope, cu16_TemplateLength}; + extern const WaveForm_t FullPulseWave{gau8_fullPulse, cu16_TemplateLength}; + + const Macro macDemoStart = { + {64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF}, + {32, 1, StateMachine::UpdateIdle, 0xFF}, + }; + + const Macro macDemoPulse = { + {64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF}, + {32, 2, StateMachine::UpdateIdle, 0xFF}, + {64, 1, &OffsetPulseWave, StateMachine::UpdateWave, 0xFF}, + }; + + const Macro macDemoFlicker = { + {64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF}, + {32, 1, StateMachine::UpdateFlicker, 0xFF}, + }; +} + void LightingTask() { ESP_LOGI(TAG, "Start Lighting"); @@ -376,6 +364,70 @@ void LightingTask() { } fos::Task::delayMs(100); } + + + // ESP_LOGI(TAG, "Start Lighting"); + + // static I2cMaster master(I2C_NUM_0, GPIO_NUM_10, GPIO_NUM_11); + // static PmwExpanderPca9685 pca9685(master); + // PmwExpanderPca9685::GrayValues values; + + // AddressableLeRtz ledBar(GPIO_NUM_15, AddressableLeRtz::Type::Rgb, 6); + // std::vector pictoSpots(6); + // for (auto &&led : pictoSpots) { + // led.set(0, 0, 0); + // } + + // auto const &colorSource = activeState.color; + // std::array processors{ + // Effect::EffectProcessor(8), + // Effect::EffectProcessor(8), + // Effect::EffectProcessor(8), + // Effect::EffectProcessor(8), + // Effect::EffectProcessor(8), + // Effect::EffectProcessor(8)}; + + // processors[0].setEffect(Effect::macDemoStart, &colorSource, 16); + // processors[1].setEffect(Effect::macDemoStart, &colorSource, 32); + // processors[2].setEffect(Effect::macDemoPulse, &colorSource, 48); + // processors[3].setEffect(Effect::macDemoPulse, &colorSource, 64); + // processors[4].setEffect(Effect::macStartFlicker, &colorSource, 80); + // processors[5].setEffect(Effect::macStartIdle, &colorSource, 96); + + // int idx = 0; + // values.fill(0); + // while (true) { + // values[0] = activeState.color.getRed() * 257; + // values[1] = activeState.color.getGreen() * 257; + // values[2] = activeState.color.getBlue() * 257; + // pca9685.sendImage(values); + + // for (size_t i = 0; i < 600; i++) { + // Task::delayMs(20); + // for (size_t i = 0; i < pictoSpots.size(); i++) { + // pictoSpots[i] = processors[i].tick(); + // } + // ledBar.send(pictoSpots); + // } + + // processors[1].setEffect(Effect::macDark, &colorSource, 32); + // processors[2].setEffect(Effect::macDark, &colorSource, 48); + // processors[3].setEffect(Effect::macDark, &colorSource, 64); + // processors[4].setEffect(Effect::macDark, &colorSource, 80); + + // for (size_t i = 0; i < 50; i++) { + // Task::delayMs(20); + // for (size_t i = 0; i < pictoSpots.size(); i++) { + // pictoSpots[i] = processors[i].tick(); + // } + // ledBar.send(pictoSpots); + // } + + // processors[1].setEffect(Effect::macDemoStart, &colorSource, 32); + // processors[2].setEffect(Effect::macDemoPulse, &colorSource, 48); + // processors[3].setEffect(Effect::macDemoPulse, &colorSource, 64); + // processors[4].setEffect(Effect::macStartFlicker, &colorSource, 80); + // } } void MovementTask() { @@ -415,6 +467,108 @@ void MovementTask() { while (true) { fos::Task::delayMs(100); } + + + + // ESP_LOGI(TAG, "Start Stepper"); + // StepperDriverTb6612::Pins stepperPins{ + // LEDC_TIMER_0, + // LEDC_CHANNEL_0, + // GPIO_NUM_23, + // {GPIO_NUM_22, GPIO_NUM_21}, + // LEDC_CHANNEL_1, + // GPIO_NUM_20, + // {GPIO_NUM_19, GPIO_NUM_18}, + // }; + // constexpr int MicroSteps = 32; + // StepperDriverTb6612 stepperDriver(stepperPins, MicroSteps); + // stepperDriver.setPos(0); + + // StepperMotor::Config stepperCfg{1.8F, MicroSteps}; + // InputPort initSens(GPIO_NUM_3); + // initSens.setLogic(GpioPort::Logic::Inverted); + // StepperMotor stepper(stepperCfg, stepperDriver, initSens); + + // ESP_LOGI(TAG, "Find Home"); + + // stepper.findRef(); + // ESP_LOGI(TAG, "Fist Position is %f.2", stepper.getScaledPos() * 6); + // Task::delayMs(1000); + + // AddressableLeRtz ledRing(GPIO_NUM_9, AddressableLeRtz::Type::Rgb, 12); + // std::vector ledStrip(12); + // for (auto &&led : ledStrip) { + // led.set(0, 0, 0); + // } + + // uint32_t ledPos = 0; + // ledStrip[0].set( + // activeState.color.getRed(), + // activeState.color.getGreen(), + // activeState.color.getBlue(), + // 0); + // ledRing.send(ledStrip); + + // while (true) { + // ESP_LOGD(TAG, "Waiting for command"); + // auto cmd = commandQueue.dequeue(); + // if (cmd->type == MoveType::Step) { + // if (stepper.isIdle()) { + // auto delta = cmd->deltaPic; + // auto profile = cmd->profile; + + // for (auto &&led : ledStrip) { + // led.set(0, 0, 0); + // } + // ledPos = (ledPos + delta) % 6; + // activeState.pictogram = ledPos; + + // ledStrip[ledPos * 2].set( + // cmd->color.getRed(), + // cmd->color.getGreen(), + // cmd->color.getBlue(), + // 0); + // ledRing.send(ledStrip); + // MqttClient::publishPosition(ledPos); + + // delta = std::min(delta, 7); + // Syslog::logv(Syslog::Severity::Notice, "Running Stepper by %i steps with prof=%i a=%.2f w=%.2f", delta, profile, activeState.accel, activeState.speed); + // stepper.moveBy(delta * std::numbers::pi / 3, + // profile, activeState.accel, activeState.speed); + // } + // } else if (cmd->type == MoveType::AllPictograms) { + // activeState.pictogram = -1; + // stepper.findRef(); + // Task::delayMs(1000); + // for (size_t i = 0; i < 6; i++) { + // for (auto &&led : ledStrip) { + // led.set(0, 0, 0); + // } + // ledStrip[i * 2].set(0, 0, 16); + // ledRing.send(ledStrip); + // stepper.moveBy(std::numbers::pi / 3); + // while (!stepper.isIdle()) { + // Task::delayMs(100); + // } + // ESP_LOGI(TAG, "Position is %f.2", stepper.getScaledPos() * 6); + // } + // } else if (cmd->type == MoveType::Arbitrary) { + // activeState.pictogram = -1; + // ESP_LOGI(TAG, "Full Turn"); + // stepper.demoFullTurn(); + // Task::delayMs(4000); + + // while (true) { + // if (stepper.isIdle()) { + // ESP_LOGI(TAG, "Position is %f.2", stepper.getScaledPos() * 6); + // Task::delayMs(500); + // stepper.createDemoProfile(); + // } + // Task::delayMs(20); + // } + // } + // } + } void IoTest() diff --git a/main/watchMain.hpp b/main/watchMain.hpp new file mode 100644 index 0000000..a6c4ad5 --- /dev/null +++ b/main/watchMain.hpp @@ -0,0 +1,84 @@ +/** + * @file watchMain.hpp + * @author Gustice + * + * @copyright Copyright (c) 2024 + */ +#pragma once + + +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_eth_driver.h" +#include "esp_wifi.h" +#include "nvs_flash.h" +#include "esp_private/wifi.h" +#include "ethernet_init.h" +#include "esp_netif.h" +#include "esp_eth.h" + +// Component header +#include "Task.hpp" +#include "PwmExpanderPca9685.hpp" +#include "StepperDrv8833.hpp" +#include "stepperMotor.hpp" +#include "cliWrapper.hpp" +#include "udpSockets.hpp" +#include "tcpSockets.hpp" +#include "Mutex.hpp" +#include "Queue.hpp" +#include "Semaphore.hpp" +#include "Task.hpp" +#include "Artnet.h" +#include "Processor.hpp" + + +// #include "esp_chip_info.h" +// #include "esp_flash.h" +// #include "esp_log.h" +// #include "esp_netif.h" +// #include "esp_ota_ops.h" +// #include "esp_partition.h" +// #include "esp_random.h" +// #include "esp_system.h" +// #include "esp_vfs_fat.h" +// #include "esp_vfs_semihost.h" +// #include "esp_wifi.h" +// #include "fileSystem.hpp" +// #include "freertos/FreeRTOS.h" +// #include "freertos/task.h" +// #include "gpTimer.hpp" +// #include "gpioPort.hpp" +// #include "i2cPort.hpp" +// #include "lwip/apps/netbiosns.h" +// #include "mdns.h" +// #include "mqttClient.hpp" +// #include "nlohmann/json.hpp" +// #include "nvsFunctions.hpp" +// #include "pwmExpanderPca9685.hpp" +// #include "pwmPort.hpp" +// #include "rmtPort.hpp" +// #include "sdkconfig.h" +// #include "soc/soc_caps.h" +// #include "stepperMotor.hpp" +// #include "stepperTB6612.hpp" +// #include "stepperDrv8833.hpp" +// #include "string.h" +// #include "systemFunctions.hpp" +// #include "tcpSockets.hpp" +// #include "uartPort.hpp" +// #include "udpSockets.hpp" +// #include "webServer.hpp" +// #include "wifiFunctions.hpp" +// #include +// #include +// #include +// #include +// #include +