Skip to main content
Image
Bridging Elk and LVGL for JavaScript-Driven Interfaces on ESP32

Bridging Elk and LVGL for JavaScript-Driven Interfaces on ESP32

Webscreen’s Open Source Journey

In the embedded world, finding a balance between performance, memory constraints, and developer-friendliness can be tough. Over at HW Lab, we wanted to bring together the Elk JavaScript engine and the LVGL graphics library on our ESP32-based Webscreen device—allowing users to run rich user interfaces loaded from an SD card in pure JavaScript!

The Vision

Imagine booting your device, scanning for an SD card, and if you find script.js, you instantly load dynamic JavaScript that creates a UI—sliders, images, animations—without having to recompile C++ code. Otherwise, a fallback application (our default Notifications App) takes over. Our system seamlessly decides:

Fallback Mode: If there’s no script.js, we show the default Notifications App with an animated screensaver.

Dynamic JS Mode: If there is a script.js, we launch Elk, parse the script, and rely on our bridging layer to call LVGL functions from JavaScript.

This approach drastically cuts iteration time for UI changes. Users can adjust, for instance, UI text or positioning simply by editing a script on the SD card.

Our Structure

Fallback App (fallback.cpp):

■ Minimal LVGL setup to display a scrolling label and a GIF.
■ Hides or shows the label/GIF based on incoming data via Serial (or WebSocket).
■ Offers a safe default so the device is always functional even without an SD card or script.

Dynamic JS (lvgl_elk.h & elk_task in lvgl_elk.cpp):

■ Initializes Elk (a tiny JavaScript interpreter),
■ Loads script.js from SD,
■ Exposes C++ LVGL functions (like draw_rect, create_image, rotate_obj. See the current full list of supported functions here) as JavaScript calls.
■ Example snippet below shows how script.js can instruct an image to rotate and animate across the screen.

Core Shared Functionalities:

■ BLE and Wi-Fi: For connectivity, scanning, and data transfers.
■ YAML Processing: We parse configuration data (e.g., Wi-Fi credentials in webscreen.yml) to easily update settings.
■ Open Source: The entire project is on GitHub, encouraging contributions and expansions.

Example script.js

Here’s a truncated example living on the SD card:

"use strict";
print("Starting JavaScript execution...");
if (wifi_connect("SSID", "Password")) {
  print("Wi-Fi connected successfully.");
  print("IP Address: " + wifi_get_ip());
} else {
  print("Failed to connect to Wi-Fi.");
}
// Draw shapes and handle images
draw_label("Hello from JavaScript!", 150, 50);
draw_rect(100, 100, 120, 100);
print("Creating an image of messi...");
let messiHandle = create_image("/messi.png", 0, 0);
print("Rotating the image...");
rotate_obj(messiHandle, 900);
print("Moving the image to (50,50)...");
move_obj(messiHandle, 50, 50);
// Animate from (50,50) to (200,200) over 3 seconds
print("Animating...");
animate_obj(messiHandle, 50, 50, 200, 200, 3000);
// List SD files
print("Listing files on SD card...");
let files = sd_list_dir("/");
if (files) {
  print(files);
} else {
  print("Directory is empty.");
}
print("Script execution completed.");

With just these few lines of JavaScript, the Elk interpreter coordinates the underlying LVGL library calls, performs shape drawing, image transformations, animations, and SD I/O. It’s an approachable environment for people who’d rather script than compile.

Challenges We Faced

■ Memory Constraints: Elk is extremely lightweight, but bridging it with the equally small but powerful LVGL demanded careful memory usage.
■ File Handling: Reading and writing from the SD card while also running a real-time UI required concurrency and good error handling.
■ Fallback Logic: We needed a robust check for whether the SD card existed, whether script.js was present, and how to revert to the default interface.

Despite these challenges, the result is an extremely flexible, scriptable environment on an embedded device.

Why BLE, Wi-Fi, and YAML?

■ BLE: For local device configuration or quick data exchanges.
■ Wi-Fi: Remote updates, connectivity to the cloud, or controlling the device over WebSocket.
■ YAML: Storing user or device config (e.g. Wi-Fi credentials, user info) in a human-readable format that’s easy to parse on-device.

Get Involved

Webscreen is an open source project, and we’d love to get feedback, bug reports, or new feature PRs from the community. You can find the full repository at:
HW-Lab-Hardware-Design-Agency/WebScreen-Software/tree/main/websocket

Check it out, clone, and experiment with your own JavaScript interfaces. We welcome your involvement to help refine the bridging architecture, expand BLE/Wi-Fi features, or optimize our YAML processing flow. Let’s make it an all-in-one ecosystem for truly dynamic embedded UI design!
 

Image
Crowd Supply Logo


If you want to get the hardware, support our campaign on Crowd Supply. Let’s continue growing this project into an all-in-one, scriptable UI platform for embedded systems.

Happy hacking—see you on GitHub and Crowd Supply!