Overview
CoAP (Constrained Application Protocol, RFC 7252) is the default network protocol for Kryon. Designed for constrained devices like ESP32, it offers UDP-based lightweight communication with REST-like semantics.
Why CoAP?
- Designed for constrained devices: ESP32, Arduino, and other microcontrollers
- UDP-based: Low overhead, no connection setup
- Binary payloads: Under 20 bytes typical for property operations
- REST-like: GET/PUT model familiar to web developers
- Standard protocol: RFC 7252, widely supported
- Optional security: DTLS for encrypted communication
Endpoint Format
Kryon exposes VFS paths as CoAP resources.
URI Format
coap://[host]:[port][vfs_path]
host— IP address or hostname of the deviceport— CoAP port (default: 5683)vfs_path— Full VFS path (e.g.,/env/w,/0/radius)
Example Endpoints
coap://192.168.1.100:5683/env/w
coap://192.168.1.100:5683/0/radius
coap://192.168.1.100:5683/meta/title
VFS Path Examples
| Path | Type | Description |
|---|---|---|
/env/w | var | Environment width value |
/env/h | var | Environment height value |
/meta/title | const | Metadata title (read-only) |
/0/radius | def instance | First instance's radius |
/0/color | def instance | First instance's color |
/1/pos | def instance | Second instance's position |
Operations
GET (Read Value)
Read the current value of a VFS path.
coap-client get coap://192.168.1.100/env/w
Response: Value in binary format
0x00C8 // 200 in big-endian
PUT (Write Value)
Set a new value for a var or def instance property.
# Set env/w to 240 (0x00F0)
echo -n -e "\x00\xF0" | coap-client put coap://192.168.1.100/env/w
# Set 0/radius to 100 (0x0064)
echo -n -e "\x00\x64" | coap-client put coap://192.168.1.100/0/radius
Response: 2.04 Changed (success) or error code
Writing to const
Attempting to write to const values returns 4.03 Forbidden.
echo -n "New Title" | coap-client put coap://192.168.1.100/meta/title
# Response: 4.03 Forbidden
DELETE (Not Implemented)
DELETE is not supported. VFS entries cannot be deleted at runtime.
Payload Encoding
Values are encoded as binary payloads.
| Type | Format | Example (value=100) |
|---|---|---|
| Integer | u16 big-endian | [0x00][0x64] |
| Array (2D) | u16 + u16 BE | [0x00][0x64][0x00][0x3C] |
| String | UTF-8 | "Title" |
| Color | u32 hex | 0xFF0000 |
Encoding Examples
# Integer = 50
echo -n -e "\x00\x32"
# Integer = 100
echo -n -e "\x00\x64"
# Integer = 200
echo -n -e "\x00\xC8"
# String
echo -n "Hello"
# Color (hex)
echo -n -e "\xFF\x00\x00\x00"
Response Codes
CoAP uses numeric response codes.
| Code | Name | Meaning |
|---|---|---|
| 2.04 | Changed | PUT successful |
| 2.05 | Content | GET successful with payload |
| 4.00 | Bad Request | Malformed request |
| 4.03 | Forbidden | Path is const (read-only) |
| 4.04 | Not Found | Path doesn't exist |
| 4.15 | Unsupported Content | Invalid payload format |
| 5.00 | Internal Server Error | Server error |
Client Tools
libcoap (coap-client)
# Install
apt-get install libcoap2-bin
# Read
coap-client get coap://192.168.1.100/env/w
# Write
echo -n -e "\x00\x64" | coap-client put coap://192.168.1.100/0/radius
# With custom port
coap-client get coap://192.168.1.100:5684/env/w
Bash Helper Script
#!/bin/bash
# kry-control.sh
ESP32_IP="192.168.1.100"
PORT=5683
int_to_be16() {
printf "\\x%02x\\x%02x" $(($1 >> 8)) $(($1 & 0xFF))
}
kry_set() {
local path=$1
local value=$2
echo -n "$(int_to_be16 $value)" | \
coap-client put coap://${ESP32_IP}:${PORT}${path}
}
# Examples
kry_set "/env/w" 240
kry_set "/0/radius" 100
Python (aiocoap)
import asyncio
from aiocoap import *
async def set_value(host, path, value):
context = await Context.create_client_context()
uri = f"coap://{host}{path}"
payload = value.to_bytes(2, 'big') # u16 big-endian
request = Message(code=PUT, uri=uri, payload=payload)
response = await context.request(request).response
print(f"Response: {response.code}")
async def get_value(host, path):
context = await Context.create_client_context()
uri = f"coap://{host}{path}"
request = Message(code=GET, uri=uri)
response = await context.request(request).response
value = int.from_bytes(response.payload, 'big')
print(f"Value: {value}")
asyncio.run(set_value("192.168.1.100", "/0/radius", 100))
asyncio.run(get_value("192.168.1.100", "/env/w"))
C (Raw Socket)
#include
#include
void kry_set_value(const char* host, const char* path, uint16_t value) {
int sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(5683),
};
inet_pton(AF_INET, host, &addr.sin_addr);
// Build CoAP packet (simplified)
uint8_t pkt[64] = {
0x40, 0x03, 0x12, 0x34, // CoAP header: CON, PUT
0xB1, 'e', 'n', 'v', // Uri-Path: "env"
0xB1, 'w', // Uri-Path: "w"
0xFF, // Payload marker
(value >> 8) & 0xFF, // Value (big-endian)
value & 0xFF
};
int len = 12;
sendto(sock, pkt, len, 0, (struct sockaddr*)&addr, sizeof(addr));
close(sock);
}
Example: Controlling a Circle
.kry Source
var env {
w 200
h 400
}
def circle {
write /dev/draw [ circ %center[0] %center[1] ./radius %color ]
}
circle {
center [ 100 100 ]
radius 50
color 0xFF0000
dir 1
logic {
listen /dev/timer {
tick 50 {
write ./radius [ + ./radius ./dir ]
if [ >= ./radius 60 ] { write ./dir -1 }
if [ <= ./radius 40 ] { write ./dir 1 }
}
}
}
}
CoAP Control
# Read current radius
coap-client get coap://192.168.1.100/0/radius
# Set radius to 100
echo -n -e "\x00\x64" | coap-client put coap://192.168.1.100/0/radius
# Read environment width
coap-client get coap://192.168.1.100/env/w
Security (DTLS)
For production deployments, enable DTLS (CoAP over UDP with TLS).
Pre-Shared Key (PSK)
# Server (ESP32)
coap_new_dtls_context(ctx, COAP_DTLS_ROLE_SERVER, psk_key, psk_identity);
# Client
coap-client -k psk_identity -u psk_key coaps://192.168.1.100/env/w
Best Practices
- Use DTLS in production environments
- Implement IP whitelisting as additional layer
- Rotate PSK keys regularly
- Consider certificate-based auth for larger deployments
Observing (NOTIFY)
CoAP supports observe mode for subscriptions to value changes.
# Subscribe to radius changes
coap-client -o 60 -b 1024 get coap://192.168.1.100/0/radius
The server will send notifications whenever the value changes.
Discovery
Discover available resources using CoAP discovery.
# List all resources
coap-client get coap://192.168.1.100/.well-known/core
Response:
;rt="var";if="rw",
;rt="var";if="rw",
;rt="const";if="r",
0/radius>;rt="property";if="rw",
0/color>;rt="property";if="rw"
Performance
CoAP is designed for efficiency on constrained networks.
| Metric | Typical Value |
|---|---|
| Packet size (read) | ~30 bytes |
| Packet size (write) | ~35 bytes |
| Latency (local) | <10ms |
| Latency (WiFi) | ~20-50ms |
| Max update rate | >100/sec |
Troubleshooting
Connection Refused
- Check device is powered on
- Verify WiFi connection
- Confirm CoAP port (default 5683)
4.04 Not Found
- Verify path is correct
- Check const/var/def exists in source
- Use discovery to list available resources
4.03 Forbidden
- Path is const (read-only)
- Use GET instead of PUT
4.00 Bad Request
- Check payload format matches value type
- Verify endianness (big-endian for u16)
- Ensure payload length is correct