WebScreen exposes a comprehensive set of functions to JavaScript applications running on the ESP32 with LVGL and the Elk engine. The configuration file (webscreen.json) on the SD card determines which JavaScript file to execute.
{
"settings": {
"wifi": {
"ssid": "MyNetwork",
"pass": "MyPassword"
},
"mqtt": {
"enabled": false
}
},
"screen": {
"background": "#2980b9",
"foreground": "#00fff1"
},
"display": {
"brightness": 200
},
"timezone": "EST5EDT,M3.2.0,M11.1.0",
"script": "my_app.js"
}The following functions are available in your JavaScript applications:
-
print(message) Print a message to the serial console for debugging.
-
mem_stats() Print memory statistics (ESP32 heap and LVGL memory usage) to the serial console. Returns the free heap size in bytes. Useful for debugging memory issues.
-
mem_info() Returns a JSON string describing all memory pools: internal heap (
heap_free,heap_min,heap_largest), PSRAM (psram_free), and the JavaScript arena (js_used,js_total). All values are in bytes.let info = mem_info(); print(info); // {"heap_free":123456,"heap_min":98760,...,"js_used":40960,"js_total":262144} let used = toNumber(parse_json_value(info, "js_used"));
-
gc() Request a JavaScript garbage collection. The collection runs at the next safe point (between timer callbacks), not immediately inside the call. Returns the number of free bytes in the JS arena before the collection.
let free_before = gc(); print("Arena free: " + numberToString(free_before));
-
delay(milliseconds) Pause execution for the specified number of milliseconds.
-
create_timer(function_name, period_ms) Create an LVGL timer that calls the named global function every
period_msmilliseconds.let update = function() { print("tick"); }; create_timer("update", 1000);
-
timer_delete(function_name) Stop and delete the timer created with
create_timer(function_name, period_ms). Returnstrueif a matching timer was found and deleted,falseotherwise.create_timer("update", 1000); // ... later ... timer_delete("update");
-
set_brightness(value) Set the display brightness. Value ranges from 0 (off) to 255 (maximum). Returns the applied brightness value, or -1 on error.
set_brightness(200); // Set brightness to ~78% set_brightness(0); // Turn off display backlight set_brightness(255); // Maximum brightness
-
get_brightness() Returns the current display brightness value (0-255).
let current = get_brightness(); print("Current brightness: " + numberToString(current));
The power button's short press (< 3s) can be delivered to JavaScript; the long press (>= 3s) is always handled by the firmware as power-off and never reaches JS. While an app is using the button, the default display on/off toggle is suppressed so the press is yours to handle.
Button state is reset whenever the app is restarted or switched: the handler is cleared, the default toggle is restored, and any queued presses are drained.
-
on_button("fn_name") Register the named global function as the button handler. On each short press it is called as
fn_name(1)at the next safe point. While a handler is registered the default display toggle is suppressed. Callon_button("")to release the button and restore the default toggle. Returnstrueon success,falseif the name is too long.let onPress = function(evt) { print("button pressed"); }; on_button("onPress"); // ... later ... on_button(""); // release, default toggle returns
-
get_button_event() Poll-style alternative to
on_button(). Returns1if a short press was pending (consuming it), or0otherwise. Pair it withbutton_set_toggle(false)so polled presses are not also consumed by the default toggle.button_set_toggle(false); let update = function() { if (get_button_event()) { print("button pressed"); } }; create_timer("update", 100);
-
button_set_toggle(enabled) Keep (
true) or suppress (false) the default display on/off toggle. Used by poll-style apps that readget_button_event()without registering a handler.
-
str_index_of(haystack, needle) Returns the index of
needleinhaystack(or -1 if not found). -
str_substring(str, start, length) Returns a substring of
strstarting atstartwith the givenlength. -
toNumber(string) Convert a string to a number.
-
numberToString(number) Convert a number to a string.
-
str_split(str, sep, idx) Split
strbysepand return theidx-th field (0-based), ornullpast the last field. The separator may be multiple characters. Empty fields are returned as"".str_split("a,b,c", ",", 1); // "b" str_split("a,,c", ",", 1); // "" (empty field) str_split("a,b", ",", 5); // null (past the end)
-
str_split_count(str, sep) Returns the number of fields produced by splitting
stronsep. An empty string returns0; a string with no separator returns1.str_split_count("a,b,c", ","); // 3
-
format_number(value, decimals) Format
valueas a fixed-point string with the given number of decimals (clamped to 0-9).format_number(3.14159, 2); // "3.14" format_number(42, 0); // "42"
-
pad_number(value, width) Format
valueas a zero-padded integer string of at leastwidthdigits (clamped to 1-16).pad_number(7, 2); // "07" pad_number(123, 2); // "123"
-
random() / random(max) / random(min, max) Returns a random number from the ESP32 hardware RNG:
random()— a float in[0, 1)random(max)— an integer in[0, max)random(min, max)— an integer in[min, max)
random(); // e.g. 0.4821... random(6); // integer 0-5 random(1, 7); // integer 1-6 (a die roll)
-
wifi_connect(ssid, password) Connect to the WiFi network using the provided SSID and password.
-
wifi_status() Returns a boolean indicating whether the device is connected to WiFi.
-
wifi_get_ip() Returns the local IP address as a string.
Time is automatically synchronized via NTP when the device connects to WiFi. The timezone is configured in webscreen.json using a POSIX TZ string (see the WebScreen Admin tool for a full dropdown of all timezones).
-
ntp_synced() Returns
trueif NTP time has been synchronized,falseotherwise. Always check this before using other time functions.if (ntp_synced()) { print("Time is available"); }
-
get_hours() Returns the current hour (0-23) in the configured timezone, or -1 if NTP is not synced.
-
get_minutes() Returns the current minute (0-59), or -1 if NTP is not synced.
-
get_seconds() Returns the current second (0-59), or -1 if NTP is not synced.
-
get_year() Returns the current year (e.g. 2026), or -1 if NTP is not synced.
-
get_month() Returns the current month (1-12), or -1 if NTP is not synced.
-
get_day() Returns the current day of the month (1-31), or -1 if NTP is not synced.
-
get_weekday() Returns the day of the week (0=Sunday, 6=Saturday), or -1 if NTP is not synced.
-
get_epoch() Returns the current Unix timestamp in seconds, or -1 if time is not valid (before 2021).
-
format_time(fmt) / format_time(fmt, epoch) Returns a
strftime-formatted string of the current local time, or of the givenepoch(interpreted in the configured local timezone). Seeman strftimefor format specifiers.format_time("%H:%M:%S"); // "14:05:09" format_time("%a %d %b"); // "Fri 13 Jun" format_time("%Y-%m-%d", 1771337025); // format a specific epoch
Example: Simple Clock
// Wait for NTP sync
if (wifi_status() && ntp_synced()) {
let h = get_hours();
let m = get_minutes();
let s = get_seconds();
let time_str = numberToString(h) + ":" +
(m < 10 ? "0" : "") + numberToString(m) + ":" +
(s < 10 ? "0" : "") + numberToString(s);
set_text(time_label, time_str);
}-
http_get(url) Perform an HTTP GET request to the specified URL. Returns the response body. Supports both HTTP and HTTPS, with custom ports.
// Standard ports http_get("http://example.com/api") // port 80 http_get("https://example.com/api") // port 443 // Custom ports http_get("http://192.168.1.20:2000/api") http_get("https://myserver.com:8443/api")
-
http_post(url, data) Perform an HTTP POST request with the given data. Supports HTTP/HTTPS and custom ports.
http_post("http://192.168.1.20:3000/api", '{"key":"value"}')
-
http_delete(url) Perform an HTTP DELETE request. Supports HTTP/HTTPS and custom ports.
-
http_set_ca_cert_from_sd(certificate_path) Load a CA certificate from SD card for HTTPS requests.
-
http_set_header(name, value) Add a custom HTTP header for subsequent requests.
-
http_clear_headers() Clear all custom HTTP headers.
-
parse_json_value(json_string, key) Parse a JSON string and extract the value for the specified key.
-
sd_read_file(filepath) Read the contents of a file from the SD card.
-
sd_write_file(filepath, content) Write content to a file on the SD card.
-
sd_list_dir(directory) List the contents of a directory on the SD card.
-
sd_delete_file(filepath) Delete a file from the SD card.
-
ble_init(device_name) Initialize BLE with the specified device name.
-
ble_is_connected() Check if a BLE client is connected.
-
ble_write(data) Send data over BLE to connected clients.
-
mqtt_init(broker, port) Initialize the broker and clear the previous session's queue and subscriptions. Returns a boolean. Broker names are limited to 127 bytes; ports must be 1–65535.
-
mqtt_connect(client_id[, username, password]) Connect with optional credentials. Each string is limited to 63 bytes; oversized values are rejected. An empty password is supported. Successful credentials are retained for reconnects.
-
mqtt_publish(topic, message) Publish the full message bytes. Returns
falseif the topic exceeds 127 bytes or the packet exceeds the MQTT buffer; payloads are never silently truncated. -
mqtt_subscribe(topic) Subscribe to a topic, returning a boolean. Up to eight distinct subscriptions are restored after reconnect; failed restoration attempts are retried every five seconds.
-
mqtt_loop() Compatibility no-op: firmware services MQTT between JS callbacks.
-
mqtt_on_message(callback_function_name) Enable incoming-message polling with a nonempty name (up to 31 bytes). The function is not invoked automatically; poll from a JS timer to avoid re-entering the engine.
-
mqtt_has_message(), mqtt_get_topic(), mqtt_get_payload(), mqtt_msg_clear() Inspect the oldest queued message, then remove it with
mqtt_msg_clear(). The FIFO holds four messages, each with a topic up to 127 bytes and payload up to 1024 bytes. Empty and embedded-NUL payloads retain their lengths. The MQTT packet buffer must also fit topic and protocol overhead. -
mqtt_dropped() Count messages rejected because the queue was full or the topic/payload exceeded its limit. Existing queued messages remain intact. Reset on
mqtt_init()and app reload.if (mqtt_dropped() > 0) { print("Losing MQTT messages - poll faster"); }
-
draw_label(text, x, y) Draw a simple text label at the specified coordinates.
-
draw_rect(x, y, width, height [, color]) Draw a rectangle with the specified dimensions. Color is optional (defaults to green 0x00ff00). Returns a handle that can be used with
move_obj(),rotate_obj(), etc. -
show_image(filepath, x, y) Display an image from SD card at the specified position.
-
show_gif_from_sd(filepath, x, y) Display an animated GIF from SD card at the specified position. Loading a new GIF automatically frees the previous one's buffer.
show_gif_from_sd("/animation.gif", 100, 50)
Note: For best performance, keep GIFs under 50KB. Large animated GIFs may cause memory issues.
-
gif_free() Delete the GIF widget created by
show_gif_from_sd()and free its PSRAM buffer. Returnstrueif anything was freed,falseif no GIF was loaded.show_gif_from_sd("/animation.gif", 100, 50); // ... later, when the GIF is no longer needed ... gif_free();
-
create_label(x, y) Create a new label widget at the specified position.
let label = create_label(100, 50); label_set_text(label, "Hello!");
-
label_set_text(label, text) Set the text content of a label.
-
create_image(filepath, x, y) Create an image widget from an SD card file at the given position. Returns an object handle, or -1 on failure.
-
create_image_from_ram(filepath, x, y[, width, height]) Load headerless RGB565_SWAP bytes from SD into PSRAM (max 16 slots). Width and height default to 200; each must be 1–2047. The file must contain exactly
width * height * 2bytes, at most 2 MiB. Returns an object handle, or -1 on failure. Slot indices are logged and assigned lowest-free-first. Usecreate_image()for LVGL binary image files with headers or supported encoded formats. -
ram_image_free(slot) Free a RAM image slot's PSRAM buffer and mark the slot reusable. Returns
falsefor an invalid, unused, or still-referenced slot. Delete image widgets and meters whose image needles use the slot before freeing it.let img = create_image_from_ram("/photo.bin", 0, 0); // uses slot 0 if free // ... later ... obj_delete(img); ram_image_free(0);
-
rotate_obj(object, angle) Rotate an object by the specified angle.
-
move_obj(object, x, y) Move an object to the specified coordinates.
-
animate_obj(object, property, target_value, duration) Animate an object property over time.
-
obj_delete(handle) Delete an object created through the handle-based API (and all of its children) and free its registry slot. Handles of deleted descendants are invalidated too, so they can never be served stale. Invalid handles are reported as an error instead of crashing.
let label = create_label(100, 50); // ... later ... obj_delete(label);
-
obj_set_size(object, width, height) Set the size of an object.
-
obj_align(object, align_type) Align an object within its parent.
-
obj_add_flag(object, flag) Add a flag to an object.
-
obj_clear_flag(object, flag) Remove a flag from an object.
-
obj_set_scroll_dir(object, direction) Set scroll direction for an object.
-
obj_set_scrollbar_mode(object, mode) Set scrollbar display mode.
-
obj_set_scroll_snap_x(object, enable) Enable/disable scroll snapping on X axis.
-
obj_set_scroll_snap_y(object, enable) Enable/disable scroll snapping on Y axis.
-
obj_set_flex_flow(object, flow) Set flexbox flow direction.
-
obj_set_flex_align(object, main_align, cross_align, track_align) Set flexbox alignment properties.
-
create_style() Create a new style object.
-
obj_add_style(object, style, selector) Apply a style to an object.
-
style_set_bg_color(style, color) Set background color.
-
style_set_bg_opa(style, opacity) Set background opacity.
-
style_set_radius(style, radius) Set corner radius.
-
style_set_border_color(style, color) Set border color.
-
style_set_border_width(style, width) Set border width.
-
style_set_border_opa(style, opacity) Set border opacity.
-
style_set_border_side(style, sides) Set which sides have borders.
-
style_set_text_color(style, color) Set text color.
-
style_set_text_font(style, font) Set text font.
-
style_set_text_align(style, align) Set text alignment.
-
style_set_text_letter_space(style, space) Set letter spacing.
-
style_set_text_line_space(style, space) Set line spacing.
-
style_set_text_decor(style, decoration) Set text decoration (underline, strikethrough, etc.).
-
style_set_pad_all(style, padding) Set padding on all sides.
-
style_set_pad_left(style, padding) Set left padding.
-
style_set_pad_right(style, padding) Set right padding.
-
style_set_pad_top(style, padding) Set top padding.
-
style_set_pad_bottom(style, padding) Set bottom padding.
-
style_set_width(style, width) Set object width.
-
style_set_height(style, height) Set object height.
-
style_set_x(style, x) Set X position.
-
style_set_y(style, y) Set Y position.
-
style_set_shadow_width(style, width) Set shadow width.
-
style_set_shadow_color(style, color) Set shadow color.
-
style_set_shadow_ofs_x(style, offset) Set shadow X offset.
-
style_set_shadow_ofs_y(style, offset) Set shadow Y offset.
-
style_set_outline_width(style, width) Set outline width.
-
style_set_outline_color(style, color) Set outline color.
-
style_set_outline_pad(style, padding) Set outline padding.
-
style_set_img_recolor(style, color) Set image recolor.
-
style_set_img_recolor_opa(style, opacity) Set image recolor opacity.
- style_set_transform_angle(style, angle) Set transformation angle.
-
style_set_line_color(style, color) Set line color.
-
style_set_line_width(style, width) Set line width.
-
style_set_line_rounded(style, rounded) Enable/disable rounded line ends.
Scales and indicators are returned to JavaScript as small slot-index handles (not pointers): lv_meter_add_scale() returns a scale handle and the lv_meter_add_* indicator functions return indicator handles, with -1 on failure. Pass the returned handle back into the corresponding lv_meter_set_* functions; an invalid handle produces an error instead of crashing the device.
-
lv_meter_create(parent) Create a meter widget. Returns an object handle.
-
lv_meter_add_scale(meter) Add a scale to the meter. Returns a scale handle, or -1 on failure.
-
lv_meter_set_scale_ticks(meter, scale, count, width, length, color) Configure scale tick marks.
-
lv_meter_set_scale_major_ticks(meter, scale, nth, width, length, color, label_gap) Configure major tick marks with labels.
-
lv_meter_set_scale_range(meter, scale, min, max, angle_range, rotation) Set the scale's value range and angular range.
-
lv_meter_add_arc(meter, scale, width, color) Add an arc indicator to the meter. Returns an indicator handle, or -1 on failure.
-
lv_meter_add_scale_lines(meter, scale, color, width, length, r_mod) Add scale lines to the meter. Returns an indicator handle, or -1 on failure.
-
lv_meter_add_needle_line(meter, scale, width, color, r_mod) Add a needle line indicator. Returns an indicator handle, or -1 on failure.
-
lv_meter_add_needle_img(meter, scale, img_src, pivot_x, pivot_y) Add a needle image indicator. Returns an indicator handle, or -1 on failure.
-
lv_meter_set_indicator_value(meter, indicator, value) Set the value of an indicator.
Charts plot one or more data series. lv_chart_create() returns an object handle; lv_chart_add_series() returns a small slot-index series handle (not a pointer), with -1 on failure. Pass both handles back into the lv_chart_set_next_value* functions. The various axis/range constants (LV_CHART_TYPE_*, LV_CHART_AXIS_*, LV_CHART_UPDATE_MODE_*) are passed as integers.
-
lv_chart_create(parent) Create a chart widget. Returns an object handle.
-
lv_chart_set_type(chart, type) Set the chart type (e.g.
LV_CHART_TYPE_LINE,LV_CHART_TYPE_BAR). -
lv_chart_set_div_line_count(chart, y_div, x_div) Set the number of horizontal and vertical division lines.
-
lv_chart_set_update_mode(chart, mode) Set the update mode (e.g.
LV_CHART_UPDATE_MODE_SHIFT,LV_CHART_UPDATE_MODE_CIRCULAR). -
lv_chart_set_range(chart, axis, min, max) Set the value range for an axis (e.g.
LV_CHART_AXIS_PRIMARY_Y). -
lv_chart_set_point_count(chart, count) Set the number of points shown per series.
-
lv_chart_refresh(chart) Redraw the chart after changing its data.
-
lv_chart_add_series(chart, color, axis) Add a data series with the given color and axis. Returns a series handle, or -1 on failure.
-
lv_chart_set_next_value(chart, series, value) Append the next Y value to a series (for line/bar charts).
-
lv_chart_set_next_value2(chart, series, x_value, y_value) Append the next X/Y pair to a series (for scatter charts).
-
lv_chart_set_axis_tick(chart, axis, major_len, minor_len, major_count, minor_count, label_enable, draw_size) Configure tick marks and labels on an axis.
-
lv_chart_set_zoom_x(chart, zoom) Set the horizontal zoom (256 = no zoom).
-
lv_chart_set_zoom_y(chart, zoom) Set the vertical zoom (256 = no zoom).
-
lv_spangroup_create(parent) Create a spangroup widget for rich text.
-
lv_spangroup_set_align(spangroup, align) Set text alignment in spangroup.
-
lv_spangroup_set_overflow(spangroup, overflow) Set text overflow behavior.
-
lv_spangroup_set_indent(spangroup, indent) Set text indentation.
-
lv_spangroup_set_mode(spangroup, mode) Set spangroup display mode.
-
lv_spangroup_new_span(spangroup) Create a new span within the spangroup. Returns a span handle (small slot index, max 16 spans), or -1 if no slot is free. Pass this handle to the
lv_span_set_text*functions; an invalid handle produces an error instead of crashing. -
lv_span_set_text(span, text) Set text content of a span. The text is copied into LVGL-owned storage.
-
lv_span_set_text_static(span, text) Same as
lv_span_set_text()— the name is kept for compatibility, but the text is always copied. (A truly static reference would point into the JS arena, which the garbage collector moves.) -
lv_spangroup_refr_mode(spangroup) Refresh the spangroup display.
-
lv_line_create(parent) Create a line widget.
-
lv_line_set_points(line, points) Set the points that define the line.
// Connect to WiFi
wifi_connect("MyNetwork", "MyPassword");
// Wait for connection
while (!wifi_status()) {
delay(1000);
print("Connecting to WiFi...");
}
// Make HTTP request
let response = http_get("https://api.example.com/data");
print("Response: " + response);// Initialize MQTT
mqtt_init("mqtt.example.com", 1883);
mqtt_connect("webscreen_device", "username", "password");
// Publish data
mqtt_publish("sensors/temperature", "25.5");
// Subscribe to topic
mqtt_subscribe("commands/display");
// Enable delivery, then consume at most four messages per timer tick.
mqtt_on_message("poll_messages");
let poll_messages = function() {
for (let i = 0; i < 4 && mqtt_has_message(); i++) {
print("Received: " + mqtt_get_topic() + " = " + mqtt_get_payload());
mqtt_msg_clear();
}
};
create_timer("poll_messages", 100);// Create a label
let label = create_label(null);
label_set_text(label, "Hello WebScreen!");
// Create and apply styles
let style = create_style();
style_set_text_color(style, "#FF0000");
style_set_bg_color(style, "#FFFFFF");
style_set_pad_all(style, 10);
obj_add_style(label, style, 0);
// Position the label
obj_align(label, "center");// Read configuration
let config = sd_read_file("/config.json");
let settings = parse_json_value(config, "settings");
// Write log file
let timestamp = numberToString(Date.now());
sd_write_file("/logs/" + timestamp + ".txt", "Application started");
// List directory contents
let files = sd_list_dir("/apps/");
print("Available apps: " + files);All functions return appropriate values to indicate success or failure. Always check return values for robust applications:
if (!wifi_connect("MyNetwork", "MyPassword")) {
print("WiFi connection failed");
return;
}
let response = http_get("https://api.example.com/data");
if (response === null || response === "") {
print("HTTP request failed");
return;
}JavaScript errors carry a 1-based source line number suffix, e.g.:
ERROR: 'foo' not found (line 12)
For an error raised inside a function body, the line number is relative to that function's first line (Elk re-parses each function body as a separate snippet), not the line within the whole file. Errors from timer callbacks, button callbacks, and /eval are also recorded and can be reviewed with the /errors serial command.
Runaway scripts no longer crash or reboot the device — they get a JavaScript error instead:
- Step limit: each evaluation (the initial script run, and each timer callback) has a statement budget of 2,000,000 statements. An infinite loop terminates with a
step limiterror. - Elapsed-time limits: startup gets 10 seconds, timer/button callbacks 5 seconds, and
/eval1 second. A timeout reportsexecution timeout. Checks run periodically during JS execution;/loadand/restart_appalso interrupt busy JS.delay()and Wi-Fi connection waiting cooperate with interruption. Native HTTP/MQTT operations must return before their elapsed-time check can run. - C-stack guard: deeply recursive code terminates with a
C stackerror instead of overflowing the task stack and crashing the device.
Errors raised in a timer callback are printed to the serial console together with arena and heap statistics. After 10 consecutive failing callbacks, the firmware restarts the JavaScript app in place (no device reboot); if the script keeps failing, the error is shown on the display and the device stays alive so you can fix the script over serial (/load, /restart_app).
- Use
delay()appropriately to prevent blocking the system - Poll queued MQTT messages from a timer; firmware services the connection between callbacks
- Minimize frequent file I/O operations
- Cache frequently accessed data in variables
- Use appropriate data types for memory efficiency
WebScreen uses LVGL v8.3 with the following configuration:
- Color Depth: 16-bit (RGB565)
- DPI: 130
- Refresh Period: 30ms
The following Montserrat font sizes are enabled:
| Size | Constant | Usage |
|---|---|---|
| 14 | 14 |
Default font, small text |
| 20 | 20 |
Body text |
| 28 | 28 |
Subheadings |
| 34 | 34 |
Medium headings |
| 40 | 40 |
Large headings |
| 44 | 44 |
Extra large |
| 48 | 48 |
Display text |
// Example: Set font size
style_set_text_font(style, 48); // Use largest font
style_set_text_font(style, 14); // Use smallest/default fontNote: Only these specific sizes are available. Using other sizes (e.g., 16, 24, 32) will not work.
| Widget | Status | Notes |
|---|---|---|
| Arc | ✅ Enabled | Circular progress/gauge |
| Button | ✅ Enabled | Clickable buttons |
| Button Matrix | ✅ Enabled | Grid of buttons |
| Canvas | ✅ Enabled | Custom drawing |
| Image | ✅ Enabled | Display images |
| Label | ✅ Enabled | Text display |
| Line | ✅ Enabled | Line drawing |
| Chart | ✅ Enabled | Data visualization |
| Meter | ✅ Enabled | Gauge/speedometer |
| Message Box | ✅ Enabled | Popup dialogs |
| Span | ✅ Enabled | Rich text |
The following widgets are not available to save memory:
- Bar, Slider, Switch
- Checkbox, Dropdown, Roller
- Textarea, Table
- Calendar, Colorwheel
- Keyboard, List, Menu
- Spinbox, Spinner
- Tabview, Tileview, Window
| Format | Status | Notes |
|---|---|---|
| PNG | ✅ Enabled | Recommended for icons |
| SJPG | ✅ Enabled | Split JPG for large images |
| GIF | ✅ Enabled | Animated images |
| BMP | ❌ Disabled | Not supported |
- Flexbox (
LV_USE_FLEX): ✅ Enabled - CSS-like flexible layouts - Grid (
LV_USE_GRID): ✅ Enabled - CSS-like grid layouts
- Default Theme: ✅ Enabled (Dark mode)
- Basic Theme: ✅ Enabled
- Mono Theme: ✅ Enabled
- Complex drawing (shadows, gradients, rounded corners): ✅ Enabled
- Text selection: ✅ Enabled
- UTF-8 encoding: ✅ Enabled
WebScreen uses the Elk JavaScript engine with 256KB of heap memory (the "JS arena") allocated in PSRAM by default. The size is configurable with the flat "js_heap_kb" key in webscreen.json (clamped to 64-1024 KB). Garbage collection runs automatically when the arena fills up; use mem_info() and gc() to observe and influence it. To ensure stable operation:
- Keep scripts under 3KB for best stability
- Larger scripts may work but consume more memory during parsing
- Limit the number of styles to 5 or fewer per app
- Limit the number of labels to 10 or fewer per app
- Reuse styles across multiple labels when possible
- Keep GIFs under 50KB for reliable playback
- Use small dimensions (100x100 or less)
- Reduce the number of frames and colors
- Large GIFs (>100KB) may cause cache errors and crashes
- Use one timer callback when possible
- Each additional timer consumes memory
- Keep callback functions simple
"use strict";
// Minimal styles (reuse where possible)
let bigStyle = create_style();
style_set_text_font(bigStyle, 48);
style_set_text_color(bigStyle, 0xFFFFFF);
style_set_text_align(bigStyle, 1);
let smallStyle = create_style();
style_set_text_font(smallStyle, 20);
style_set_text_color(smallStyle, 0x888888);
// Create labels
let title = create_label(268, 80);
obj_add_style(title, bigStyle, 0);
label_set_text(title, "Hello");
let subtitle = create_label(268, 140);
obj_add_style(subtitle, smallStyle, 0);
label_set_text(subtitle, "World");
// Single timer callback
let update = function() {
// Keep it simple
label_set_text(title, "Updated");
};
create_timer("update", 1000);This API provides comprehensive access to WebScreen's hardware and software capabilities, enabling the creation of sophisticated embedded applications with rich user interfaces and network connectivity.
create_timer(name, period_ms)accepts a simple JS identifier up to 55 bytes and periods from 1 to 2,147,483,647 ms. At most 32 app timers are active. Reusing a name resets its period; invalid requests and capacity exhaustion returnfalse. Success returnsnull.timer_delete(name)may safely delete the timer calling it.- Widget handles are opaque, nonnegative numbers. Up to 256 registered widgets are active; creation returns -1 when the registry is full. Deleted widget handles stay invalid even when slots are reused. This generation protection applies to widget handles, not style or sub-object slot indices.
- Graphics bindings reject nonnumeric, nonfinite, and out-of-range 32-bit numeric arguments with a JS error. Boolean style/flag arguments remain supported as 0/1. RGB colors use
0xRRGGBB. str_length()counts raw bytes, including literal quotes and embedded NULs.str_substring(text, start, length)clamps offsets safely and preserves those bytes; negative length means through the end.sd_write_file(path, content)writes raw string bytes and reports short writes asfalse.- Chart types retain LVGL 8 values: 0 = none, 1 = line, 2 = bar, 3 = scatter. Native chart zoom and axis ticks remain active. Point counts are limited to 1–4096; zoom is 256–4096 (256 = 1×).
- A series must belong to the chart passed to it; scales and indicators must belong to the supplied meter. Invalid ownership or widget classes are rejected. Chart ranges must increase and fit LVGL coordinates.
- Meter scales require 2–1000 ticks, a positive major-tick frequency, an increasing range, and an angle range of 1–360 degrees.
lv_line_set_pointskeeps up to 16 coordinate pairs per line and frees its storage with the line. Updating one line does not change another.label_set_textpreserves string contents, including newlines and quotes, with a 255-byte text limit.- Script files must be nonempty and no larger than 1 MiB. Execution and recursion budgets turn runaway JavaScript into recoverable errors;
/errorsreports them and/loadretries a corrected app.