Skip to content

Siryll/wg_display_embedded_widget_template

Repository files navigation

Widget Template Development

Template for creating Widgets for the WG-Display-Embedded, included with an automatic build pipeline to create the final widget binary.

Widgets are Rust crates compiled to wasm32-unknown-unknown WebAssembly and packaged as WASM components using the WIT Component Model. They implement a standard interface (defined in WIT) and are installed and managed through the Web UI without reflashing the device.


Quick Start

Use the Widget Template as a starting point:

git clone https://github.com/Siryll/wg_display_embedded_widget_template my-widget
cd my-widget

The template includes the correct Cargo.toml, WIT files, and a working lib.rs example.

For reference implementations see:


Install on device

  1. Push your widget to GitHub
  2. Create a new Tag, the Pipeline will automatically build and put the widget.precompiled.wasm binary into the release.
  3. In the Web UI of the embedded WG-Display, click Install from URL and enter the direct download URL

WIT Interface

Exported functions (widget implements these)

package widget:widget;

export get-name: func() -> string;
export get-version: func() -> string;
export get-config-schema: func() -> string;
export get-run-update-cycle-seconds: func() -> u32;
export run: func(context: widget-context) -> widget-result;
Export Return Description
get-name string Display name shown in the Web UI and on screen
get-version string Semver string (e.g. "0.1.0")
get-config-schema string JSON Schema for the configuration form. Return "{}" if no config is needed
get-run-update-cycle-seconds u32 How often the widget should be invoked (seconds)
run widget-result Main entry point — fetch data, compute output, return string

Types (types.wit)

record datetime {
    seconds: u64,
    nanoseconds: u32,
}

record widget-context {
    last-invocation: datetime,   // UTC time of previous run (0 on first run)
    config: string,              // JSON matching your config schema
}

record widget-result {
    data: string,                // Text displayed on screen (or error message)
}

Imported host functions (the device provides these)

http

interface http {
    type status = u16;

    variant method { get, head, post, put, delete }

    record response {
        status: status,
        content-length: option<u64>,
        bytes: list<u8>,
    }

    request: func(method: method, url: string, body: option<list<u8>>) -> result<response>;
}

Limitations:

  • Maximum response size: ~512 KB (HTTP client buffer limit)
  • Timeout: 30 seconds
  • No TLS certificate verification
  • Redirects: automatic, up to 5 hops
  • No streaming — entire response is buffered

clocks

interface clocks {
    now: func() -> datetime;
}

Returns the current UTC time (synced from timeapi.io at boot). seconds is a Unix timestamp; nanoseconds is the sub-second part. Returns {0, 0} if time has not been synced yet.

logging

interface logging {
    enum level { debug, info, warn, error }
    log: func(level: level, context: string, message: string);
}

Output appears in the defmt serial monitor.

random

interface random {
    get-random: func() -> u64;
}

Returns a 64-bit random value from the ESP32 hardware RNG. Not cryptographically secure.


Configuration Schema

Return a valid JSON Schema from get-config-schema. The Web UI uses the jsonform library to render a form from the schema automatically.

In run(), parse the config from context.config:

#[derive(serde::Deserialize)]
struct Config {
    location: String,
}

fn run(context: WidgetContext) -> WidgetResult {
    let config: Config = serde_json::from_str(&context.config)
        .unwrap_or(Config { location: "Bern".into() });
    // ...
}

Manually Compiling and Installing

1. Clone submodules

git submodule update --init

2. Compile to WASM

rustup target add wasm32-unknown-unknown
cargo build --release --manifest-path widget/Cargo.toml --target wasm32-unknown-unknown

3. Create a WASM component

Install wasm-tools:

cargo install wasm-tools
wasm-tools component new \
  widget/target/wasm32-unknown-unknown/release/widget.wasm \
  -o widget/widget.component.wasm

4. Precompile for Wasmtime on ESP32

The on-device runtime is unable to compile the binary at runtime, for that reason it needs to be compiled for the xtensa architecture beforehand.

cargo build --release --manifest-path wg_display_embedded_precompiler/Cargo.toml
./wg_display_embedded_precompiler/target/release/wg-display-embedded-precompiler widget/widget.component.wasm  widget.precompiled.wasm

Size Constraints

If your widget exceeds ~512 KB after precompilation, the installation will fail with an HTTP error. Reduce size by:

  • Minimizing dependencies
  • Using default-features = false on all crates
  • Avoiding large embedded data (fonts, lookup tables)
  • Running wasm-opt -Os before the component/precompile steps

About

Template repo for the wg_display_embedded widgets to make creation of own widgets easy

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages