Complete guide to developing WebAssembly applications for AkiraOS. Apps can be written in C, Rust, or Python (MicroPython).
Toolchain Setup
C Apps — WASI SDK
The WASI SDK provides the clang/lld toolchain used to compile .wasm binaries. AkiraOS apps target wasm32-unknown-unknown — not the WASI runtime. No WASI system calls are used at runtime.
cd ~
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-linux.tar.gz
tar xvf wasi-sdk-24.0-x86_64-linux.tar.gz
export WASI_SDK_PATH=~/wasi-sdk-24.0
Rust Apps — rustup
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add the WASM target
rustup target add wasm32-unknown-unknown
No WASI SDK is needed for Rust apps.
Python Apps — micropython.wasm
Python apps use a prebuilt micropython.wasm runtime that includes the _akira native C module. Obtain it from the AkiraOS releases page and place it at:
AkiraSDK/python/runtime/micropython.wasm
or set the environment variable MICROPYTHON_WASM=/path/to/micropython.wasm.
See AkiraSDK/python/runtime/README.md.
Build Process
C Apps — Using AkiraSDK (Recommended)
The AkiraSDK/ submodule includes include/akira_api.h and ready-to-build sample apps in AkiraSDK/wasm_apps/.
# Build all C sample apps
cd ~/akira-workspace/AkiraOS/AkiraSDK/wasm_apps
./build.sh
# Build a single C app
./build.sh hello_world
C Apps — Simple Custom App
// app.c
#include "akira_api.h"
int main(void) {
printf("Hello from WASM!");
return 0;
}
Compile:
$WASI_SDK_PATH/bin/clang \
-target wasm32-unknown-unknown \
-nostdlib \
-fvisibility=hidden \
-Wl,--no-entry \
-Wl,--export=main \
-Wl,--allow-undefined \
-Wl,--strip-all \
-z stack-size=4096 \
-Wl,--initial-memory=65536 \
-Wl,--max-memory=65536 \
-I ~/akira-workspace/AkiraOS/AkiraSDK/include \
-O2 \
-o app.wasm \
app.c
Important: Use wasm32-unknown-unknown (bare-metal), not wasm32-wasi. AkiraOS provides its own API layer without WASI system calls.
Rust Apps
Full guide: AkiraSDK/docs/RUST_GUIDE.md
Project structure
my_rust_app/
├── Cargo.toml # crate-type = ["cdylib"]
├── .cargo/config.toml # target = "wasm32-unknown-unknown" + linker flags
├── manifest.json
└── src/
└── main.rs
A complete template is at AkiraSDK/wasm_apps/rust/hello_world/.
Writing a Rust app
#![no_std]
#![no_main]
use akira_sdk::{console, display};
#[no_mangle]
pub extern "C" fn main() -> i32 {
console::println("Hello from Rust WASM!");
display::clear(display::COLOR_BLACK);
display::text(10, 10, "Hello Rust!", display::COLOR_WHITE);
display::flush();
0
}
Building
# From the app directory
cargo build --target wasm32-unknown-unknown --release
# Output: target/wasm32-unknown-unknown/release/<name>.wasm
# Via AkiraSDK build script
cd AkiraSDK/wasm_apps
./build.sh rust/my_app
# Via make
cd AkiraSDK/wasm_apps
make rust/my_app
make build-rust # all Rust apps
Cargo.toml essentials
[lib]
crate-type = ["cdylib"] # produces .wasm with C export table
name = "my_app"
path = "src/main.rs"
[dependencies]
akira_sdk = { path = "../../rust/akira_sdk" }
[profile.release]
opt-level = "s"
lto = true
panic = "abort"
strip = true
Python Apps (MicroPython)
Full guide: AkiraSDK/docs/PYTHON_GUIDE.md
Architecture
Python scripts are packaged into a WASM binary by injecting the source as a custom section (akira_py_script) into micropython.wasm. At runtime, WAMR loads the binary; MicroPython reads the section and executes the script. The _akira native C module (built into the runtime) maps all AkiraOS API calls.
main.py + micropython.wasm → py_to_wasm.py → app.wasm
Project structure
my_python_app/
├── main.py # import akira; call API
└── manifest.json # memory_quota must be ≥ 262144 (256 KB)
A complete template is at AkiraSDK/python/apps/hello_world/.
Writing a Python app
import akira
def main():
akira.print("Hello from Python WASM!")
akira.display_clear(akira.COLOR_BLACK)
akira.display_text(10, 10, "Hello Python!", akira.COLOR_WHITE)
akira.display_flush()
main()
Building
# Direct (from app directory)
python3 AkiraSDK/scripts/py_to_wasm.py main.py \
--manifest manifest.json -o my_app.wasm
# Via AkiraSDK build script
cd AkiraSDK/wasm_apps
./build.sh python/my_app
# Via make
cd AkiraSDK/wasm_apps
make python/my_app
make build-python # all Python apps
manifest.json for Python apps
{
"name": "my_app",
"version": "1.0.0",
"capabilities": ["input.read"],
"memory_quota": 262144
}
Python apps need 256 KB (
262144) for the MicroPython heap.
Manifest File
Create manifest.json alongside your app:
{
"name": "my_app",
"version": "1.0.0",
"capabilities": ["display.write"],
"memory_quota": 65536
}
Embedding Manifest:
The AkiraSDK build script automatically embeds the manifest as a custom section:
# Done automatically by build.sh if manifest.json exists
python3 AkiraSDK/scripts/embed_manifest.py app.wasm manifest.json app.wasm
This allows the runtime to extract capabilities without requiring a separate JSON file.
Build Output
AkiraSDK/wasm_apps/bin/
├── app.wasm # WASM bytecode (2-10 KB, universal)
├── app.json # Standalone manifest
├── app-xtensa.aot # AOT for ESP32-S3 (10-50 KB)
├── app-thumb.aot # AOT for nRF54L15
├── app-riscv32.aot # AOT for ESP32-C3
└── app-x86_64.aot # AOT for native_sim
Memory Configuration
WASM apps have two separate stack limits:
1. WASM Linear Memory Stack: 4KB (set by -z stack-size=4096)
- Used for WASM local variables and call frames
- Overflow causes WASM trap
2. Zephyr Thread Stack: 8KB (CONFIG_AKIRA_WASM_APP_STACK_SIZE=8192)
- Used for native API calls (C call chain)
- Handles host function execution
Heap Memory:
- Initial/max: 64KB (65536 bytes)
- Cannot grow beyond limit without rebuilding
Custom Memory Sizes:
clang \
-z stack-size=8192 \
-Wl,--initial-memory=131072 \
-Wl,--max-memory=131072 \
...
Optimization
Optimization Levels
# Balanced (default in build.sh)
clang -O2 -Wl,--strip-all ...
# Size-optimized (smallest binary)
clang -Oz -Wl,--strip-all ...
# Speed-optimized (faster execution)
clang -O3 ...
AOT Optimization:
# Default (balanced)
wamrc --opt-level=3 --size-level=1 -o app.aot app.wasm
# Maximum speed (larger binary)
wamrc --opt-level=3 --size-level=0 -o app.aot app.wasm
# Maximum size reduction
wamrc --opt-level=0 --size-level=3 -o app.aot app.wasm
AOT Compilation
For maximum performance, compile your .wasm to native machine code using WAMR’s AOT compiler.
Setup wamrc
Build wamrc from the WAMR submodule already included in AkiraOS (required — the AkiraOS fork includes Xtensa backend support):
cd ~/akira-workspace/AkiraOS/modules/wasm-micro-runtime/wamr-compiler
cmake . -DWAMR_BUILD_PLATFORM=linux
make
sudo cp wamrc /usr/local/bin/
# or: export WAMRC=$PWD/wamrc
Compile with the AkiraSDK build script (recommended)
cd ~/akira-workspace/AkiraOS/AkiraSDK/wasm_apps
./build.sh # build .wasm
./build.sh aot # AOT for ESP32-S3 (xtensa, default)
./build.sh aot thumb # AOT for nRF54L15 (Cortex-M33)
./build.sh aot thumbv7em # AOT for STM32 (Cortex-M7)
./build.sh aot riscv32 # AOT for ESP32-C3 (RISC-V)
# Output: bin/<app>-<target>.aot
Or invoke wamrc directly
# ESP32-S3 (Xtensa LX7)
wamrc --target=xtensa --cpu=esp32s3 -o app-xtensa.aot app.wasm
# nRF54L15 (Cortex-M33)
wamrc --target=thumb --cpu=cortex-m33 -o app-thumb.aot app.wasm
# ESP32-C3 (RISC-V 32)
wamrc --target=riscv32 -o app-riscv32.aot app.wasm
# Native simulation (x86-64)
wamrc --target=x86_64 -o app-x86_64.aot app.wasm
Performance Gain: 10-50x faster execution compared to interpreter mode.
Tradeoff: Architecture-specific binaries (one .aot per platform).
See AOT Compilation Architecture for detailed guide.
Deployment
Upload via HTTP
The device provides two upload endpoints:
# Simple file upload (saved to /lfs/apps/ or /SD:/apps/)
curl -X POST -F "file=@app.wasm" http://192.168.1.100/upload
# Named app installation (recommended)
curl -X POST \
-F "file=@app.wasm" \
"http://192.168.1.100/api/apps/install?name=myapp"
# Hybrid deployment with AOT (best performance)
curl -X POST -F "file=@app.wasm" \
"http://192.168.1.100/api/apps/install?name=myapp"
curl -X POST -F "file=@app-xtensa.aot" \
"http://192.168.1.100/api/apps/install?name=myapp"
Limits: Max file size is CONFIG_AKIRA_APP_MAX_SIZE_KB (default: 1MB per app).
Hybrid Mode: Runtime automatically uses .aot if available for the current architecture, falls back to .wasm otherwise.
SD Card Installation
# Copy apps to SD card
cp bin/*.wasm /media/SD_CARD/apps/
cp bin/*.json /media/SD_CARD/apps/
# Insert SD → apps auto-scanned on boot
USB Storage Installation
# Copy to USB drive
cp bin/*.wasm /media/USB_DRIVE/apps/
# Connect USB → scan from shell: app scan usb
Shell Commands
# View installed apps
AkiraOS:~$ app list
# Install from filesystem
AkiraOS:~$ app install /lfs/apps/myapp.wasm
# Start app
AkiraOS:~$ app start myapp
# View running apps
AkiraOS:~$ app status
# Stop app
AkiraOS:~$ app stop myapp