Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ WebScreen is an ESP32-based platform that runs dynamic JavaScript applications u
### Build Process
The project is compiled using Arduino IDE:
1. Install ESP32 boards package via Board Manager (v2.0.3 or higher)
2. Install required libraries: ArduinoJson, LVGL v8.3.0-dev, PubSubClient
2. Install required libraries: ArduinoJson, LVGL 9.5.0, PubSubClient
3. Copy the provided `lv_conf.h` configuration file to Arduino libraries folder
4. Select ESP32-S3 board with specific settings (refer to docs/arduino_tools_settings.png)
5. Compile and upload via USB (CDC_ON_BOOT must be enabled)
Expand Down Expand Up @@ -68,7 +68,7 @@ JS errors carry a 1-based `(line N)` suffix (relative to a function body's first
Table-driven dispatch (`kCommands[]`, one row per command; table order = `/help` order). Commands: `/help`, `/stats`, `/info`, `/write`, `/upload <file> [base64]`, `/config get|set`, `/ls [path] [json]` (json = single-line machine-readable listing; plain listing ends with a `Total: N files, M directories` marker), `/cat`, `/rm <file|empty-dir>` (empty directories removed via `rmdir`), `/mkdir <path>`, `/download <file>` (alias `/dl`; base64 dump between `=== DOWNLOAD <path> SIZE <n> ===` / `=== DOWNLOAD END ===` markers), `/load [save]`, `/restart_app`, `/eval`, `/errors`, `/gc`, `/screenshot` (alias `/ss`), `/wget` (alias `/fetch` — the old `download` alias now belongs to the base64 dump), `/ping`, `/backup`, `/monitor`, `/brightness`, `/time`, `/settime`, `/factory_reset confirm` (requires the literal `confirm`; deletes `/webscreen.json` and reboots to fallback), `/reboot`.

- **`webscreen_base64.h`**: shared header-only base64 encoder used by `/download` and `/screenshot` (57 raw bytes → one 76-char MIME-width line).
- **Screenshot handoff pattern**: same discipline as `/eval` — the serial handler on loopTask only sets `g_js_screenshot_pending` (via `webscreen_runtime_request_screenshot()`); the JS task notices the flag at its next safe point, takes an `lv_snapshot` (LVGL objects must never be touched from another task), and streams the base64 RGB565 dump between `=== SCREENSHOT <w>x<h> RGB565_SWAP ===` / `=== SCREENSHOT END ===` markers. Requires the JS runtime to be active; refuses while a capture is in flight.
- **Screenshot handoff pattern**: same discipline as `/eval` — the serial handler on loopTask only sets `g_js_screenshot_pending` (via `webscreen_runtime_request_screenshot()`); the JS task notices the flag at its next safe point, takes an `lv_snapshot` (LVGL objects must never be touched from another task), and streams the base64 dump between `=== SCREENSHOT <w>x<h> RGB565 ===` / `=== SCREENSHOT END ===` markers (plain little-endian RGB565 since LVGL 9). Requires the JS runtime to be active; refuses while a capture is in flight.

### LVGL Configuration (lv_conf.h)

Expand All @@ -77,8 +77,8 @@ Table-driven dispatch (`kCommands[]`, one row per command; table order = `/help`
- Other sizes (8, 10, 12, 16, 18, 22, 24, etc.) are NOT enabled

**Enabled Widgets**:
- Core: Label, Image, Arc, Line, Button, Button Matrix, Canvas
- Extra: Chart, Meter, Message Box, Span (rich text)
- Core: Label, Image, Arc, Line, Button, Canvas
- Extra: Chart, Scale (backs the JS meter API), Span (rich text)

**Disabled Widgets** (to save memory):
- Bar, Slider, Switch, Checkbox, Dropdown, Roller
Expand All @@ -88,14 +88,14 @@ Table-driven dispatch (`kCommands[]`, one row per command; table order = `/help`
**Image Formats**:
- PNG: ✅ Enabled
- GIF: ✅ Enabled
- SJPG: ✅ Enabled (split JPG)
- JPG: ✅ Enabled (baseline; SJPG split-JPG is NOT supported by LVGL 9 — convert old .sjpg assets)
- BMP: ❌ Disabled

**Layouts**: Flexbox and Grid enabled

**Display**: 16-bit color (RGB565), 130 DPI, 30ms refresh

**Rendering**: a single internal-DRAM draw buffer (the second PSRAM buffer was removedLVGL 8 alternates buffers, so under a synchronous flush half of all rendering hit slow OPI PSRAM for no benefit). Image cache enabled (`LV_IMG_CACHE_DEF_SIZE 2`) so PNG/SJPG/GIF images from SD are not re-decoded on every redraw. Snapshot support enabled (`LV_USE_SNAPSHOT 1`) for the `/screenshot` serial command. `lv_conf.h` was changed for these — copy the updated file to the Arduino libraries folder (`~/Arduino/libraries/lv_conf.h`).
**Rendering**: LVGL **9.5** (branch `feature/lvgl-9.5-migration`; the Arduino library at `~/Arduino/libraries/lvgl` must be 9.5.0an 8.3.11 backup lives at `~/Arduino/lvgl-8.3.11.bak` for older branches). Single internal-DRAM draw buffer, display color format `LV_COLOR_FORMAT_RGB565_SWAPPED` (replaces LVGL 8's `LV_COLOR_16_SWAP`). Snapshot enabled (`LV_USE_SNAPSHOT 1`) for `/screenshot`, which now streams plain (non-swapped) RGB565. `lv_conf.h` is the v9 formatalways copy the repo copy to `~/Arduino/libraries/lv_conf.h` after changing it. JS API compat notes: `lv_meter_*` bindings are reimplemented on `lv_scale` (v9 removed lv_meter); `lv_chart_set_zoom_x/y` and `lv_chart_set_axis_tick` are no-ops (removed upstream).

### Configuration System
Uses `/webscreen.json` on SD card:
Expand Down
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ This is the easiest way to get started with WebScreen without any development se
```
Library Manager → Install:
- ArduinoJson (by Benoit Blanchon) - v6.x or later
- LVGL (by kisvegabor) - v8.3.X
- LVGL (by kisvegabor) - v9.5.x
- PubSubClient (by Nick O'Leary) - v2.8 or later
```

Expand All @@ -91,13 +91,17 @@ This is the easiest way to get started with WebScreen without any development se
```

Key LVGL settings configured for WebScreen:
- Color depth: 16-bit (RGB565) with byte swap enabled
- Custom memory management using stdlib malloc/free
- Color depth: 16-bit (RGB565), display runs in byte-swapped mode for the QSPI panel
- stdlib malloc/free for LVGL allocations
- Display refresh: 30ms for stability
- **Enabled fonts**: Montserrat 14, 20, 28, 34, 40, 44, 48
- **Image formats**: PNG, GIF, SJPG (BMP disabled)
- **Widgets**: Label, Image, Arc, Line, Button, Chart, Meter, Span
- **Image formats**: PNG, GIF, JPG (BMP disabled)
- **Widgets**: Label, Image, Arc, Line, Button, Bar, Chart, Scale, Span
- **Layouts**: Flexbox and Grid enabled

Note: this branch requires LVGL 9.5. The `lv_conf.h` in the repo is in the
v9 format and will not work with LVGL 8. If you were building an older
branch before, replace the LVGL library and copy the new `lv_conf.h` again.
- Complex drawing features enabled (shadows, gradients, etc.)

5. **Open WebScreen Sketch**
Expand Down Expand Up @@ -310,10 +314,10 @@ WebScreen features a modular architecture with clear separation of concerns:
```

#### LVGL Configuration
WebScreen includes a custom `lv_conf.h` file optimized for ESP32-S3 with AMOLED display:
WebScreen includes a custom `lv_conf.h` file (LVGL 9.5 format) optimized for ESP32-S3 with AMOLED display:

**Display Settings:**
- **Color Format**: 16-bit RGB565 with byte swapping for SPI compatibility
- **Color Format**: 16-bit RGB565, byte-swapped at the display level (`LV_COLOR_FORMAT_RGB565_SWAPPED`)
- **Resolution**: 536x240 pixels
- **DPI**: 130 for optimal widget sizing
- **Refresh Rate**: 30ms for stable display output
Expand All @@ -337,10 +341,10 @@ WebScreen includes a custom `lv_conf.h` file optimized for ESP32-S3 with AMOLED
- **Layouts**: Flexbox and Grid

**Supported Image Formats:**
- PNG ✅, GIF ✅, SJPG ✅, BMP ❌
- PNG ✅, GIF ✅, JPG ✅ (SJPG not supported since LVGL 9), BMP ❌

**Performance Optimizations:**
- Image caching enabled (decoded PNG/SJPG/GIF images from SD are not re-decoded on every redraw)
- Image caching enabled (decoded PNG/JPG/GIF images from SD are not re-decoded on every redraw)
- Gradient caching disabled to reduce memory usage
- Shadow caching disabled for predictable memory consumption
- Memory management uses ESP32 heap allocator
Expand Down
25 changes: 25 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Release Notes

## Unreleased - LVGL 9.5 Migration

The firmware now builds against LVGL 9.5.0 instead of 8.3.11. If you build
locally, replace the LVGL library in your Arduino libraries folder and copy
the new `lv_conf.h` next to it — the old v8 config no longer applies.

What changed under the hood:

- New display driver API. The panel's byte-swapped RGB565 is handled by the
display color format now (`LV_COLOR_FORMAT_RGB565_SWAPPED`), since the old
`LV_COLOR_16_SWAP` option is gone.
- LVGL dropped the meter widget, so the `lv_meter_*` JS functions are now
implemented with `lv_scale` under the hood. Scripts don't need changes.
Two parameters are accepted but ignored (no v9 equivalent): the gradient
color of `lv_meter_add_scale_lines` and the major-tick `label_gap`.
- `lv_chart_set_zoom_x/y` and `lv_chart_set_axis_tick` became no-ops; LVGL
removed those chart features.
- `/screenshot` streams plain RGB565 now (header says `RGB565` instead of
`RGB565_SWAP`). The admin tool and IDE handle both.
- The boot logo and fallback GIF were converted to the v9 image format.
- SJPG (split JPG) support is gone — LVGL 9 dropped the decoder. Plain JPG
and PNG still work; convert any .sjpg assets on your SD card.

Flash use went up about 80KB to 96% of the 3MB app slot.

## Unreleased (2.2.0-dev) - JS APIs, Live REPL, Serial Tooling & Rendering Performance

### New JS APIs
Expand Down
23 changes: 14 additions & 9 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,12 @@ if (wifi_status() && ntp_synced()) {

#### Meter Widget

Since the LVGL 9.5 migration the meter functions are implemented on top of the
`lv_scale` widget (LVGL removed `lv_meter` in v9). The JavaScript API below is
unchanged; two cosmetic parameters are accepted but ignored because v9 has no
equivalent: the gradient color of `lv_meter_add_scale_lines` and the
`label_gap` of `lv_meter_set_scale_major_ticks`.

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)**
Expand Down Expand Up @@ -645,13 +651,13 @@ Charts plot one or more data series. `lv_chart_create()` returns an object handl
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.
No-op since LVGL 9.5 (the underlying API was removed upstream). Kept so older scripts keep running.

- **lv_chart_set_zoom_x(chart, zoom)**
Set the horizontal zoom (256 = no zoom).
No-op since LVGL 9.5, same reason as above.

- **lv_chart_set_zoom_y(chart, zoom)**
Set the vertical zoom (256 = no zoom).
No-op since LVGL 9.5, same reason as above.

#### Spangroup Widget (Rich Text)

Expand Down Expand Up @@ -816,7 +822,7 @@ Errors raised in a timer callback are printed to the serial console together wit

## LVGL Configuration

WebScreen uses LVGL v8.3 with the following configuration:
WebScreen uses LVGL 9.5 with the following configuration:

### Display Settings
- **Color Depth**: 16-bit (RGB565)
Expand Down Expand Up @@ -857,8 +863,7 @@ style_set_text_font(style, 14); // Use smallest/default font
| Label | ✅ Enabled | Text display |
| Line | ✅ Enabled | Line drawing |
| Chart | ✅ Enabled | Data visualization |
| Meter | ✅ Enabled | Gauge/speedometer |
| Message Box | ✅ Enabled | Popup dialogs |
| Scale | ✅ Enabled | Gauge/speedometer (backs the meter API) |
| Span | ✅ Enabled | Rich text |

### Disabled Widgets
Expand All @@ -867,8 +872,8 @@ The following widgets are **not available** to save memory:
- Bar, Slider, Switch
- Checkbox, Dropdown, Roller
- Textarea, Table
- Calendar, Colorwheel
- Keyboard, List, Menu
- Calendar, Keyboard
- List, Menu, Message Box
- Spinbox, Spinner
- Tabview, Tileview, Window

Expand All @@ -877,7 +882,7 @@ The following widgets are **not available** to save memory:
| Format | Status | Notes |
|--------|--------|-------|
| PNG | ✅ Enabled | Recommended for icons |
| SJPG | ✅ Enabled | Split JPG for large images |
| JPG | ✅ Enabled | Baseline JPG via TJpgDec. SJPG (split JPG) is not supported by LVGL 9 |
| GIF | ✅ Enabled | Animated images |
| BMP | ❌ Disabled | Not supported |

Expand Down
12 changes: 6 additions & 6 deletions docs/SerialCommands.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,16 +395,16 @@ Captures the current screen contents and streams them over serial as base64-enco
WebScreen> /screenshot
Queued. Data follows as an '=== SCREENSHOT ... ===' block

=== SCREENSHOT 536x240 RGB565_SWAP ===
=== SCREENSHOT 536x240 RGB565 ===
[... base64 lines, 76 chars each ...]
=== SCREENSHOT END ===
```

**Stream Format:**
- Header line: `=== SCREENSHOT <w>x<h> RGB565_SWAP ===` (width and height in pixels)
- Header line: `=== SCREENSHOT <w>x<h> RGB565 ===` (width and height in pixels)
- Body: base64-encoded raw RGB565 pixel data, 76 characters per line (57 raw bytes per line, classic MIME width)
- Footer line: `=== SCREENSHOT END ===`
- `RGB565_SWAP` means the two bytes of each 16-bit pixel are swapped (`LV_COLOR_16_SWAP` is enabled in `lv_conf.h`) — swap them back on the host before interpreting the pixels as little-endian RGB565
- Pixels are little-endian RGB565. Older firmware (LVGL 8 builds) reports `RGB565_SWAP` in the header instead, meaning the two bytes of each pixel are swapped — check the marker before decoding

**Refused when:**
- The JS runtime is not running (fallback mode)
Expand All @@ -431,9 +431,9 @@ while True:
elif w and line and not line.startswith(('WebScreen>', 'Queued')):
data += base64.b64decode(line)

swapped = bytearray(len(data)) # undo LV_COLOR_16_SWAP
swapped[0::2], swapped[1::2] = data[1::2], data[0::2]
Image.frombytes('RGB', (w, h), bytes(swapped), 'raw', 'BGR;16').save('shot.png')
Image.frombytes('RGB', (w, h), bytes(data), 'raw', 'BGR;16').save('shot.png')
# For RGB565_SWAP (older firmware), swap byte pairs first:
# data[0::2], data[1::2] = data[1::2], data[0::2]
```

**Use Cases:**
Expand Down
Loading