Merge branch 'custom-effects' into mdev
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
; default_envs = travis_esp8266, travis_esp32
|
||||
|
||||
# Release binaries
|
||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3
|
||||
; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3
|
||||
|
||||
# Build everything
|
||||
; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
|
||||
@@ -32,6 +32,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s
|
||||
; default_envs = esp8285_4CH_MagicHome
|
||||
; default_envs = esp8285_H801
|
||||
; default_envs = d1_mini_5CH_Shojo_PCB
|
||||
default_envs = esp32mdev
|
||||
; default_envs = wemos_shield_esp32
|
||||
; default_envs = m5atom
|
||||
; default_envs = esp32_eth
|
||||
@@ -440,6 +441,22 @@ lib_deps = ${esp8266.lib_deps}
|
||||
# custom board configurations
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
[env:esp32mdev]
|
||||
board = esp32dev
|
||||
platform = ${esp32.platform}
|
||||
upload_speed = 460800 ; or 921600
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32}
|
||||
-D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
|
||||
-D USERMOD_AUDIOREACTIVE
|
||||
-D USERMOD_CUSTOMEFFECTS
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
https://github.com/blazoncek/arduinoFFT.git ; arduinoFFT @ 1.5.6
|
||||
; monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
|
||||
[env:wemos_shield_esp32]
|
||||
board = esp32dev
|
||||
platform = espressif32@3.2
|
||||
|
||||
2718
usermods/customeffects/arti.h
Normal file
2718
usermods/customeffects/arti.h
Normal file
File diff suppressed because it is too large
Load Diff
540
usermods/customeffects/arti_wled.h
Normal file
540
usermods/customeffects/arti_wled.h
Normal file
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
@title Arduino Real Time Interpreter (ARTI)
|
||||
@file arti_wled_plugin.h
|
||||
@version 0.3.1
|
||||
@date 20220818
|
||||
@author Ewoud Wijma
|
||||
@repo https://github.com/ewoudwijma/ARTI
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// For testing porposes, definitions should not only run on Arduino but also on Windows etc.
|
||||
// Because compiling on arduino takes seriously more time than on Windows.
|
||||
// The plugin.h files replace native arduino calls by windows simulated calls (e.g. setPixelColor will become printf)
|
||||
|
||||
#define ARTI_ARDUINO 1
|
||||
#define ARTI_EMBEDDED 2
|
||||
#ifdef ESP32 //ESP32 is set in wled context: small trick to set WLED context
|
||||
#define ARTI_PLATFORM ARTI_ARDUINO // else on Windows/Linux/Mac...
|
||||
#endif
|
||||
|
||||
#if ARTI_PLATFORM == ARTI_ARDUINO
|
||||
#include "arti.h"
|
||||
#else
|
||||
#include "../arti.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
//make sure the numbers here correspond to the order in which these functions are defined in wled.json!!
|
||||
enum Externals
|
||||
{
|
||||
F_ledCount,
|
||||
F_matrixWidth,
|
||||
F_matrixHeight,
|
||||
F_setPixelColor,
|
||||
F_leds,
|
||||
F_setPixels,
|
||||
F_hsv,
|
||||
F_rgbw,
|
||||
|
||||
F_setRange,
|
||||
F_fill,
|
||||
F_colorBlend,
|
||||
F_colorWheel,
|
||||
F_colorFromPalette,
|
||||
F_beatSin,
|
||||
F_fadeToBlackBy,
|
||||
F_iNoise,
|
||||
F_fadeOut,
|
||||
|
||||
F_counter,
|
||||
F_segcolor,
|
||||
F_speedSlider,
|
||||
F_intensitySlider,
|
||||
F_custom1Slider,
|
||||
F_custom2Slider,
|
||||
F_custom3Slider,
|
||||
F_sampleAvg,
|
||||
|
||||
F_shift,
|
||||
F_circle2D,
|
||||
|
||||
F_constrain,
|
||||
F_map,
|
||||
F_seed,
|
||||
F_random,
|
||||
F_sin,
|
||||
F_cos,
|
||||
F_abs,
|
||||
F_min,
|
||||
F_max,
|
||||
F_floor,
|
||||
|
||||
F_hour,
|
||||
F_minute,
|
||||
F_second,
|
||||
F_millis,
|
||||
|
||||
F_time,
|
||||
F_triangle,
|
||||
F_wave,
|
||||
F_square,
|
||||
F_clamp,
|
||||
|
||||
F_printf
|
||||
};
|
||||
|
||||
#if ARTI_PLATFORM != ARTI_ARDUINO
|
||||
#define PI 3.141592654
|
||||
#endif
|
||||
|
||||
float ARTI::arti_external_function(uint8_t function, float par1, float par2, float par3, float par4, float par5)
|
||||
{
|
||||
// MEMORY_ARTI("fun %d(%f, %f, %f)\n", function, par1, par2, par3);
|
||||
#if ARTI_PLATFORM == ARTI_ARDUINO
|
||||
switch (function) {
|
||||
case F_setPixelColor: {
|
||||
if (par3 == floatNull)
|
||||
SEGMENT.setPixelColor(((uint16_t)par1)%SEGLEN, (uint32_t)par2);
|
||||
else
|
||||
SEGMENT.setPixelColorXY((uint16_t)par1, (uint16_t)par2, (uint32_t)par3);
|
||||
return floatNull;
|
||||
}
|
||||
case F_setPixels:
|
||||
// setPixels(leds); No action needed as allready stored via sPC
|
||||
return floatNull;
|
||||
case F_hsv:
|
||||
{
|
||||
CRGB color = CHSV((uint8_t)par1, (uint8_t)par2, (uint8_t)par3);
|
||||
return RGBW32(color.r, color.g, color.b, 0);
|
||||
}
|
||||
case F_rgbw:
|
||||
return RGBW32((uint8_t)par1, (uint8_t)par2, (uint8_t)par3, (uint8_t)par4);
|
||||
|
||||
case F_setRange: {
|
||||
strip.setRange((uint16_t)par1, (uint16_t)par2, (uint32_t)par3);
|
||||
return floatNull;
|
||||
}
|
||||
case F_fill: {
|
||||
SEGMENT.fill((uint32_t)par1);
|
||||
return floatNull;
|
||||
}
|
||||
case F_colorBlend:
|
||||
return color_blend((uint32_t)par1, (uint32_t)par2, (uint16_t)par3);
|
||||
case F_colorWheel:
|
||||
return SEGMENT.color_wheel((uint8_t)par1);
|
||||
case F_colorFromPalette:
|
||||
{
|
||||
CRGB color;
|
||||
if (par2 == floatNull)
|
||||
color = ColorFromPalette(SEGPALETTE, (uint8_t)par1);
|
||||
else
|
||||
color = ColorFromPalette(SEGPALETTE, (uint8_t)par1, (uint8_t)par2); //brightness
|
||||
return RGBW32(color.r, color.g, color.b, 0);
|
||||
}
|
||||
case F_beatSin:
|
||||
return beatsin8((uint8_t)par1, (uint8_t)par2, (uint8_t)par3, (uint8_t)par4, (uint8_t)par5);
|
||||
case F_fadeToBlackBy:
|
||||
SEGMENT.fadeToBlackBy((uint8_t)par1);
|
||||
return floatNull;
|
||||
case F_iNoise:
|
||||
return inoise16((uint32_t)par1, (uint32_t)par2);
|
||||
case F_fadeOut:
|
||||
SEGMENT.fade_out((uint8_t)par1);
|
||||
return floatNull;
|
||||
|
||||
case F_segcolor:
|
||||
return SEGCOLOR((uint8_t)par1);
|
||||
|
||||
case F_shift: {
|
||||
uint32_t saveFirstPixel = SEGMENT.getPixelColor(0);
|
||||
for (uint16_t i=0; i<SEGLEN-1; i++)
|
||||
{
|
||||
SEGMENT.setPixelColor(i, SEGMENT.getPixelColor((uint16_t)(i + par1)%SEGLEN));
|
||||
}
|
||||
SEGMENT.setPixelColor(SEGLEN - 1, saveFirstPixel);
|
||||
return floatNull;
|
||||
}
|
||||
case F_circle2D: {
|
||||
uint16_t circleLength = min(strip.matrixWidth, strip.matrixHeight);
|
||||
uint16_t deltaWidth=0, deltaHeight=0;
|
||||
|
||||
if (circleLength < strip.matrixHeight) //portrait
|
||||
deltaHeight = (strip.matrixHeight - circleLength) / 2;
|
||||
if (circleLength < strip.matrixWidth) //portrait
|
||||
deltaWidth = (strip.matrixWidth - circleLength) / 2;
|
||||
|
||||
float halfLength = (circleLength-1)/2.0;
|
||||
|
||||
//calculate circle positions, round to 5 digits and then round again to cater for radians inprecision (e.g. 3.49->3.5->4)
|
||||
int x = round(round((sin(radians(par1)) * halfLength + halfLength) * 10)/10) + deltaWidth;
|
||||
int y = round(round((halfLength - cos(radians(par1)) * halfLength) * 10)/10) + deltaHeight;
|
||||
return SEGMENT.XY(x,y);
|
||||
}
|
||||
|
||||
case F_constrain:
|
||||
return constrain(par1, par2, par3);
|
||||
case F_map:
|
||||
return map(par1, par2, par3, par4, par5);
|
||||
case F_seed:
|
||||
random16_set_seed((uint16_t)par1);
|
||||
return floatNull;
|
||||
case F_random:
|
||||
return random16();
|
||||
|
||||
case F_millis:
|
||||
return millis();
|
||||
|
||||
default: {}
|
||||
}
|
||||
#else // not arduino
|
||||
switch (function)
|
||||
{
|
||||
case F_setPixelColor:
|
||||
PRINT_ARTI("%s(%f, %f, %f)\n", "setPixelColor", par1, par2, par3);
|
||||
return floatNull;
|
||||
case F_setPixels:
|
||||
PRINT_ARTI("%s\n", "setPixels(leds)");
|
||||
return floatNull;
|
||||
case F_hsv:
|
||||
PRINT_ARTI("%s(%f, %f, %f)\n", "hsv", par1, par2, par3);
|
||||
return par1 + par2 + par3;
|
||||
case F_rgbw:
|
||||
PRINT_ARTI("%s(%f, %f, %f, %f)\n", "rgbw", par1, par2, par3, par4);
|
||||
return par1 + par2 + par3 + par4;
|
||||
|
||||
case F_setRange:
|
||||
return par1 + par2 + par3;
|
||||
case F_fill:
|
||||
PRINT_ARTI("%s(%f)\n", "fill", par1);
|
||||
return floatNull;
|
||||
case F_colorBlend:
|
||||
return par1 + par2 + par3;
|
||||
case F_colorWheel:
|
||||
return par1;
|
||||
case F_colorFromPalette:
|
||||
return par1 + par2;
|
||||
case F_beatSin:
|
||||
return par1+par2+par3+par4+par5;
|
||||
case F_fadeToBlackBy:
|
||||
return par1;
|
||||
case F_iNoise:
|
||||
return par1 + par2;
|
||||
case F_fadeOut:
|
||||
return par1;
|
||||
|
||||
case F_segcolor:
|
||||
return par1;
|
||||
|
||||
case F_shift:
|
||||
PRINT_ARTI("%s(%f)\n", "shift", par1);
|
||||
return floatNull;
|
||||
case F_circle2D:
|
||||
PRINT_ARTI("%s(%f)\n", "circle2D", par1);
|
||||
return par1 / 2;
|
||||
|
||||
case F_constrain:
|
||||
return par1 + par2 + par3;
|
||||
case F_map:
|
||||
return par1 + par2 + par3 + par4 + par5;
|
||||
case F_seed:
|
||||
PRINT_ARTI("%s(%f)\n", "seed", par1);
|
||||
return floatNull;
|
||||
case F_random:
|
||||
return rand();
|
||||
|
||||
case F_millis:
|
||||
return 1000;
|
||||
}
|
||||
#endif
|
||||
|
||||
//same on Arduino or Windows
|
||||
switch (function)
|
||||
{
|
||||
case F_sin:
|
||||
return sin(par1);
|
||||
case F_cos:
|
||||
return cos(par1);
|
||||
case F_abs:
|
||||
return fabs(par1);
|
||||
case F_min:
|
||||
return fmin(par1, par2);
|
||||
case F_max:
|
||||
return fmax(par1, par2);
|
||||
case F_floor:
|
||||
return floorf(par1);
|
||||
|
||||
// Reference: https://github.com/atuline/PixelBlaze
|
||||
case F_time: // A sawtooth waveform between 0.0 and 1.0 that loops about every 65.536*interval seconds. e.g. use .015 for an approximately 1 second.
|
||||
{
|
||||
float myVal = millis();
|
||||
myVal = myVal / 65535 / par1; // PixelBlaze uses 1000/65535 = .015259.
|
||||
myVal = fmod(myVal, 1.0); // ewowi: with 0.015 as input, you get fmod(millis/1000,1.0), which has a period of 1 second, sounds right
|
||||
return myVal;
|
||||
}
|
||||
case F_triangle: // Converts a sawtooth waveform v between 0.0 and 1.0 to a triangle waveform between 0.0 to 1.0. v "wraps" between 0.0 and 1.0.
|
||||
return 1.0 - fabs(fmod(2 * par1, 2.0) - 1.0);
|
||||
case F_wave: // Converts a sawtooth waveform v between 0.0 and 1.0 to a sinusoidal waveform between 0.0 to 1.0. Same as (1+sin(v*PI2))/2 but faster. v "wraps" between 0.0 and 1.0.
|
||||
return (1 + sin(par1 * 2 * PI)) / 2;
|
||||
case F_square: // Converts a sawtooth waveform v to a square wave using the provided duty cycle where duty is a number between 0.0 and 1.0. v "wraps" between 0.0 and 1.0.
|
||||
{
|
||||
float sinValue = arti_external_function(F_wave, par1);
|
||||
return sinValue >= par2 ? 1 : 0;
|
||||
}
|
||||
case F_clamp:
|
||||
{
|
||||
const float t = par1 < par2 ? par2 : par1;
|
||||
return t > par3 ? par3 : t;
|
||||
}
|
||||
|
||||
case F_printf: {
|
||||
if (par3 == floatNull) {
|
||||
if (par2 == floatNull) {
|
||||
PRINT_ARTI("%f\n", par1);
|
||||
}
|
||||
else
|
||||
PRINT_ARTI("%f, %f\n", par1, par2);
|
||||
}
|
||||
else
|
||||
PRINT_ARTI("%f, %f, %f\n", par1, par2, par3);
|
||||
return floatNull;
|
||||
}
|
||||
}
|
||||
|
||||
ERROR_ARTI("Error: arti_external_function: %u not implemented\n", function);
|
||||
errorOccurred = true;
|
||||
return function;
|
||||
}
|
||||
|
||||
float ARTI::arti_get_external_variable(uint8_t variable, float par1, float par2, float par3)
|
||||
{
|
||||
// MEMORY_ARTI("get %d(%f, %f, %f)\n", variable, par1, par2, par3);
|
||||
#if ARTI_PLATFORM == ARTI_ARDUINO
|
||||
switch (variable)
|
||||
{
|
||||
case F_ledCount:
|
||||
return SEGLEN;
|
||||
case F_matrixWidth:
|
||||
return strip.matrixWidth;
|
||||
case F_matrixHeight:
|
||||
return strip.matrixHeight;
|
||||
case F_leds:
|
||||
if (par1 == floatNull) {
|
||||
ERROR_ARTI("arti_get_external_variable leds without indices not supported yet (get leds)\n");
|
||||
errorOccurred = true;
|
||||
return floatNull;
|
||||
}
|
||||
else if (par2 == floatNull)
|
||||
return SEGMENT.getPixelColor((uint16_t)par1);
|
||||
else
|
||||
return SEGMENT.getPixelColorXY((uint16_t)par1, (uint16_t)par2); //2D value!!
|
||||
|
||||
case F_counter:
|
||||
return SEGENV.call;
|
||||
case F_speedSlider:
|
||||
return SEGMENT.speed;
|
||||
case F_intensitySlider:
|
||||
return SEGMENT.intensity;
|
||||
case F_custom1Slider:
|
||||
return SEGMENT.custom1;
|
||||
case F_custom2Slider:
|
||||
return SEGMENT.custom2;
|
||||
case F_custom3Slider:
|
||||
return SEGMENT.custom3;
|
||||
case F_sampleAvg:
|
||||
{
|
||||
um_data_t *um_data;
|
||||
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
|
||||
// add support for no audio
|
||||
um_data = simulateSound(SEGMENT.soundSim);
|
||||
}
|
||||
float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg???
|
||||
|
||||
return volumeSmth;
|
||||
}
|
||||
case F_hour:
|
||||
return ((float)hour(localTime));
|
||||
case F_minute:
|
||||
return ((float)minute(localTime));
|
||||
case F_second:
|
||||
return ((float)second(localTime));
|
||||
}
|
||||
#else
|
||||
switch (variable)
|
||||
{
|
||||
case F_ledCount:
|
||||
return 3; // used in testing e.g. for i = 1 to ledCount
|
||||
case F_matrixWidth:
|
||||
return 2;
|
||||
case F_matrixHeight:
|
||||
return 4;
|
||||
case F_leds:
|
||||
if (par1 == floatNull) {
|
||||
ERROR_ARTI("arti_get_external_variable leds without indices not supported yet (get leds)\n");
|
||||
errorOccurred = true;
|
||||
return F_leds;
|
||||
}
|
||||
else if (par2 == floatNull)
|
||||
return par1;
|
||||
else
|
||||
return par1 * par2; //2D value!!
|
||||
|
||||
case F_counter:
|
||||
return frameCounter;
|
||||
case F_speedSlider:
|
||||
return F_speedSlider;
|
||||
case F_intensitySlider:
|
||||
return F_intensitySlider;
|
||||
case F_custom1Slider:
|
||||
return F_custom1Slider;
|
||||
case F_custom2Slider:
|
||||
return F_custom2Slider;
|
||||
case F_custom3Slider:
|
||||
return F_custom3Slider;
|
||||
case F_sampleAvg:
|
||||
return F_sampleAvg;
|
||||
|
||||
case F_hour:
|
||||
return F_hour;
|
||||
case F_minute:
|
||||
return F_minute;
|
||||
case F_second:
|
||||
return F_second;
|
||||
}
|
||||
#endif
|
||||
|
||||
ERROR_ARTI("Error: arti_get_external_variable: %u not implemented\n", variable);
|
||||
errorOccurred = true;
|
||||
return variable;
|
||||
}
|
||||
|
||||
void ARTI::arti_set_external_variable(float value, uint8_t variable, float par1, float par2, float par3)
|
||||
{
|
||||
#if ARTI_PLATFORM == ARTI_ARDUINO
|
||||
// MEMORY_ARTI("%s %s %u %u (%u)\n", spaces+50-depth, variable_name, par1, par2, esp_get_free_heap_size());
|
||||
switch (variable)
|
||||
{
|
||||
case F_leds:
|
||||
if (par1 == floatNull)
|
||||
{
|
||||
ERROR_ARTI("arti_set_external_variable leds without indices not supported yet (set leds to %f)\n", value);
|
||||
errorOccurred = true;
|
||||
}
|
||||
else if (par2 == floatNull)
|
||||
SEGMENT.setPixelColor((uint16_t)par1%SEGLEN, value);
|
||||
else
|
||||
SEGMENT.setPixelColorXY((uint16_t)par1%SEGMENT.virtualWidth(), (uint16_t)par2%SEGMENT.virtualHeight(), value); //2D value!!
|
||||
|
||||
return;
|
||||
}
|
||||
#else
|
||||
switch (variable)
|
||||
{
|
||||
case F_leds:
|
||||
if (par1 == floatNull)
|
||||
{
|
||||
ERROR_ARTI("arti_set_external_variable leds without indices not supported yet (set leds to %f)\n", value);
|
||||
errorOccurred = true;
|
||||
}
|
||||
else if (par2 == floatNull)
|
||||
RUNLOG_ARTI("arti_set_external_variable: leds(%f) := %f\n", par1, value);
|
||||
else
|
||||
RUNLOG_ARTI("arti_set_external_variable: leds(%f, %f) := %f\n", par1, par2, value);
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
ERROR_ARTI("Error: arti_set_external_variable: %u not implemented\n", variable);
|
||||
errorOccurred = true;
|
||||
}
|
||||
|
||||
bool ARTI::loop()
|
||||
{
|
||||
if (stages < 5) {close(); return true;}
|
||||
|
||||
if (parseTreeJsonDoc == nullptr || parseTreeJsonDoc->isNull())
|
||||
{
|
||||
ERROR_ARTI("Loop: No parsetree created\n");
|
||||
errorOccurred = true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t depth = 8;
|
||||
|
||||
bool foundRenderFunction = false;
|
||||
|
||||
const char * function_name = "renderFrame";
|
||||
Symbol* function_symbol = global_scope->lookup(function_name);
|
||||
|
||||
if (function_symbol != nullptr) { //calling undefined function: pre-defined functions e.g. print
|
||||
|
||||
foundRenderFunction = true;
|
||||
|
||||
ActivationRecord* ar = new ActivationRecord(function_name, "Function", function_symbol->scope_level + 1);
|
||||
|
||||
RUNLOG_ARTI("%s %s %s (%u)\n", spaces+50-depth, "Call", function_name, this->callStack->recordsCounter);
|
||||
|
||||
this->callStack->push(ar);
|
||||
|
||||
if (!interpret(function_symbol->block, nullptr, global_scope, depth + 1))
|
||||
return false;
|
||||
|
||||
this->callStack->pop();
|
||||
|
||||
delete ar; ar = nullptr;
|
||||
|
||||
} //function_symbol != nullptr
|
||||
|
||||
function_name = "renderLed";
|
||||
function_symbol = global_scope->lookup(function_name);
|
||||
|
||||
if (function_symbol != nullptr) { //calling undefined function: pre-defined functions e.g. print
|
||||
|
||||
foundRenderFunction = true;
|
||||
|
||||
ActivationRecord* ar = new ActivationRecord(function_name, "function", function_symbol->scope_level + 1);
|
||||
|
||||
for (int i = 0; i< arti_get_external_variable(F_ledCount); i++)
|
||||
{
|
||||
ar->set(function_symbol->function_scope->symbols[0]->scope_index, i%strip.matrixWidth); // set x
|
||||
if (function_symbol->function_scope->nrOfFormals == 2) // 2D
|
||||
ar->set(function_symbol->function_scope->symbols[1]->scope_index, i/strip.matrixWidth); // set y
|
||||
|
||||
this->callStack->push(ar);
|
||||
|
||||
if (!interpret(function_symbol->block, nullptr, global_scope, depth + 1))
|
||||
return false;
|
||||
|
||||
this->callStack->pop();
|
||||
}
|
||||
|
||||
delete ar; ar = nullptr;
|
||||
|
||||
}
|
||||
|
||||
if (!foundRenderFunction)
|
||||
{
|
||||
ERROR_ARTI("%s renderFrame or renderLed not found\n", spaces+50-depth);
|
||||
errorOccurred = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
frameCounter++;
|
||||
|
||||
if (frameCounter == 1)
|
||||
startMillis = millis();
|
||||
|
||||
if (millis() - startMillis > 3000) //startMillis != 0 && logToFile &&
|
||||
{
|
||||
// ERROR_ARTI("time %u\n", millis() - startMillis);
|
||||
closeLog();
|
||||
// startMillis = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // loop
|
||||
12
usermods/customeffects/customeffects.css
Normal file
12
usermods/customeffects/customeffects.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.ceTextarea {
|
||||
width: 90%;
|
||||
height: 300px;
|
||||
resize: none;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#kceEditor {
|
||||
max-width: 490px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
169
usermods/customeffects/customeffects.js
Normal file
169
usermods/customeffects/customeffects.js
Normal file
@@ -0,0 +1,169 @@
|
||||
|
||||
var isCEEditor = false;
|
||||
|
||||
function toggleCEEditor(name, segID) {
|
||||
if (isInfo) toggleInfo();
|
||||
if (isNodes) toggleNodes();
|
||||
isCEEditor = !isCEEditor;
|
||||
if (isCEEditor) populateCEEditor(name, segID);
|
||||
d.getElementById('ceEditor').style.transform = (isCEEditor) ? "translateY(0px)":"translateY(100%)";
|
||||
}
|
||||
|
||||
function fetchAndExecute(url, name, callback)
|
||||
{
|
||||
fetch
|
||||
(url+name, {
|
||||
method: 'get'
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
showToast("File " + name + " not found", true);
|
||||
return "";
|
||||
}
|
||||
return res.text();
|
||||
})
|
||||
.then(text => {
|
||||
callback(text);
|
||||
})
|
||||
.catch(function (error) {
|
||||
showToast("Error getting " + name, true);
|
||||
// showToast(error, true);
|
||||
// console.log(error);
|
||||
presetError(false);
|
||||
})
|
||||
.finally(() => {
|
||||
// if (callback) setTimeout(callback,99);
|
||||
});
|
||||
}
|
||||
|
||||
function loadLogFile(name, attempt) {
|
||||
var ceLogArea = d.getElementById("ceLogArea");
|
||||
fetchAndExecute((loc?`http://${locip}`:'.') + "/", name , function(logtext)
|
||||
{
|
||||
if (logtext == "") {
|
||||
if (attempt < 10) {
|
||||
ceLogArea.value = ("...........").substring(0, attempt + 1);
|
||||
setTimeout(() =>
|
||||
{
|
||||
loadLogFile(name, attempt + 1);
|
||||
}, 1000);
|
||||
}
|
||||
else
|
||||
ceLogArea.value = "log not found after 10 seconds";
|
||||
}
|
||||
else
|
||||
ceLogArea.value = logtext;
|
||||
});
|
||||
}
|
||||
|
||||
function uploadFileWithText(name, text)
|
||||
{
|
||||
var req = new XMLHttpRequest();
|
||||
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
|
||||
req.addEventListener('error', function(e){showToast(e.stack,true);});
|
||||
req.open("POST", "/upload");
|
||||
var formData = new FormData();
|
||||
|
||||
var blob = new Blob([text], {type : 'application/text'});
|
||||
var fileOfBlob = new File([blob], name);
|
||||
formData.append("upload", fileOfBlob);
|
||||
|
||||
req.send(formData);
|
||||
}
|
||||
|
||||
function saveCE(name, segID) {
|
||||
showToast("Saving " + name);
|
||||
|
||||
var ceProgramArea = d.getElementById("ceProgramArea");
|
||||
|
||||
uploadFileWithText("/" + name, ceProgramArea.value);
|
||||
|
||||
var obj = {"seg": {"id": segID, "reset": true}};
|
||||
requestJson(obj);
|
||||
|
||||
var ceLogArea = d.getElementById("ceLogArea");
|
||||
ceLogArea.value = ".";
|
||||
setTimeout(() =>
|
||||
{
|
||||
loadLogFile(name + ".log", 1);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function populateCEEditor(name, segID)
|
||||
{
|
||||
fetchAndExecute((loc?`http://${locip}`:'.') + "/", name + ".wled", function(text)
|
||||
{
|
||||
var cn=`Custom Effects Editor<br>
|
||||
<i>${name}.wled</i><br>
|
||||
<textarea class="ceTextarea" id="ceProgramArea">${text}</textarea><br>
|
||||
<button class="btn infobtn" onclick="toggleCEEditor()">Close</button>
|
||||
<button class="btn infobtn" onclick="saveCE('${name}.wled', ${segID})">Save and Run</button><br>
|
||||
<button class="btn infobtn" onclick="downloadCEFile('${name}.wled')">Download ${name}.wled</button>
|
||||
<button class="btn infobtn" onclick="loadCETemplate('${name}')">Load template</button><br>
|
||||
<button class="btn infobtn" onclick="downloadCEFile('wled.json')">Download wled.json</button>
|
||||
<button class="btn infobtn" onclick="downloadCEFile('presets.json')">Download presets.json</button><br>
|
||||
<a href="https://github.com/MoonModules/WLED-Effects/tree/master/CustomEffects/wled" target="_blank">Custom Effects Library</a><br>
|
||||
<a href="https://github.com/atuline/WLED/wiki/WLED-Custom-effects" target="_blank">Custom Effects Help</a><br>
|
||||
<br><i>Compile and Run Log</i><br>
|
||||
<textarea class="ceTextarea" id="ceLogArea"></textarea><br>
|
||||
<i>Run log > 3 seconds is send to Serial Ouput.</i>`;
|
||||
|
||||
d.getElementById('kceEditor').innerHTML = cn;
|
||||
|
||||
var ceLogArea = d.getElementById("ceLogArea");
|
||||
ceLogArea.value = ".";
|
||||
loadLogFile(name + ".wled.log", 1);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function downloadCEFile(name) {
|
||||
var url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/CustomEffects/wled/";
|
||||
|
||||
fetchAndExecute(url, name, function(text) {
|
||||
console.log(text);
|
||||
if (name == "wled.json" || name == "presets.json") {
|
||||
if (!confirm('Are you sure to download/overwrite ' + name + '?'))
|
||||
return;
|
||||
uploadFileWithText("/" + name, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ceProgramArea = d.getElementById("ceProgramArea");
|
||||
ceProgramArea.value = text;
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.onload = function() {
|
||||
if (name == "wled.json" || name == "presets.json") {
|
||||
if (!confirm('Are you sure to download ' + name + '?'))
|
||||
return;
|
||||
uploadFileWithText("/" + name, request.response);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ceProgramArea = d.getElementById("ceProgramArea");
|
||||
ceProgramArea.value = request.response;
|
||||
}
|
||||
}
|
||||
request.open("GET", url);
|
||||
request.send();
|
||||
}
|
||||
|
||||
function loadCETemplate(name) {
|
||||
var ceProgramArea = d.getElementById("ceProgramArea");
|
||||
ceProgramArea.value = `/*
|
||||
Custom Effects Template
|
||||
*/
|
||||
program ${name}
|
||||
{
|
||||
function renderFrame()
|
||||
{
|
||||
setPixelColor(counter, colorFromPalette(counter, counter))
|
||||
}
|
||||
}`;
|
||||
|
||||
}
|
||||
210
usermods/customeffects/usermod_v2_customeffects.h
Normal file
210
usermods/customeffects/usermod_v2_customeffects.h
Normal file
@@ -0,0 +1,210 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
#include "arti_wled.h"
|
||||
|
||||
//declare weathermod global variables (always precede with weather_ (psuedo class static variables)
|
||||
static uint32_t usermods_pushLoop = 0; //effect pushes loop to execute. might be interesting for audioreactive too
|
||||
ARTI * arti;
|
||||
|
||||
//effect function
|
||||
uint16_t mode_customEffect(void) {
|
||||
//tbd: move statics to SEGMENT.data
|
||||
static bool succesful;
|
||||
static bool notEnoughHeap;
|
||||
|
||||
static char previousEffect[charLength];
|
||||
if (SEGENV.call == 0)
|
||||
strcpy(previousEffect, ""); //force init
|
||||
|
||||
char currentEffect[charLength];
|
||||
strcpy(currentEffect, (SEGMENT.name != nullptr)?SEGMENT.name:"default"); //note: switching preset with segment name to preset without does not clear the SEGMENT.name variable, but not gonna solve here ;-)
|
||||
|
||||
if (strcmp(previousEffect, currentEffect) != 0)
|
||||
{
|
||||
strcpy(previousEffect, currentEffect);
|
||||
|
||||
// if (artiWrapper != nullptr && artiWrapper->arti != nullptr) {
|
||||
if (arti != nullptr)
|
||||
{
|
||||
arti->close();
|
||||
delete arti; arti = nullptr;
|
||||
}
|
||||
|
||||
// if (!SEGENV.allocateData(sizeof(ArtiWrapper))) return mode_static(); // We use this method for allocating memory for static variables.
|
||||
// artiWrapper = reinterpret_cast<ArtiWrapper*>(SEGENV.data);
|
||||
arti = new ARTI();
|
||||
|
||||
char programFileName[fileNameLength];
|
||||
strcpy(programFileName, "/");
|
||||
strcat(programFileName, currentEffect);
|
||||
strcat(programFileName, ".wled");
|
||||
|
||||
succesful = arti->setup("/wled.json", programFileName);
|
||||
|
||||
if (!succesful)
|
||||
ERROR_ARTI("Setup not succesful\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (succesful) // && SEGENV.call < 250 for each frame
|
||||
{
|
||||
if (esp_get_free_heap_size() <= 20000)
|
||||
{
|
||||
ERROR_ARTI("Not enough free heap (%u <= 30000)\n", esp_get_free_heap_size());
|
||||
notEnoughHeap = true;
|
||||
succesful = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// static int previousMillis;
|
||||
// static int previousCall;
|
||||
// if (millis() - previousMillis > 5000) { //tried SEGENV.aux0 but that looks to be overwritten!!! (dangling pointer???)
|
||||
// previousMillis = millis();
|
||||
// MEMORY_ARTI("Heap renderFrame %u %u fps\n", esp_get_free_heap_size(), (SEGENV.call - previousCall)/5);
|
||||
// previousCall = SEGENV.call;
|
||||
// }
|
||||
|
||||
succesful = arti->loop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
arti->closeLog();
|
||||
if (notEnoughHeap && esp_get_free_heap_size() > 20000) {
|
||||
ERROR_ARTI("Again enough free heap, restart effect (%u > 30000)\n", esp_get_free_heap_size());
|
||||
succesful = true;
|
||||
notEnoughHeap = false;
|
||||
strcpy(previousEffect, ""); // force new create
|
||||
}
|
||||
else {
|
||||
//mode_static
|
||||
SEGMENT.fill(SEGCOLOR(0));
|
||||
return 350;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
static const char _data_FX_MODE_CUSTOMEFFECT[] PROGMEM = " ⚙️ Custom Effect@Speed,Intensity,Custom 1, Custom 2, Custom 3;!;!";
|
||||
|
||||
class CustomEffectsUserMod : public Usermod {
|
||||
private:
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[]; //usermod name
|
||||
|
||||
unsigned long lastTime = 0; //will be used to download new forecast every hour
|
||||
char errorMessage[100] = "";
|
||||
|
||||
bool enabled = false;
|
||||
bool initDone = false;
|
||||
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
if (!initDone)
|
||||
strip.addEffect(255, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT);
|
||||
initDone = true;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void connected() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//execute only if effect pushes it or every hour
|
||||
if (usermods_pushLoop > millis() - 1000 && (lastTime == 0 || millis() - lastTime > 3600 * 1000)) {
|
||||
lastTime = millis();
|
||||
}
|
||||
usermods_pushLoop = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
||||
infoArr.add(errorMessage); //value
|
||||
// infoArr.add(""); //unit
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
//root["user0"] = userVar0;
|
||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (usermod.isNull()) {
|
||||
usermod = root.createNestedObject(FPSTR(_name));
|
||||
}
|
||||
usermod["on"] = enabled;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
// userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
|
||||
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
|
||||
}
|
||||
|
||||
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
|
||||
// * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings)
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
void appendConfigData()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
|
||||
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
|
||||
* Commonly used for custom clocks (Cronixie, 7 segment)
|
||||
*/
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
//strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_CUSTOMEFFECTS;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char CustomEffectsUserMod::_name[] PROGMEM = "CustomEffects";
|
||||
@@ -80,6 +80,7 @@
|
||||
#define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h"
|
||||
#define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h
|
||||
#define USERMOD_ID_AUDIOREACTIVE 31 //Usermod "audioreactive.h"
|
||||
#define USERMOD_ID_CUSTOMEFFECTS 32 //Usermod "usermod_v2_customeffects.h"
|
||||
|
||||
//Access point behavior
|
||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
setTimeout(()=>{h.appendChild(l)},100);
|
||||
</script>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="stylesheet" href="../../usermods/customeffects/customeffects.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -365,6 +366,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WLEDSR Custom Effects -->
|
||||
<div id="ceEditor" class="modal">
|
||||
<div id="kceEditor">Loading...</div><br>
|
||||
</div>
|
||||
|
||||
<div id="mliveview2D" class="modal">
|
||||
<div id="kliveview2D" style="width:100%; height:100%">Loading...</div><br>
|
||||
</div>
|
||||
@@ -380,5 +386,6 @@
|
||||
</div>
|
||||
<i id="roverstar" class="icons huge" onclick="setLor(0)"></i><br>
|
||||
<script src="index.js"></script>
|
||||
<script src="../../usermods/customeffects/customeffects.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -728,6 +728,7 @@ function populateSegments(s)
|
||||
<option value="3" ${inst.ssim==3?' selected':''}>U14_3</option>
|
||||
</select></div>
|
||||
</div>`;
|
||||
let cusEff = `<button class="btn" onclick="toggleCEEditor('${inst.n?inst.n:"default"}', ${i})">Custom Effect Editor</button><br>`;
|
||||
cn += `<div class="seg lstI ${i==s.mainseg ? 'selected' : ''} ${exp ? "expanded":""}" id="seg${i}">
|
||||
<label class="check schkl">
|
||||
<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>
|
||||
@@ -774,6 +775,7 @@ function populateSegments(s)
|
||||
${!isM?rvXck:''}
|
||||
${isM&&stoY-staY>1&&stoX-staX>1?map2D:''}
|
||||
${s.AudioReactive && s.AudioReactive.on ? "" : sndSim}
|
||||
${s.CustomEffects && s.CustomEffects.on ? cusEff : ""}
|
||||
<label class="check revchkl" id="seg${i}lbtm">
|
||||
${isM?'Transpose':'Mirror effect'}
|
||||
<input type="checkbox" id="seg${i}${isM?'tp':'mi'}" onchange="${(isM?'setTp(':'setMi(')+i})" ${isM?(inst.tp?"checked":""):(inst.mi?"checked":"")}>
|
||||
|
||||
2715
wled00/html_ui.h
2715
wled00/html_ui.h
File diff suppressed because it is too large
Load Diff
@@ -108,6 +108,12 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
|
||||
bool on = elem["on"] | seg.on;
|
||||
if (elem["on"].is<const char*>() && elem["on"].as<const char*>()[0] == 't') on = !on;
|
||||
seg.setOption(SEG_OPTION_ON, on); // use transition
|
||||
|
||||
//WLEDSR Custom Effects (but general usable)
|
||||
bool reset = elem["reset"];
|
||||
if (reset)
|
||||
seg.markForReset();
|
||||
|
||||
bool frz = elem["frz"] | seg.freeze;
|
||||
if (elem["frz"].is<const char*>() && elem["frz"].as<const char*>()[0] == 't') frz = !seg.freeze;
|
||||
seg.freeze = frz;
|
||||
|
||||
@@ -139,6 +139,10 @@
|
||||
#include "../usermods/audioreactive/audio_reactive.h"
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_CUSTOMEFFECTS
|
||||
#include "../usermods/customeffects/usermod_v2_customeffects.h"
|
||||
#endif
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
/*
|
||||
@@ -265,4 +269,9 @@ void registerUsermods()
|
||||
#endif
|
||||
usermods.add(new AudioReactive());
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_CUSTOMEFFECTS
|
||||
usermods.add(new CustomEffectsUserMod());
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user