1023 lines
30 KiB
C++
1023 lines
30 KiB
C++
#include "led_animation.h"
|
|
|
|
#include <Arduino.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "common/HSVTable.h"
|
|
#include "common/neo_colors.h"
|
|
#include "common/color_tools.h"
|
|
|
|
static const char* tag = "led_anim";
|
|
|
|
|
|
int AnimTestModeCount = 0;
|
|
|
|
ANIM_STATUS animStatus;
|
|
WHITE_FILL_STATUS whiteStatus;
|
|
HUE_PALLET_DISPENSER HuePalletDispenser;// (1, 1, 1);
|
|
|
|
/********************************************************************/
|
|
|
|
// Animation Loop Template
|
|
EXIT_TYPE Animation_Loop(ANIM_STATUS &stateMon, int msFreq, TickType_t duration, std::function<int()> callback){
|
|
stateMon.busy = true;
|
|
ulTaskNotifyTake( pdTRUE, 0);
|
|
EXIT_TYPE ret = EXIT_NORMAL;
|
|
|
|
TickType_t startTicks = xTaskGetTickCount();
|
|
|
|
for(;;){
|
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
|
if(callback() == EXIT_FINISHED){ return EXIT_FINISHED; }
|
|
|
|
if(ulTaskNotifyTake( pdTRUE, msFreq - ( xTaskGetTickCount() - xLastWakeTime ))){ // delay
|
|
stateMon.busy = false;
|
|
return EXIT_FROM_NOTIFY;
|
|
}
|
|
|
|
// May have a duration limit
|
|
if(duration){
|
|
if((xLastWakeTime - startTicks) > duration){
|
|
return EXIT_TIMEOUT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int Const_White_Fill(WHITE_FILL_STATUS &status, FRONT_LIGHT light , float delayFactor, int countDown, int msFreq) { //int msRampUpTime, int msRampDownTime, int msHoldTime, int msFreq, float minDuty, float maxDuty){
|
|
status.busy = true;
|
|
ulTaskNotifyTake( pdTRUE, 0);
|
|
|
|
// Linear brightness rise
|
|
if(light.maxDuty > 100) { light.maxDuty = 100; }
|
|
if(light.maxDuty < 1){ light.maxDuty = 1; }
|
|
|
|
if (light.minDuty >= light.maxDuty) { light.minDuty = light.maxDuty -1; }
|
|
if (light.minDuty < 0) { light.minDuty = 0; }
|
|
|
|
// Pre Delay
|
|
if(delayFactor > 0.0){
|
|
int waitDelay = int((float)countDown * delayFactor);
|
|
ESP_LOGD(tag, "Const Light Delay: %d, countDown: %d", waitDelay, countDown);
|
|
if(ulTaskNotifyTake( pdTRUE, waitDelay)){ // delay
|
|
status.busy = false;
|
|
return EXIT_FROM_NOTIFY;
|
|
}
|
|
}
|
|
|
|
int rampSteps = (countDown - ((float)countDown * delayFactor)) / msFreq;
|
|
float dutyStep = ( light.maxDuty - pwmOut[animProps.frontLight.relayIndex]->currDuty ) / rampSteps;
|
|
if(dutyStep < 0.1) { dutyStep = 0.1; }
|
|
|
|
//Log.noticeln("<front> upTime: %d, hold: %d, downTime: %d, msFreq: %d, minDuty: %F, maxDuty: %F", msRampUpTime, msHoldTime, msRampDownTime, msFreq, minDuty, maxDuty);
|
|
//Log.noticeln("<front> dutyStep: %F", dutyStep);
|
|
// Ramp Up
|
|
for(;;){
|
|
//calc new newDuty value
|
|
float newDuty = pwmOut[animProps.frontLight.relayIndex]->currDuty + dutyStep;
|
|
|
|
//check if dslrbooth jumped ahead and catch up
|
|
if(delayFactor > 0){
|
|
if(status.dslrCountStatus > newDuty && ((status.dslrCountStatus-10) < 90)){
|
|
newDuty = status.dslrCountStatus;
|
|
}
|
|
}
|
|
|
|
// Set the new Duty
|
|
if(newDuty > light.maxDuty){ newDuty = light.maxDuty; }
|
|
pwmOut[animProps.frontLight.relayIndex]->setOutput(newDuty); // set duty
|
|
|
|
if(newDuty >= light.maxDuty) { break;} // break if complete
|
|
|
|
//check if there was a trigger to leave function
|
|
if(ulTaskNotifyTake( pdTRUE, msFreq )){ goto done; } // delay
|
|
}
|
|
|
|
// Hold Steady for msHoldTime
|
|
//Serial.printf("Holding at %.1f\n\r", pwmOut[animProps.frontLight.relay]->currDuty );
|
|
pwmOut[animProps.frontLight.relayIndex]->setOutput(light.maxDuty);
|
|
if(ulTaskNotifyTake( pdTRUE, light.holdTime )){ goto done; } // delay
|
|
|
|
// Linear Ramp Down to Min
|
|
rampSteps = light.rampTime / msFreq;
|
|
dutyStep = abs(( pwmOut[animProps.frontLight.relayIndex]->currDuty - light.minDuty) / rampSteps );
|
|
if(dutyStep < .1) { dutyStep = .1; }
|
|
//Serial.printf("stepDown: %.1f\n\r", dutyStep);
|
|
|
|
for(;;){
|
|
pwmOut[animProps.frontLight.relayIndex]->setOutput( pwmOut[animProps.frontLight.relayIndex]->currDuty - dutyStep ); // set duty
|
|
if( pwmOut[animProps.frontLight.relayIndex]->currDuty <= light.minDuty ) { break; } // break if complete
|
|
|
|
//if(status.repeat){ goto done;}
|
|
if(ulTaskNotifyTake( pdTRUE, msFreq )){ break; } // delay
|
|
}
|
|
|
|
done:
|
|
pwmOut[animProps.frontLight.relayIndex]->setOutput(light.minDuty);
|
|
status.busy = false;
|
|
status.dslrCountStatus = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// TODO Roamer/Helio type White Fill
|
|
//int Const_White_Fill_Disc
|
|
/****************************************************************/
|
|
int Animation_White_Fill(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msFillTime, rgbpixel_t& col, rgbpixel_t& baseCol)
|
|
{
|
|
stateMon.busy = true;
|
|
ulTaskNotifyTake( pdTRUE, 0);
|
|
|
|
int msDelay = msFillTime / strip.effSize;
|
|
|
|
// Fill background first
|
|
for(int i = 0; i < strip.effSize; i++){
|
|
strip.pixels[i] = baseCol; // skips index calc
|
|
}
|
|
|
|
// Linear fill
|
|
for(int i = 0; i < strip.effSize; i++){
|
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
|
strip.setPixel(i, col, 255);
|
|
strip.show(true);
|
|
if(ulTaskNotifyTake( pdTRUE, msDelay - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return 1; } // delay
|
|
}
|
|
|
|
stateMon.busy = false;
|
|
return 0;
|
|
}
|
|
|
|
/******************************** WHITE FILL MIRRORED *********************************/
|
|
EXIT_TYPE Animation_White_Fill_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event)
|
|
{
|
|
int msFillTime = 0;
|
|
if(event.countDown > 0){ // use countdown if available
|
|
msFillTime = event.countDown;
|
|
}else{
|
|
msFillTime = map(event.param1, 0, 100, 0, 10000);
|
|
if(msFillTime < 1000){ msFillTime = 1000; }
|
|
}
|
|
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
|
|
// TODO set msFreq from density
|
|
int msFreq = 25;
|
|
int halfSize = (strip.effSize / 2);
|
|
float steps = ((float)msFreq * (float)halfSize) / msFillTime;
|
|
strip.fill(col_black, 0, strip.effSize); // clear/off all pixels
|
|
|
|
float stepsTotal = steps;
|
|
//int startIndex = 0;
|
|
int endIndex = stepsTotal;
|
|
//uint8_t frac = 0;
|
|
ESP_LOGD(tag, "whitefill-> msFillTime: %d, halfsize: %d, steps: %.3f, freq: %d, hue: %d", msFillTime, halfSize, steps, msFreq, event.hue);
|
|
|
|
rgbpixel_t mainCol = GetColorFromHue(event.hue);
|
|
//rgbpixel_t fracCol; // = event.col1;
|
|
|
|
int lastEndIndex = 0;
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, [&](){
|
|
|
|
stepsTotal += steps;
|
|
endIndex = (int)stepsTotal;
|
|
if(endIndex != lastEndIndex){
|
|
for(int i = lastEndIndex; i < endIndex; i++){
|
|
strip.setPixelMirrored(i, mainCol);
|
|
}
|
|
lastEndIndex = endIndex;
|
|
strip.show(true);
|
|
}
|
|
|
|
if(endIndex > (halfSize -1)){ return EXIT_FINISHED; }
|
|
|
|
/*
|
|
frac = (stepsTotal - (int)stepsTotal) * 255;
|
|
fracCol = mainCol;
|
|
scalePixel(fracCol, frac);
|
|
linearizePixel(fracCol);
|
|
strip.setPixelMirrored(endIndex + 1, fracCol);
|
|
*/
|
|
//strip.show(true);
|
|
|
|
return EXIT_NORMAL;
|
|
});
|
|
|
|
// TODO remove this trace later
|
|
//Log.traceln("exited white fill");
|
|
return ret;
|
|
}
|
|
|
|
float calculateSteps(int msFreq, float LEDCount, int msFillTime) {
|
|
return (msFreq * LEDCount) / msFillTime;
|
|
}
|
|
|
|
void GetOptimizedStepInterval(float &steps, int &interval, int LEDCount, int msFillTime, int startInterval, float tolerance)
|
|
{
|
|
steps = calculateSteps(startInterval, LEDCount, msFillTime);
|
|
|
|
while (std::abs(steps - std::round(steps)) > tolerance) {
|
|
steps = calculateSteps(++startInterval, LEDCount, msFillTime);
|
|
}
|
|
interval = startInterval;
|
|
steps = round(steps);
|
|
}
|
|
|
|
|
|
EXIT_TYPE Animation_Linear_Brighten(LEDSTRIP& strip, ANIMATION_EVENT& event){
|
|
return EXIT_NORMAL;
|
|
}
|
|
|
|
|
|
EXIT_TYPE Animation_Tick_Fill(LEDSTRIP& strip, ANIMATION_EVENT& event){
|
|
return EXIT_NORMAL;
|
|
}
|
|
|
|
|
|
|
|
/******************************** COLORED SNAKES *********************************/
|
|
EXIT_TYPE Animation_Snakes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles)
|
|
{
|
|
bool mix = event.check1;
|
|
bool rotate = event.check2;
|
|
int sectors = constrain(event.param1/10, 1, 10);
|
|
int colorCount = constrain(event.param2/10, 1, 10);
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
int snakeSize = trunc(roundf(strip.effSize / (float)sectors));
|
|
|
|
HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount);
|
|
ESP_LOGD(tag, "<snakes> size: %d, sectors: %d, freq: %d", snakeSize, sectors, msFreq);
|
|
|
|
// Create Color Pallet Array
|
|
rgbpixel_t col[colorCount];
|
|
if(colorCount == 1){
|
|
col[0] = GetColorFromHue(event.hue);
|
|
}else{
|
|
for(int i = 0; i < colorCount; i++){
|
|
float hue = HuePalletDispenser.GetNextPalletHue();
|
|
col[i] = HUEtoRGB( hue );
|
|
//ESP_LOGD(tag, "<sectors> hue%d: ", hue);
|
|
}
|
|
}
|
|
|
|
|
|
LED_DIR dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV;
|
|
int dirVal = (dir==DIR_FWD)? 1 : -1;
|
|
int pixIndex = 0, rotateOffset = 0, colIndex = 0;
|
|
bool colorCycle = true;
|
|
|
|
strip.fill({0,0,0}, 0, strip.effSize);
|
|
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function<int()>([&]() -> int {
|
|
|
|
if(colorCycle){
|
|
for(int sector = 0; sector < sectors; sector++){
|
|
int pix = (sector * snakeSize) + pixIndex + rotateOffset;
|
|
if(mix){
|
|
strip.setPixel(pix * dirVal, (rgbpixel_t)col[(colIndex + sector)%colorCount]);
|
|
}else{
|
|
strip.setPixel(pix * dirVal, (rgbpixel_t)col[colIndex]);
|
|
}
|
|
}
|
|
}else{
|
|
for(int sector = 0; sector < sectors; sector++){
|
|
int pix = (sector * snakeSize) + pixIndex + rotateOffset;
|
|
strip.setPixel(pix * dirVal, {0,0,0});
|
|
}
|
|
}
|
|
|
|
if(rotate){ // turn off front pixels
|
|
strip.rotatePixels(dir);
|
|
rotateOffset = ++rotateOffset % strip.effSize;
|
|
}
|
|
strip.show(true);
|
|
|
|
pixIndex = ++pixIndex % snakeSize;
|
|
|
|
if(pixIndex == 0){
|
|
if(colorCycle){
|
|
colorCycle = false;
|
|
}else{
|
|
// Check if there's an iteration limit
|
|
if(cycles){
|
|
if(--cycles <= 0){ return EXIT_FINISHED; }
|
|
}
|
|
// Once full cycle here
|
|
if(dirType == DT_BOTH){
|
|
dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD;
|
|
dirVal = -dirVal;
|
|
}
|
|
colorCycle = true;
|
|
colIndex = ++colIndex % colorCount; // next color
|
|
}
|
|
}
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************** COLORED COMETS *********************************/
|
|
#define COMET_SIZE_FACTOR 0.15
|
|
#define COMET_FADE_FACTOR1 128
|
|
#define COMET_FADE_FACTOR2 192
|
|
|
|
EXIT_TYPE Animation_Comets(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles){
|
|
int sectorCount = constrain(event.param1/10, 1, 10); // cometCount also
|
|
int colorCount = constrain(event.param2/10, 1, 10); // color divisions
|
|
bool mix = event.check1;
|
|
bool rotate = true;
|
|
bool randomDecay = event.check2;
|
|
bool shorterTail = event.check3;
|
|
uint8_t fadeFactor = COMET_FADE_FACTOR1;
|
|
if(shorterTail){ fadeFactor = COMET_FADE_FACTOR2; }
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
int sectorSize = trunc(roundf(strip.effSize / (float)sectorCount));
|
|
|
|
int cometSize = (strip.effSize / sectorCount) * COMET_SIZE_FACTOR;
|
|
if(cometSize < 1){ cometSize = 1;}
|
|
|
|
HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount);
|
|
ESP_LOGD(tag, "<sectors> size: %d, sectorCount: %d, freq: %d", sectorSize, sectorCount, msFreq);
|
|
|
|
// Create Color Pallet Array
|
|
rgbpixel_t col[colorCount];
|
|
if(colorCount == 1){
|
|
col[0] = GetColorFromHue(event.hue);
|
|
}else{
|
|
for(int i = 0; i < colorCount; i++){
|
|
float hue = HuePalletDispenser.GetNextPalletHue();
|
|
col[i] = HUEtoRGB( hue );
|
|
//ESP_LOGD(tag, "<sectors> hue%d: ", hue);
|
|
}
|
|
}
|
|
|
|
LED_DIR dir;
|
|
if(dirType == DT_BOTH){
|
|
dir = DIR_FWD;
|
|
}else{
|
|
dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV;
|
|
}
|
|
|
|
strip.fill({0,0,0}, 0, strip.effSize);
|
|
|
|
int rotateIndex = 0;
|
|
int cycleCount = 0;
|
|
int colIndex = 0;
|
|
int rotOffset = 0;
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function<int()>([&]() -> int {
|
|
|
|
// Random Tail Decay here....
|
|
if(randomDecay){
|
|
for (int j = 0; j < strip.effSize; j++){
|
|
if (random(10) > 5){
|
|
fadeToBlackBy(strip.pixels[j], fadeFactor);
|
|
}
|
|
}
|
|
}else{ // Regular Tail Decay
|
|
for (int j = 0; j < strip.effSize; j++){
|
|
fadeToBlackBy(strip.pixels[j], fadeFactor);
|
|
}
|
|
}
|
|
|
|
// Draw Comets
|
|
if(mix){
|
|
for(int sector = 0; sector < sectorCount; sector++){
|
|
strip.fill((rgbpixel_t)col[(colIndex + sector) % colorCount], rotOffset + (sector * sectorSize), cometSize);
|
|
}
|
|
}else{
|
|
for(int sector = 0; sector < sectorCount; sector++){
|
|
strip.fill((rgbpixel_t)col[colIndex], rotOffset + (sector * sectorSize), cometSize);
|
|
}
|
|
}
|
|
|
|
rotateIndex = ++rotateIndex % strip.effSize;
|
|
|
|
if(dir == DIR_FWD){ rotOffset++; }
|
|
else{ rotOffset--; }
|
|
|
|
// full rotation
|
|
if(rotateIndex == 0){
|
|
// reverse rotation
|
|
if(dirType == DT_BOTH){
|
|
dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD;
|
|
}
|
|
|
|
if(cycles){
|
|
cycleCount++;
|
|
if(cycleCount >= cycles){
|
|
return EXIT_FINISHED;
|
|
}
|
|
}
|
|
|
|
colIndex = ++colIndex % colorCount; // Increment color index
|
|
}
|
|
strip.show(true);
|
|
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void fadeToBlackBy(rgbpixel_t& pixel, uint8_t fadeAmount) {
|
|
pixel.red = (pixel.red <= 8) ? 0 : pixel.red - ((pixel.red * fadeAmount) >> 8);
|
|
pixel.grn = (pixel.grn <= 8) ? 0 : pixel.grn - ((pixel.grn * fadeAmount) >> 8);
|
|
pixel.blu = (pixel.blu <= 8) ? 0 : pixel.blu - ((pixel.blu * fadeAmount) >> 8);
|
|
}
|
|
|
|
inline uint8_t nscale8(uint8_t i, uint8_t scale) {
|
|
return (((int)i * (int)(scale)) >> 8) + ((i && scale) ? 1 : 0);
|
|
}
|
|
|
|
|
|
/******************************** COLORED SECTORS *********************************/
|
|
EXIT_TYPE Animation_Sectors(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles){
|
|
bool rotate = event.check2;
|
|
int sectorCount = constrain(event.param1/10, 2, 10);
|
|
int colorCount = constrain(event.param2/10, 1, 10);
|
|
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
int sectorSize = trunc(roundf(strip.effSize / (float)sectorCount));
|
|
|
|
ESP_LOGD(tag, "hue: %d, range: %d\n", event.hue, event.hueRange);
|
|
HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount);
|
|
ESP_LOGD(tag, "size: %d, sectorCount: %d, colors: %d, freq: %d", sectorSize, sectorCount, colorCount, msFreq);
|
|
|
|
// Create Color Pallet Array
|
|
rgbpixel_t col[colorCount];
|
|
if(colorCount == 1){
|
|
col[0] = GetColorFromHue(event.hue);
|
|
}else{
|
|
for(int i = 0; i < colorCount; i++){
|
|
float hue = HuePalletDispenser.GetNextPalletHue();
|
|
col[i] = HUEtoRGB( hue );
|
|
//ESP_LOGD(tag, "hue%d: ", hue);
|
|
}
|
|
}
|
|
|
|
LED_DIR dir;
|
|
if(dirType == DT_BOTH){
|
|
dir = DIR_FWD;
|
|
}else{
|
|
dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV;
|
|
}
|
|
|
|
strip.fill({0,0,0}, 0, strip.effSize);
|
|
|
|
bool updateColors = true;
|
|
int rotateIndex = 0;
|
|
int cycleCount = 0;
|
|
int colIndex = 0;
|
|
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function<int()>([&]() -> int {
|
|
|
|
if(updateColors){ //
|
|
for(int sector = 0; sector < sectorCount; sector++){
|
|
strip.fill((rgbpixel_t)col[(colIndex + sector) % colorCount], sector * sectorSize, sectorSize);
|
|
}
|
|
colIndex = ++colIndex % colorCount; // Increment color index
|
|
updateColors = false;
|
|
}
|
|
|
|
if(rotate){ // turn off front pixels
|
|
strip.rotatePixels(dir);
|
|
rotateIndex = ++rotateIndex % strip.effSize;
|
|
|
|
// full rotation
|
|
if(rotateIndex == 0){
|
|
// reverse rotation
|
|
if(dirType == DT_BOTH){
|
|
dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD;
|
|
}
|
|
|
|
if(cycles){
|
|
cycleCount++;
|
|
if(cycleCount >= cycles){
|
|
return EXIT_FINISHED;
|
|
}
|
|
}
|
|
|
|
updateColors = true;
|
|
}
|
|
}
|
|
|
|
strip.show(true);
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************** DASHES SECTORS *********************************/
|
|
EXIT_TYPE Animation_Dashes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType, int cycles){
|
|
int sectorCount = constrain(event.param1/10, 1, 10);
|
|
int colorCount = constrain(event.param2/10, 1, 10);
|
|
bool mix = event.check1;
|
|
bool rotate = event.check2;
|
|
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
int sectorSize = trunc(roundf(strip.effSize / (float)sectorCount));
|
|
|
|
HuePalletDispenser.Initialize(event.hue, event.hueRange, colorCount);
|
|
ESP_LOGD(tag, "<sectors> size: %d, sectorCount: %d, freq: %d", sectorSize, sectorCount, msFreq);
|
|
|
|
// Create Color Pallet Array
|
|
rgbpixel_t col[colorCount];
|
|
if(colorCount == 1){
|
|
col[0] = GetColorFromHue(event.hue);
|
|
}else{
|
|
for(int i = 0; i < colorCount; i++){
|
|
float hue = HuePalletDispenser.GetNextPalletHue();
|
|
col[i] = HUEtoRGB( hue );
|
|
//ESP_LOGD(tag, "<sectors> hue%d: ", hue);
|
|
}
|
|
}
|
|
|
|
LED_DIR dir;
|
|
if(dirType == DT_BOTH){
|
|
dir = DIR_FWD;
|
|
}else{
|
|
dir = (dirType == DT_FWD)? DIR_FWD : DIR_REV;
|
|
}
|
|
|
|
strip.fill({0,0,0}, 0, strip.effSize);
|
|
|
|
bool updateColors = true;
|
|
int rotateIndex = 0;
|
|
int cycleCount = 0;
|
|
int colIndex = 0;
|
|
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function<int()>([&]() -> int {
|
|
if(updateColors){ //
|
|
if(mix){
|
|
for(int sector = 0; sector < sectorCount; sector++){
|
|
strip.fill((rgbpixel_t)col[(colIndex + sector) % colorCount], sector * sectorSize, sectorSize/2);
|
|
}
|
|
}else{
|
|
for(int sector = 0; sector < sectorCount; sector++){
|
|
strip.fill((rgbpixel_t)col[colIndex], sector * sectorSize, sectorSize/2);
|
|
}
|
|
}
|
|
|
|
colIndex = ++colIndex % colorCount; // Increment color index
|
|
updateColors = false;
|
|
}
|
|
|
|
if(rotate){ // turn off front pixels
|
|
strip.rotatePixels(dir);
|
|
rotateIndex = ++rotateIndex % strip.effSize;
|
|
|
|
// full rotation
|
|
if(rotateIndex == 0){
|
|
// reverse rotation
|
|
if(dirType == DT_BOTH){
|
|
dir = (dir == DIR_FWD)? DIR_REV : DIR_FWD;
|
|
}
|
|
|
|
if(cycles){
|
|
cycleCount++;
|
|
if(cycleCount >= cycles){
|
|
return EXIT_FINISHED;
|
|
}
|
|
}
|
|
|
|
updateColors = true;
|
|
}
|
|
}
|
|
|
|
strip.show(true);
|
|
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/****************************************************************/
|
|
void Animation_TransTo_Color(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msTransTime, int msFreq, rgbpixel_t newCol)
|
|
{
|
|
stateMon.busy = true;
|
|
ulTaskNotifyTake( pdTRUE, 0);
|
|
|
|
int numSteps = (msTransTime / msFreq);
|
|
if (numSteps == 0) { return; }
|
|
|
|
for (uint8_t step = 0; step < numSteps; step++) {
|
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
|
|
|
for (size_t i = 0; i < strip.effSize; i++) {
|
|
rgbpixel_t colorDiff = {
|
|
.red = static_cast<uint8_t>((newCol.red - strip.pixels[i].red) / numSteps),
|
|
.grn = static_cast<uint8_t>((newCol.grn - strip.pixels[i].grn) / numSteps),
|
|
.blu = static_cast<uint8_t>((newCol.blu - strip.pixels[i].blu) / numSteps)
|
|
};
|
|
strip.pixels[i].red += colorDiff.red;
|
|
strip.pixels[i].grn += colorDiff.grn;
|
|
strip.pixels[i].blu += colorDiff.blu;
|
|
}
|
|
|
|
strip.show(true);
|
|
if(ulTaskNotifyTake( pdTRUE, msFreq - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return; } // delay
|
|
}
|
|
stateMon.busy = false;
|
|
}
|
|
|
|
|
|
/******************************** HUE SPECTRUM MIRRORED *********************************/
|
|
|
|
EXIT_TYPE Animation_Hue_Spectrum_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration){
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
Fill_Hue_Spectrum_Mirrored(strip, event.hue, event.hueRange);
|
|
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, duration, std::function<int()>([&]() -> int {
|
|
strip.show(true);
|
|
strip.rotatePixels(DIR_FWD);
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Fill_Hue_Spectrum(LEDSTRIP& strip, float hue, float range)
|
|
{
|
|
HuePalletDispenser.Initialize(hue, range, strip.effSize);
|
|
for(int i = 0; i < strip.effSize; i++){
|
|
strip.setPixel(i, HUEtoRGB( HuePalletDispenser.GetNextPalletHue() ));
|
|
}
|
|
}
|
|
|
|
void Fill_Hue_Spectrum_Mirrored(LEDSTRIP& strip, float hue1, float hue2)
|
|
{
|
|
HuePalletDispenser.Initialize(hue1, hue2, strip.effSize / 2.0);
|
|
|
|
for(int i = 0; i < strip.effSize/2; i++){
|
|
strip.setPixelMirrored(i, HUEtoRGB( HuePalletDispenser.GetNextPalletHue() ));
|
|
}
|
|
}
|
|
|
|
|
|
/******************************** PULSE COLOR CYCLING *********************************/
|
|
//TODO Add Linearizing for smoother transitions
|
|
#define PWR_STEP 8
|
|
EXIT_TYPE Animation_Pulse_Color_Cycling(LEDSTRIP& strip, ANIMATION_EVENT& event){
|
|
int colorSteps = event.param1/10.0;
|
|
if(colorSteps < 3){ colorSteps = 2; }
|
|
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
|
|
HuePalletDispenser.Initialize(event.hue, event.hueRange, colorSteps);
|
|
|
|
int pwrStep = PWR_STEP;
|
|
int pwr = pwrStep;
|
|
|
|
rgbpixel_t col = HUEtoRGB(HuePalletDispenser.GetNextPalletHue());
|
|
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function<int()>([&]() -> int {
|
|
// ramp up/down brightness
|
|
rgbpixel_t col_pwr = col;
|
|
scalePixel(col_pwr, (uint8_t)pwr);
|
|
linearizePixel(col_pwr);
|
|
strip.fill(col_pwr, 0, strip.effSize);
|
|
strip.show(true);
|
|
|
|
pwr += pwrStep;
|
|
if(pwr >= (256-PWR_STEP) || pwr <= PWR_STEP){
|
|
pwrStep = -pwrStep; // switch brightness direction
|
|
|
|
// Change color
|
|
if(pwrStep > 0){
|
|
col = HUEtoRGB(HuePalletDispenser.GetNextPalletHue());
|
|
}
|
|
}
|
|
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************** RAINBOW *********************************/
|
|
// TODO Need more Red for rainboow
|
|
EXIT_TYPE Animation_Rainbow(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration){
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
double hueStep = 359.2 / (strip.effSize-1);
|
|
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
|
|
for(int i = 0; i < strip.effSize; i++){
|
|
strip.setPixel(i, HUEtoRGB((int)round(i*hueStep)));
|
|
}
|
|
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, duration, std::function<int()>([&]() -> int {
|
|
strip.rotatePixels(DIR_FWD);
|
|
strip.show(true);
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************** FIRE ANIMATION *********************************/
|
|
//TODO Get these fire params from json
|
|
#define FIRE_SPARK_PIXEL_PERCENT 15
|
|
#define MIN_FIRE_COOLING 20
|
|
#define MAX_FIRE_COOLING 100
|
|
#define MIN_FIRE_SPARKING 50
|
|
#define MAX_FIRE_SPARKING 200
|
|
uint8_t *heat;
|
|
uint8_t sparkCount;
|
|
|
|
EXIT_TYPE Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event){
|
|
int msFreq = CalcEventInterval(event.speed, MIN_LED_UPDATE_INTERVAL, 100);
|
|
bool cycleColors = event.check1;
|
|
uint8_t sparking = map(event.param1, 0, 100, MIN_FIRE_SPARKING, MAX_FIRE_SPARKING); // input, input max, output min, out max
|
|
uint8_t cooling = map(event.param2, 0, 100, MIN_FIRE_COOLING, MAX_FIRE_COOLING);
|
|
|
|
strip.powerDiv = (event.check4) ? 1 : 0;
|
|
|
|
int duration = map(event.hueRange, 0, 360, 0, 60000 ); // upto 1 minute
|
|
if(cycleColors && duration == 0) { duration = 10000; }
|
|
if(!cycleColors && duration > 0) { duration = 0; }
|
|
|
|
FIRE_COLOR fireColor = RED_FIRE;
|
|
|
|
// Choose which Color comes closest
|
|
if(event.hue = -1 || event.hue == -2) { event.hue == 0;}
|
|
rgbpixel_t col = HUEtoRGB(event.hue);
|
|
if (col.red >= col.grn && col.red >= col.blu) {
|
|
fireColor = RED_FIRE;
|
|
} else if (col.grn >= col.red && col.grn >= col.blu) {
|
|
fireColor = GREEN_FIRE;
|
|
} else {
|
|
fireColor = BLUE_FIRE;
|
|
}
|
|
|
|
EXIT_TYPE ret;
|
|
while(1){
|
|
ret = Animation_Loop( animStatus, msFreq, duration, std::function<int()>([&]() -> int {
|
|
Fire_Update( strip, fireColor, cooling, sparking);
|
|
strip.show(true);
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
if(ret == EXIT_TIMEOUT){
|
|
fireColor = (FIRE_COLOR)(((int)fireColor + 1) % 3);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void LEDStrip_FireInit(LEDSTRIP& strip){
|
|
sparkCount = (strip.effSize/2 * FIRE_SPARK_PIXEL_PERCENT) / 100;
|
|
heat = new uint8_t[strip.effSize/2];
|
|
for(int i = 0; i < (strip.effSize/2); i++){
|
|
heat[i] = 0;
|
|
}
|
|
|
|
srand(70);
|
|
}
|
|
|
|
void Fire_Update( LEDSTRIP& strip, FIRE_COLOR fire, int Cooling, int Sparking) {
|
|
int cooldown;
|
|
|
|
// Step 1. Cool down every cell a little
|
|
for( int i = 0; i < (strip.effSize/2); i++) {
|
|
cooldown = rand() % ((Cooling * 10) / (strip.effSize/2) + 2);
|
|
|
|
if(cooldown > heat[i]) {
|
|
heat[i] = 0;
|
|
}else {
|
|
heat[i] -= cooldown;
|
|
}
|
|
}
|
|
|
|
// Step 2. Heat from each cell drifts 'up' and diffuses a little
|
|
// Starts from farthest and work down to the source
|
|
for( int k = (strip.effSize/2) - 1; k >= 2; k--) {
|
|
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
|
|
}
|
|
|
|
// Step 3. Randomly ignite new 'sparks' near the bottom
|
|
if( (rand() % 255) < Sparking ) {
|
|
int y = (rand() % sparkCount);
|
|
heat[y] += 160 + (rand() % 95);
|
|
}
|
|
|
|
// Step 4. Convert heat to LED colors
|
|
for( int j = 0; j < strip.effSize/2; j++) {
|
|
strip.setPixelMirrored(j, GetPixelHeatColor(fire, heat[j]));
|
|
}
|
|
}
|
|
|
|
rgbpixel_t GetPixelHeatColor ( FIRE_COLOR fire, uint8_t temperature){
|
|
rgbpixel_t led;
|
|
|
|
// Scale 'heat' down from 0-255 to 0-191
|
|
//uint8_t T = ((uint16_t)(temperature*191))>>8;
|
|
uint8_t T = (uint16_t)(temperature*120) >> 8;
|
|
|
|
// calculate ramp up from (heat ramp)
|
|
int hr = T & 0x3F; // 0..63
|
|
hr <<= 2; // scale up to 0..252
|
|
|
|
// Choose Fire Color
|
|
switch(fire){
|
|
case RED_FIRE:{
|
|
//if((hr + 32) & 255){ hr = 255; }
|
|
if((hr + 32) > 255){ hr = 255; }
|
|
// figure out which third of the spectrum we're in:
|
|
if( T > 117) { // hottest
|
|
led = (rgbpixel_t){ .red=120, .grn=120, .blu=(uint8_t)hr }; // spark color
|
|
} else if( T > 48 ) { // middle
|
|
led = (rgbpixel_t){ .red=255, .grn=(uint8_t)hr, .blu=0 }; // flame color
|
|
} else{ // coolest
|
|
led = (rgbpixel_t){ .red=(uint8_t)hr, .grn=0, .blu=0 }; // cooling color
|
|
}
|
|
break;
|
|
}
|
|
case BLUE_FIRE:{
|
|
if( T > 117) { // hottest
|
|
led = (rgbpixel_t){ .red=120, .grn=(uint8_t)hr, .blu=120 };
|
|
} else if( T > 48 ) { // middle
|
|
led = (rgbpixel_t){ .red=(uint8_t)hr, .grn=0, .blu=255 };
|
|
} else { // coolest
|
|
led = (rgbpixel_t){ .red=0, .grn=0,.blu=(uint8_t)hr };
|
|
}
|
|
break;
|
|
}
|
|
default:{ // GRN_FIRE
|
|
if( T > 117) { // hottest
|
|
led = (rgbpixel_t){ .red=(uint8_t)hr, .grn=120, .blu=120 };
|
|
}else if ( T > 48 ) { // middle
|
|
led = (rgbpixel_t){ .red=0, .grn=255, .blu=(uint8_t)hr };
|
|
}else { // coolest
|
|
led = (rgbpixel_t){ .red=0, .grn=(uint8_t)hr, .blu=0 };
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return led;
|
|
}
|
|
|
|
|
|
|
|
/******************************** Serial Out Toggle *********************************/
|
|
EXIT_TYPE Animation_Serial_Test2(LEDSTRIP& strip, int msFreq, const char* s1, const char* s2){
|
|
//Animation_Loop( animStatus, msFreq, [&](){
|
|
EXIT_TYPE ret = Animation_Loop( animStatus, msFreq, INFINITE_LOOP, std::function<int()>([&]() -> int {
|
|
static bool x = false;
|
|
|
|
if(x){
|
|
Serial.print("[ "); Serial.print( s1 ); Serial.print(" ]\r");
|
|
}else{
|
|
Serial.print("[ "); Serial.print( s2 ); Serial.print(" ]\r");
|
|
}
|
|
x = !x;
|
|
|
|
strip.show(true);
|
|
return EXIT_NORMAL;
|
|
}));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************** *********************************/
|
|
|
|
void Animation_SparkleHue(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msFreq, uint8_t hueRange, uint8_t hue, uint8_t sat, uint8_t val)
|
|
{
|
|
stateMon.busy = true;
|
|
ulTaskNotifyTake( pdTRUE, 0);
|
|
|
|
for(;;){
|
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
|
|
|
//decay all leds
|
|
for(int i = 0; i < strip.effSize; i++){
|
|
// strip.scale(strip.pixels[i], 0.5 * 0xFF);
|
|
}
|
|
|
|
// Random Pixel
|
|
//int index = random(strip.effSize);
|
|
//uint16_t h = (random(hueRange * 2) - hueRange) % HUE_MAX;
|
|
//strip.setPixel(index, h, sat, val);
|
|
|
|
strip.show(true);
|
|
if(ulTaskNotifyTake( pdTRUE, msFreq - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return; } // delay
|
|
}
|
|
|
|
stateMon.busy = false;
|
|
}
|
|
|
|
void Animation_SparkleColor(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msFreq, rgbpixel_t col)
|
|
{
|
|
stateMon.busy = true;
|
|
ulTaskNotifyTake( pdTRUE, 0);
|
|
|
|
for(;;){
|
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
|
|
|
//decay all leds
|
|
for(int i = 0; i < strip.effSize; i++){
|
|
// strip.scale(strip.pixels[i], 0.5 * 0xFF);
|
|
}
|
|
|
|
// Random Pixel
|
|
int index = random(strip.effSize);
|
|
strip.pixels[index] = col;
|
|
|
|
strip.show(true);
|
|
if(ulTaskNotifyTake( pdTRUE, msFreq - (xTaskGetTickCount() - xLastWakeTime ) == pdTRUE )){ return; } // delay
|
|
}
|
|
|
|
stateMon.busy = false;
|
|
}
|
|
|
|
|
|
|
|
/******************************** test Functions *********************************/
|
|
EXIT_TYPE Animation_SetPixel(LEDSTRIP& strip, ANIMATION_EVENT& event){
|
|
return EXIT_NORMAL;
|
|
}
|
|
|
|
EXIT_TYPE Animation_Clear(LEDSTRIP& strip, ANIMATION_EVENT& event){
|
|
return EXIT_NORMAL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/******************************** *********************************/
|
|
|
|
// This takes into account that the web color picker hue is reversed
|
|
float GetHueRangeWeb(float hue1, float hue2){
|
|
float rng;
|
|
if (hue1 > hue2) { rng = hue1 - hue2; }
|
|
else { rng = 360.0 + hue1 - hue2; }
|
|
|
|
if (rng < 0.0) { rng += 360.0; }
|
|
else if (rng > 360.0) { rng -= 360.0; }
|
|
|
|
return rng;
|
|
}
|
|
|
|
float GetHueRange(float hue1, float hue2){
|
|
float range = hue2 - hue1;
|
|
if(hue2 < hue1){ range += 360.0; }
|
|
//Log.noticeln("<anims> hue range: %d", hueRange);
|
|
return range;
|
|
}
|
|
|
|
|
|
|
|
/******************************** SPEED CALC HELPER FUNCTIONS *********************************/
|
|
|
|
void Init_Speed_Range(float startSpeed, float endSpeed, int steps){
|
|
|
|
}
|
|
|
|
float GetNextSpeed(void){
|
|
return 0.0;
|
|
}
|
|
|
|
|
|
|
|
int CalcEventInterval(float speed, int min, int max){
|
|
int range = max - min;
|
|
return (max - ((speed/100.0) * range));
|
|
}
|
|
|
|
rgbpixel_t GetColorFromHue(int hue){
|
|
if(hue == -1){
|
|
return col_white;
|
|
}else if(hue == -2){
|
|
return col_black;
|
|
}else if(hue >= 0 && hue <= 360){
|
|
return HUEtoRGB(hue);
|
|
}
|
|
return col_black;
|
|
}
|
|
|
|
|
|
|