Kryon / Documentation / CoAP Protocol

CoAP Protocol

Constrained Application Protocol for Kryon network control

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 device
  • port — 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

PathTypeDescription
/env/wvarEnvironment width value
/env/hvarEnvironment height value
/meta/titleconstMetadata title (read-only)
/0/radiusdef instanceFirst instance's radius
/0/colordef instanceFirst instance's color
/1/posdef instanceSecond 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.

TypeFormatExample (value=100)
Integeru16 big-endian[0x00][0x64]
Array (2D)u16 + u16 BE[0x00][0x64][0x00][0x3C]
StringUTF-8"Title"
Coloru32 hex0xFF0000

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.

CodeNameMeaning
2.04ChangedPUT successful
2.05ContentGET successful with payload
4.00Bad RequestMalformed request
4.03ForbiddenPath is const (read-only)
4.04Not FoundPath doesn't exist
4.15Unsupported ContentInvalid payload format
5.00Internal Server ErrorServer 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",
;rt="property";if="rw",
;rt="property";if="rw"

Performance

CoAP is designed for efficiency on constrained networks.

MetricTypical 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