Compare commits

...

12 Commits

Author SHA1 Message Date
Christian Schwinne
1c88cdd0a8 compiling 2024-01-21 11:11:18 +01:00
Christian Schwinne
f87be0293f Merge branch '0_15' into wasm 2024-01-18 15:21:18 +01:00
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 446 additions and 4 deletions

View File

@@ -183,6 +183,7 @@ lib_deps =
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.5
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7
wasm3/Wasm3 @ 0.5.0 # custom effect runtime
# ESP-NOW library (includes mandatory QuickDebug library)
; gmag11/QuickESPNow @ 0.6.2
https://github.com/blazoncek/QuickESPNow.git#optional-debug

View File

@@ -85,6 +85,33 @@ uint16_t mode_static(void) {
static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid";
/*
* Custom mode. Executes WebAssembly fx() function
*/
uint16_t mode_custom(void) {
if (SEGENV.call == 0) wasmfx.init();
wasmfx.run();
return 1;
}
static const char _data_FX_MODE_CUSTOM[] PROGMEM = "Custom"; // TODO special case dependant on values actually used in FX
//testing TEMP
/*uint16_t 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
@@ -7841,7 +7868,7 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS);
addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE);
addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL);
addEffect(FX_MODE_CUSTOM, &mode_custom, _data_FX_MODE_CUSTOM);
addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE);
addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE);
addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE);

View File

@@ -180,7 +180,7 @@
#define FX_MODE_TWO_DOTS 50
#define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity)
#define FX_MODE_RUNNING_DUAL 52
// #define FX_MODE_HALLOWEEN 53 // removed in 0.14!
#define FX_MODE_CUSTOM 53 // was Halloween prior to 0.14
#define FX_MODE_TRICOLOR_CHASE 54
#define FX_MODE_TRICOLOR_WIPE 55
#define FX_MODE_TRICOLOR_FADE 56

View File

@@ -194,6 +194,7 @@ void IRAM_ATTR Segment::deallocateData() {
*/
void Segment::resetIfRequired() {
if (!reset) return;
wasmfx.end(); // TODO this is certainly not the right place for this
//DEBUG_PRINTF("-- Segment reset: %p\n", this);
if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;

View File

@@ -412,6 +412,15 @@
#define MAX_LEDS_PER_BUS 2048 // may not be enough for fast LEDs (i.e. APA102)
#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)
#ifdef ESP8266
#define SETTINGS_STACK_BUF_SIZE 2048

View File

@@ -93,6 +93,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();
@@ -360,6 +361,32 @@ um_data_t* simulateSound(uint8_t simulationId);
void enumerateLedmaps();
uint8_t get_random_wheel_index(uint8_t pos);
//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);
};
// RAII guard class for the JSON Buffer lock
// Modeled after std::lock_guard
class JSONBufferGuard {

View File

@@ -410,3 +410,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 SEGMENT.speed;
}
uint32_t WASMFX::intensity() {
return SEGMENT.intensity;
}
uint32_t WASMFX::len() {
return strip._virtualSegmentLength;
}

View File

@@ -737,6 +737,12 @@ WLED_GLOBAL uint32_t ledMaps _INIT(0); // bitfield representation of available l
WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps
#endif
// 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

@@ -24,6 +24,24 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
} else if(type == WS_EVT_DATA){
// data packet
AwsFrameInfo * info = (AwsFrameInfo*)arg;
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;
}
return;
}
if(info->final && info->index == 0 && info->len == len){
// the whole message is in a single frame and we got all of its data (max. 1450 bytes)
if(info->opcode == WS_TEXT)
@@ -64,7 +82,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
// force broadcast in 500ms after updating client
//lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); // ESP8266 does not like this
}
}
}
} else {
//message is comprised of multiple frames or the frame is split into multiple packets
//if(info->index == 0){
@@ -92,7 +110,6 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
} else if(type == WS_EVT_PONG){
//pong message was received (in response to a ping request maybe)
DEBUG_PRINTLN(F("WS pong."));
}
}