Overview
Kryon runs natively on ESP32 microcontrollers via the kryesp implementation. This provides a complete UI runtime with CoAP server, WiFi connectivity, and LCD display support.
Features
- Native ESP-IDF implementation
- ILI9341 LCD support (240x320, SPI)
- CoAP server via libcoap
- WiFi connectivity (Station mode)
- FreeRTOS threading
- Thread-safe VFS updates
Hardware Requirements
Supported Boards
- ESP32-S3: Recommended - More RAM, faster
- ESP32-C3: Supported - Lower cost, less RAM
- ESP32 (original): Supported
Minimum Specifications
- Flash: 4MB+
- RAM: 400KB+ free
- WiFi required for CoAP
Pin Configuration
Default pin configuration for ILI9341 LCD:
| Function | Pin | Description |
|---|---|---|
| LCD_SCLK | GPIO 14 | SPI Clock |
| LCD_MOSI | GPIO 13 | SPI Data |
| LCD_MISO | GPIO 12 | SPI MISO (not used for display) |
| LCD_CS | GPIO 15 | Chip Select |
| LCD_DC | GPIO 2 | Data/Command |
| LCD_RST | -1 | Reset (use shared reset) |
| LCD_BCKL | GPIO 27 | Backlight (PWM) |
Modifying Pins
Edit hardware.h to change pin assignments:
#define LCD_SCLK_PIN 14
#define LCD_MOSI_PIN 13
#define LCD_MISO_PIN 12
#define LCD_CS_PIN 15
#define LCD_DC_PIN 2
#define LCD_BCKL_PIN 27
Display Support
ILI9341 (Recommended)
- Resolution: 240x320
- Interface: SPI
- Color: RGB565 (16-bit)
- Touch: Not supported (planned)
Other Displays
Other SPI displays can be added by implementing the ESP LCD panel interface.
Building
Prerequisites
# Install ESP-IDF
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html
# Set up environment
. ~/esp/esp-idf/export.sh
Build
cd kryesp
idf.py build
Flash
idf.py -p /dev/ttyUSB0 flash
Monitor
idf.py -p /dev/ttyUSB0 monitor
WiFi Configuration
Method 1: Hardcoded (Development)
Edit main/main.c:
#define WIFI_SSID "YourNetwork"
#define WIFI_PASS "YourPassword"
Method 2: NVS (Production)
# Set WiFi via NVS
esptool.py --port /dev/ttyUSB0 write_nv_config --ssid "YourNetwork" --pass "YourPassword"
WiFi Events
- On connect: CoAP server starts
- On disconnect: CoAP server stops
- Auto-reconnect enabled
CoAP Server
Default Configuration
- Port: 5683 (standard CoAP)
- Protocol: UDP
- Security: None (add DTLS for production)
Endpoints
See CoAP documentation for endpoint format.
Example Usage
# Read circle radius
coap-client get coap://esp32.local/0/radius
# Set circle radius to 100
echo -n -e "\x00\x64" | coap-client put coap://esp32.local/0/radius
Loading .krb Files
Method 1: SPIFFS (Recommended)
# Format SPIFFS (first time only)
idf.py erase-flash
idf.py flash
# Upload .krb file
esptool.py --port /dev/ttyUSB0 write_spi_flash 0x300000 ui.krb
# Or use SPIFFS upload tool
idf.py spi-flash
Method 2: Embedded
Embed .krb as C array:
# Convert .krb to C header
xxd -i ui.krb > ui_krb.h
# In main.c
extern const unsigned char ui_krb[];
extern const unsigned int ui_krb_len;
load_krb_from_memory(ui_krb, ui_krb_len);
Method 3: HTTP Download
Download .krb from URL on boot:
esp_http_client_config_t config = {
.url = "http://server.com/ui.krb",
};
esp_http_client_handle_t client = esp_http_client_init(&config);
// Download and load...
Architecture
Thread Layout
- Main Task: UI rendering and event handling
- CoAP Task: Network request handling
- FreeRTOS Queue: Thread-safe UI updates
Memory Layout
| Component | ESP32-S3 | ESP32-C3 |
|---|---|---|
| FreeRTOS Queue | 512 bytes | 512 bytes |
| VFS Table | ~1KB | ~1KB |
| CoAP Context | ~8KB | ~8KB |
| Network Buffers | ~4KB | ~4KB |
| Total Overhead | ~13.5KB | ~13.5KB |
Troubleshooting
Display Not Working
- Check SPI pin connections
- Verify 3.3V power supply
- Test with known-good display
- Check for backlight (screen may be on but dark)
WiFi Not Connecting
- Verify SSID and password
- Check 2.4GHz only (ESP32 doesn't support 5GHz)
- Verify antenna connection
- Check router logs
CoAP Not Responding
- Confirm device has IP address
- Check port 5683 is not blocked
- Try from same network first
- Check firewall settings
Out of Memory
- Reduce MAX_COAP_RESOURCES in VFSRegistry.h
- Use smaller .krb files
- Reduce CoAP task stack size
Example Projects
Basic Circle
const meta {
title "Circle"
}
var env {
w 240
h 320
}
def window {
write /dev/wctl [ new 0 0 %size[0] %size[1] %title ]
}
def circle {
write /dev/draw [ circ %center[0] %center[1] ./radius %color ]
}
window {
title /meta/title
size [ /env/w /env/h ]
circle {
center [ 120 160 ]
radius 50
color 0xFF0000
}
}
Animated Circle
const meta {
title "Animated"
}
var env {
w 240
h 320
}
def circle {
write /dev/draw [ circ %center[0] %center[1] ./radius %color ]
%logic
}
window {
title /meta/title
speed 50
size [ /env/w /env/h ]
circle {
center [ 120 160 ]
radius 40
color 0xFF0000
dir 1
logic {
listen /dev/timer {
tick ../speed {
write ./radius [ + ./radius ./dir ]
if [ >= ./radius 60 ] { write ./dir -1 }
if [ <= ./radius 40 ] { write ./dir 1 }
}
}
}
}
}
Control from Python
from aiocoap import *
async def animate():
ctx = await Context.create_client_context()
# Set radius to 100
payload = (100).to_bytes(2, 'big')
req = Message(code=PUT, uri="coap://esp32.local/0/radius", payload=payload)
await ctx.request(req).response
# Read radius
req = Message(code=GET, uri="coap://esp32.local/0/radius")
resp = await ctx.request(req).response
value = int.from_bytes(resp.payload, 'big')
print(f"Radius: {value}")
asyncio.run(animate())
Production Checklist
- ✔ Enable DTLS for security
- ✔ Implement IP whitelist
- ✔ Use NVS for WiFi credentials
- ✔ Add error handling for display
- ✔ Implement watchdog timer
- ✔ Add OTA updates
- ✔ Test power consumption
- ✔ Verify CoAP reliability