Compare commits

...

10 Commits
main ... wasm

Author SHA1 Message Date
cschwinne
11b0b75e26 wasmRun in separate task 2021-12-22 17:28:13 +01:00
cschwinne
214d4364f1 Benchmark mode, allow arbitrary frame rate 2021-12-16 23:39:49 +01:00
cschwinne
7c3b96c0e1 WS support working 2021-10-24 11:30:53 +02:00
cschwinne
82faa46531 WS support preliminary 2021-08-16 12:30:45 +02:00
cschwinne
63acddf1a5 Loading from wasm file 2021-08-11 14:10:38 +02:00
cschwinne
a84e242a37 Unloading at effect end 2021-07-23 00:30:25 +02:00
cschwinne
148de04e6f WASM Interpreter functional 2021-07-12 22:08:22 +02:00
cschwinne
4f4b6ca253 Hello world! 2021-07-12 16:38:36 +02:00
cschwinne
776971db2c Something works in WASM 2021-07-12 16:33:43 +02:00
cschwinne
301cacc91d First WASM commit 2021-07-12 14:37:54 +02:00
11 changed files with 468 additions and 18 deletions

View File

@ -158,6 +158,7 @@ lib_compat_mode = strict
lib_deps =
fastled/FastLED @ 3.4.0
IRremoteESP8266 @ 2.7.18
wasm3/Wasm3 @ 0.5.0
https://github.com/lorol/LITTLEFS.git
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.2
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line

View File

@ -38,6 +38,32 @@ uint16_t WS2812FX::mode_static(void) {
}
/*
* Custom mode. Executes WebAssembly fx() function
*/
uint16_t WS2812FX::mode_custom(void) {
if (SEGENV.call == 0) wasmfx.init();
wasmfx.run();
return 1;
}
//testing TEMP
uint16_t WS2812FX::mode_benchmark(void) {
uint32_t i=(now/4);
uint32_t c=0;
while(c<SEGLEN){
uint32_t v=i%256;
setPixelColor(c,v,v/2,0);
c++;
i=(i+SEGMENT.speed/16+1);
}
return 1;
}
/*
* Blink/strobe function
* Alternate between color1 and color2

View File

@ -69,7 +69,7 @@
#endif
#define LED_SKIP_AMOUNT 1
#define MIN_SHOW_DELAY 15
#define MIN_SHOW_DELAY 1
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT _segments[_segment_index]
@ -113,7 +113,7 @@
#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE )
#define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED )
#define MODE_COUNT 118
#define MODE_COUNT 120
#define FX_MODE_STATIC 0
#define FX_MODE_BLINK 1
@ -233,6 +233,8 @@
#define FX_MODE_BLENDS 115
#define FX_MODE_TV_SIMULATOR 116
#define FX_MODE_DYNAMIC_SMOOTH 117
#define FX_MODE_CUSTOM 118
#define FX_MODE_BENCHMARK 119
class WS2812FX {
@ -378,12 +380,16 @@ class WS2812FX {
*/
void resetIfRequired() {
if (_requiresReset) {
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
deallocateData();
_requiresReset = false;
}
}
bool isResetRequired() {
return _requiresReset;
}
/**
* Flags that before the next effect is calculated,
* the internal segment state should be reset.
@ -594,6 +600,8 @@ class WS2812FX {
_mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends;
_mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator;
_mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth;
_mode[FX_MODE_CUSTOM] = &WS2812FX::mode_custom;
_mode[FX_MODE_BENCHMARK] = &WS2812FX::mode_benchmark;
_brightness = DEFAULT_BRIGHTNESS;
currentPalette = CRGBPalette16(CRGB::Black);
@ -816,7 +824,9 @@ class WS2812FX {
mode_candy_cane(void),
mode_blends(void),
mode_tv_simulator(void),
mode_dynamic_smooth(void);
mode_dynamic_smooth(void),
mode_custom(void),
mode_benchmark(void);
private:
uint32_t crgb_to_col(CRGB fastled);
@ -892,6 +902,7 @@ class WS2812FX {
ColorTransition transitions[MAX_NUM_TRANSITIONS]; //12 bytes per element
friend class ColorTransition;
friend class WASMFX;
uint16_t
realPixelIndex(uint16_t i),
@ -903,15 +914,15 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow",
"Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd",
"Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Aurora","Stream",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Chase 2","Aurora","Stream",
"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Police All",
"Two Dots","Two Areas","Running Dual","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Two Dots","Two Areas","Running Dual","Halloween","Chase 3","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise",
"Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple",
"Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst",
"Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow",
"Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise",
"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth"
"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth","Custom","Benchmark"
])=====";

View File

@ -144,6 +144,7 @@ void WS2812FX::service() {
// reset the segment runtime data if needed, called before isActive to ensure deleted
// segment's buffers are cleared
if (SEGENV.isResetRequired()) wasmfx.end();
SEGENV.resetIfRequired();
if (!SEGMENT.isActive()) continue;

View File

@ -249,6 +249,15 @@
#define MAX_LEDS_PER_BUS 4096
#endif
#ifndef MAX_WASM_BIN_SIZE
#define MAX_WASM_BIN_SIZE 8192
#endif
#define WASM_STATE_UNLOADED 0
#define WASM_STATE_READY 1 //wasm runtime allocated
#define WASM_STATE_STALE 2 //inited, but wasm_buffer has updated. Runtime re-init required.
#define WASM_STATE_ERROR 3 //runtime wasm error
// string temp buffer (now stored in stack locally)
#define OMAX 2048

View File

@ -82,6 +82,7 @@ bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* conte
bool writeObjectToFile(const char* file, const char* key, JsonDocument* content);
bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest);
bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest);
bool readToBuffer(const char* file, uint8_t** buf, uint32_t* len);
void updateFSInfo();
void closeFile();
@ -249,6 +250,32 @@ void userSetup();
void userConnected();
void userLoop();
//wasm.cpp
void wasmInit();
void wasmRunTask();
void wasmEnd();
class WASMFX {
public:
void init() {
wasmInit();
}
void run() {
wasmRunTask();
}
void end() {
wasmEnd();
}
uint32_t now();
uint32_t speed();
uint32_t intensity();
uint32_t len();
void set(uint32_t i, uint32_t r, uint32_t g, uint32_t b);
};
//wled_eeprom.cpp
void applyMacro(byte index);
void deEEP();

View File

@ -409,3 +409,21 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
}
return false;
}
//provide maximum buffer size in len variable
//if this returns true, the buffer array must be deleted by the caller
bool readToBuffer(const char* file, uint8_t** buf, uint32_t* len) {
Serial.println("opening file");
File f = WLED_FS.open(file,"r");
if (!f) return false;
uint32_t sz = f.size();
Serial.println(sz);
if (!sz || sz > *len) {f.close(); return false;}
*buf = new uint8_t[sz];
if (!*buf) {f.close(); return false;}
*len = sz;
f.read(*buf, sz);
f.close();
Serial.println("done");
return true;
}

319
wled00/wasm.cpp Normal file
View File

@ -0,0 +1,319 @@
#include <wasm3.h>
#include <m3_env.h>
#include "wled.h"
#define WASM_STACK_SLOTS 1024
#define NATIVE_STACK_SIZE (32*1024)
// For (most) devices that cannot allocate a 64KiB wasm page
#define WASM_MEMORY_LIMIT 4096
/*unsigned char app_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0f, 0x03, 0x60,
0x00, 0x01, 0x7f, 0x60, 0x04, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x60, 0x00,
0x00, 0x02, 0x2b, 0x04, 0x03, 0x6c, 0x65, 0x64, 0x03, 0x6e, 0x6f, 0x77,
0x00, 0x00, 0x03, 0x6c, 0x65, 0x64, 0x03, 0x6c, 0x65, 0x6e, 0x00, 0x00,
0x03, 0x6c, 0x65, 0x64, 0x03, 0x73, 0x65, 0x74, 0x00, 0x01, 0x03, 0x6c,
0x65, 0x64, 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, 0x00, 0x00, 0x03, 0x02,
0x01, 0x02, 0x05, 0x03, 0x01, 0x00, 0x00, 0x07, 0x0f, 0x02, 0x02, 0x66,
0x78, 0x00, 0x04, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00,
0x0a, 0x45, 0x01, 0x43, 0x01, 0x04, 0x7f, 0x10, 0x00, 0x41, 0x02, 0x76,
0x21, 0x00, 0x10, 0x01, 0x21, 0x02, 0x03, 0x40, 0x20, 0x01, 0x20, 0x02,
0x49, 0x04, 0x40, 0x20, 0x01, 0x20, 0x00, 0x41, 0xff, 0x01, 0x71, 0x22,
0x03, 0x20, 0x03, 0x41, 0x01, 0x76, 0x41, 0x00, 0x10, 0x02, 0x20, 0x00,
0x10, 0x03, 0x41, 0x04, 0x76, 0x41, 0x01, 0x6a, 0x6a, 0x21, 0x00, 0x20,
0x01, 0x41, 0x01, 0x6a, 0x21, 0x01, 0x0c, 0x01, 0x0b, 0x0b, 0x0b, 0x00,
0x20, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x61, 0x70, 0x70,
0x69, 0x6e, 0x67, 0x55, 0x52, 0x4c, 0x0e, 0x2e, 0x2f, 0x61, 0x70, 0x70,
0x2e, 0x77, 0x61, 0x73, 0x6d, 0x2e, 0x6d, 0x61, 0x70
};*/
//unsigned int app_wasm_len = 201;
/*
* API bindings
*
* Note: each RawFunction should complete with one of these calls:
* m3ApiReturn(val) - Returns a value
* m3ApiSuccess() - Returns void (and no traps)
* m3ApiTrap(trap) - Returns a trap
*/
m3ApiRawFunction(m3_arduino_millis)
{
m3ApiReturnType (uint32_t)
m3ApiReturn(millis());
}
m3ApiRawFunction(m3_arduino_delay)
{
m3ApiGetArg (uint32_t, ms)
// You can also trace API calls
Serial.print("api: delay "); Serial.println(ms);
//delay(ms);
m3ApiSuccess();
}
// This maps pin modes from arduino_wasm_api.h
// to actual platform-specific values
uint8_t mapPinMode(uint8_t mode)
{
switch(mode) {
case 0: return INPUT;
case 1: return OUTPUT;
case 2: return INPUT_PULLUP;
}
return INPUT;
}
m3ApiRawFunction(m3_arduino_pinMode)
{
m3ApiGetArg (uint32_t, pin)
m3ApiGetArg (uint32_t, mode)
pinMode(pin, (uint8_t)mapPinMode(mode));
m3ApiSuccess();
}
m3ApiRawFunction(m3_arduino_digitalWrite)
{
m3ApiGetArg (uint32_t, pin)
m3ApiGetArg (uint32_t, value)
digitalWrite(pin, value);
m3ApiSuccess();
}
m3ApiRawFunction(m3_arduino_getPinLED)
{
m3ApiReturnType (uint32_t)
m3ApiReturn(2);
}
m3ApiRawFunction(m3_arduino_print)
{
m3ApiGetArgMem (const uint8_t *, buf)
m3ApiGetArg (uint32_t, len)
Serial.write(buf, len);
m3ApiSuccess();
}
m3ApiRawFunction(m3_led_now) {
m3ApiReturnType(uint32_t)
m3ApiReturn(wasmfx.now());
}
m3ApiRawFunction(m3_led_speed) {
m3ApiReturnType(uint32_t)
m3ApiReturn(wasmfx.speed());//strip._segments[strip._segment_index].speed);
}
m3ApiRawFunction(m3_led_intensity) {
m3ApiReturnType(uint32_t)
m3ApiReturn(wasmfx.intensity());//strip._segments[strip._segment_index].intensity);
}
m3ApiRawFunction(m3_led_len) {
m3ApiReturnType(uint32_t)
m3ApiReturn(wasmfx.len());//strip._virtualSegmentLength);
}
m3ApiRawFunction(m3_led_fill) {
m3ApiGetArg (uint32_t, color)
strip.fill(color);
m3ApiSuccess();
}
m3ApiRawFunction(m3_led_set) {
m3ApiGetArg (uint32_t, index)
m3ApiGetArg (uint32_t, r)
m3ApiGetArg (uint32_t, g)
m3ApiGetArg (uint32_t, b)
strip.setPixelColor(index, r,g,b);
m3ApiSuccess();
}
m3ApiRawFunction(m3_led_rgb) {
m3ApiGetArg (uint32_t, r)
m3ApiGetArg (uint32_t, g)
m3ApiGetArg (uint32_t, b)
m3ApiReturnType(uint32_t)
uint32_t c = (r << 16) + (g << 8) + b;
m3ApiReturn(c);
}
M3Result LinkArduino (IM3Runtime runtime)
{
IM3Module module = runtime->modules;
const char* arduino = "arduino";
const char* led = "led";
m3_LinkRawFunction (module, arduino, "millis", "i()", &m3_arduino_millis);
m3_LinkRawFunction (module, arduino, "delay", "v(i)", &m3_arduino_delay); //temp
m3_LinkRawFunction (module, arduino, "pinMode", "v(ii)", &m3_arduino_pinMode); //temp
m3_LinkRawFunction (module, arduino, "digitalWrite", "v(ii)", &m3_arduino_digitalWrite); //temp
// Test functions
m3_LinkRawFunction (module, arduino, "getPinLED", "i()", &m3_arduino_getPinLED); //temp
m3_LinkRawFunction (module, arduino, "print", "v(*i)", &m3_arduino_print);
//WLED functions
m3_LinkRawFunction (module, led, "now", "i()", &m3_led_now);
m3_LinkRawFunction (module, led, "speed", "i()", &m3_led_speed);
m3_LinkRawFunction (module, led, "intensity", "i()", &m3_led_intensity);
m3_LinkRawFunction (module, led, "len", "i()", &m3_led_len);
m3_LinkRawFunction (module, led, "fill", "v(i)", &m3_led_fill);
m3_LinkRawFunction (module, led, "set", "v(iiii)",&m3_led_set);
m3_LinkRawFunction (module, led, "rgb", "i(iii)", &m3_led_rgb);
return m3Err_none;
}
/*
* Engine start, liftoff!
*/
#define FATAL(func, msg) { Serial.print("Fatal: " func " "); Serial.println(msg); return; }
M3Result result;
IM3Environment env;
IM3Runtime runtime;
IM3Module module;
IM3Function fu;
//uint32_t app_wasm_len = MAX_WASM_BIN_SIZE;
//uint8_t* app_wasm = nullptr;
void wasm_task(void*)
{
result = m3Err_none;
env = m3_NewEnvironment ();
if (!env) FATAL("NewEnv", "failed");
runtime = m3_NewRuntime (env, WASM_STACK_SLOTS, NULL);
if (!runtime) FATAL("NewRt", "failed");
#ifdef WASM_MEMORY_LIMIT
runtime->memoryLimit = WASM_MEMORY_LIMIT;
#endif
if (!wasm_buffer) { //from filesystem (fx.wasm)
wasm_buffer_len = MAX_WASM_BIN_SIZE;
if (!readToBuffer("/fx.wasm", &wasm_buffer, &wasm_buffer_len)) {
result = "fload";
return;
}
}
//Serial.println(app_wasm_len);
//Serial.println((uint32_t)app_wasm);
if (wasm_buffer == nullptr) {
result = "npr";
return;
}
result = m3_ParseModule (env, &module, wasm_buffer, wasm_buffer_len);
if (result) FATAL("Prs", result);
delete[] wasm_buffer; wasm_buffer = nullptr; wasm_buffer_len = 0;
result = m3_LoadModule (runtime, module);
if (result) FATAL("Load", result);
result = LinkArduino (runtime);
if (result) FATAL("Lnk", result);
result = m3_FindFunction (&fu, runtime, "fx");
if (result) FATAL("Func", result);
Serial.println(F("WASM init success!"));
wasm_state = WASM_STATE_READY;
}
void wasmInit()
{
if (runtime || env) wasmEnd();
wasm_task(NULL);
}
volatile bool wasmRunning = false;
void wasmRun(void * parameter) {
result = m3_CallV(fu);
if (result) {
M3ErrorInfo info;
m3_GetErrorInfo (runtime, &info);
Serial.print("Err: ");
Serial.print(result);
Serial.print(" (");
Serial.print(info.message);
Serial.println(")");
if (info.file && strlen(info.file) && info.line) {
Serial.print("At ");
Serial.print(info.file);
Serial.print(":");
Serial.println(info.line);
}
wasm_state = WASM_STATE_ERROR;
}
wasmRunning = false;
#ifdef ESP32
vTaskDelete(NULL);
#endif
}
void wasmRunTask() {
//re-init after wasm_buffer refresh
if (wasm_state == WASM_STATE_STALE) wasmInit();
if (wasm_state != WASM_STATE_READY) return;
if (result) {
Serial.print("WASM run error");
Serial.println(result);
wasm_state = WASM_STATE_ERROR;
return;
}
#ifdef ESP32
// On ESP32, we can launch in a separate thread
wasmRunning = true;
unsigned long startTime = millis();
TaskHandle_t xHandle = NULL;
xTaskCreate(&wasmRun, "wasm3", 8096, NULL, 1, &xHandle);
while (wasmRunning) {
if (millis() - startTime > 250) { //bail
if (xHandle != NULL) vTaskDelete(xHandle);
wasmRunning = false;
}
}
#else
wasmRun(nullptr); //no hang protection (e.g. "while (true);")
#endif
}
void wasmEnd() {
//if (module) m3_FreeModule(module); module = nullptr;
Serial.println("F");
if (runtime) m3_FreeRuntime(runtime); runtime = nullptr;
if (env) m3_FreeEnvironment(env); env = nullptr;
Serial.println("F later");
wasm_state = WASM_STATE_UNLOADED;
}

17
wled00/wasmfx.cpp Normal file
View File

@ -0,0 +1,17 @@
#include "wled.h"
uint32_t WASMFX::now() {
return strip.now;
}
uint32_t WASMFX::speed() {
return strip._segments[strip._segment_index].speed;
}
uint32_t WASMFX::intensity() {
return strip._segments[strip._segment_index].intensity;
}
uint32_t WASMFX::len() {
return strip._virtualSegmentLength;
}

View File

@ -588,6 +588,12 @@ WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after
WLED_GLOBAL bool doInitBusses _INIT(false);
// WASM
WLED_GLOBAL WASMFX wasmfx _INIT(WASMFX());
WLED_GLOBAL uint8_t* wasm_buffer _INIT(nullptr);
WLED_GLOBAL uint32_t wasm_buffer_len _INIT(0);
WLED_GLOBAL byte wasm_state _INIT(WASM_STATE_UNLOADED);
// Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());

View File

@ -19,12 +19,12 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
} else if(type == WS_EVT_DISCONNECT){
//client disconnected
if (client->id() == wsLiveClientId) wsLiveClientId = 0;
} else if(type == WS_EVT_DATA){
} else if(type == WS_EVT_DATA) {
//data packet
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
if(info->opcode == WS_TEXT) {
//the whole message is in a single frame and we got all of its data (max. 1450byte)
if(info->opcode == WS_TEXT)
if(info->final && info->index == 0 && info->len == len)
{
bool verboseResponse = false;
{ //scope JsonDocument so it releases its buffer
@ -51,8 +51,29 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
//update if it takes longer than 300ms until next "broadcast"
if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client);
} else {
if((info->index + len) == info->len){
if(info->final){
client->text(F("{\"error\":9}")); //we do not handle split packets right now
}
}
}
} else if (info->opcode == WS_BINARY){ //wasm custom FX binary
if (len > MAX_WASM_BIN_SIZE) return;
if(info->index == 0){
delete[] wasm_buffer;
wasm_buffer = new uint8_t[len];
wasm_buffer_len = len;
}
if (info->index + info->len <= len) {
memcpy(wasm_buffer + info->index, data, info->len);
}
if (info->final) {
//reload WASM on the next frame
wasm_state = WASM_STATE_STALE;
}
} else {
//message is comprised of multiple frames or the frame is split into multiple packets
//if(info->index == 0){
//if (!wsFrameBuffer && len < 4096) wsFrameBuffer = new uint8_t[4096];
@ -63,13 +84,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
//}
if((info->index + len) == info->len){
if(info->final){
if(info->message_opcode == WS_TEXT) {
client->text(F("{\"error\":9}")); //we do not handle split packets right now
}
}
}
}
} else if(type == WS_EVT_ERROR){
//error was received from the other end