Merge branch 'custom-effects' into mdev

This commit is contained in:
Ewowi
2022-08-30 23:46:14 +02:00
12 changed files with 5083 additions and 1325 deletions

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -0,0 +1,12 @@
.ceTextarea {
width: 90%;
height: 300px;
resize: none;
white-space: pre;
}
#kceEditor {
max-width: 490px;
display: inline-block;
}

View 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))
}
}`;
}

View 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";

View File

@@ -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

View File

@@ -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)">&#xe410;</i><br>
<script src="index.js"></script>
<script src="../../usermods/customeffects/customeffects.js"></script>
</body>
</html>

View File

@@ -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":"")}>

File diff suppressed because it is too large Load Diff

View File

@@ -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;

View File

@@ -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
}