Skip to content

feat: FreeRTOS port, with ESP-IDF as its first platform - #13

Merged
reubenmiller merged 4 commits into
mainfrom
feat/freertos-port
Nov 11, 2026
Merged

reubenmiller merged 4 commits into
mainfrom
feat/freertos-port

Conversation

@reubenmiller

@reubenmiller reubenmiller commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A FreeRTOS port of tiny-edge, so ESP-IDF applications can use the same client as the Zephyr and POSIX ports: the same core, the same options, and the same behavior in Cumulocity.

About 93% of the ESP-IDF build is shared code:

Layer Directory Used by Lines
The client core/ Every port ~9,340
Sockets with mbedTLS 4, device key ports/common/ (new) POSIX and ESP-IDF 767
FreeRTOS kernel services ports/freertos/src/ Every FreeRTOS platform 446
ESP-IDF services ports/freertos/esp-idf/tiny_edge/src/ ESP-IDF 704

Commits

  1. refactor: share the POSIX sockets, TLS, and device key in ports/common. The POSIX port shrinks by about 790 lines and keeps only its platform hooks (interface queries, key file, socket options). scripts/check-portability.py allows ports to use ports/common/, and holds it to the core's rules.
  2. feat: the FreeRTOS port.
    • Kernel layer: static tasks, mutexes, queues, event groups, one-shot timers, and the client's heap as an allowance on pvPortMalloc. It uses only the FreeRTOS API, so another FreeRTOS platform (an STM32 with lwIP, for example) can reuse it.
    • The ESP-IDF component tiny_edge: NVS settings, esp_netif, SNTP, esp_ota with bootloader rollback, and ESP_LOG with log upload. Every option is a CONFIG_TEDGE_* Kconfig option.
    • samples/minimal: joins Wi-Fi, logs the registration URL, and publishes a measurement.
    • CI (.github/workflows/freertos.yml): ESP-IDF 6.1 builds for the ESP32-C6, ESP32-S3, and ESP32 with every feature, and an ESP32-C6 build with a reduced set.
    • Core: PRId32/PRIu32 in two log lines, because int32_t is long on the ESP-IDF toolchains.

Testing

  • POSIX with -Werror: 3/3 tests pass, and the core still compiles without an RTOS.
  • ESP-IDF 6.1 builds without warnings. The sample with every feature is 1.06 MB on the ESP32-C6, 61% of a 1.75 MB OTA slot.
  • On an ESP32-C6 against a Cumulocity tenant, all of these worked:
    • certificate enrollment from the registration URL
    • the MQTT connection
    • measurements and the health measurement
    • log upload
    • a remote access tunnel
  • The board testing found two problems, both fixed here:
    • Stack overflow: the log upload thread used 8.1 KB of stack with ESP-IDF's mbedTLS and overflowed its 8 KB stack. Threads that run their own TLS handshake now default to 12 KB, and log at debug level how much of their stack they never used.
    • Cut-off log lines: a log line longer than the 127-byte upload buffer lost its line break, so the next line ran into it. Cut lines now end with "..." and keep their line break.
  • Also on the C6:
    • parameter changes, including a refused invalid value, kept across a reboot
    • a firmware update from the tenant's firmware repository: 1.1 MB downloaded in 16 s, rebooted into the other slot, and confirmed once connected
    • installing the version already running is refused
    • cloud shell commands: help, free, version, and uptime returned their output, and restart (not on the allow-list) was refused
    • firmware rollback: an image that never connected was reset after its 60 s confirmation deadline, the bootloader went back to the previous image, and that image reported the update as failed ("was rolled back")
  • Later commits add cloud shell commands (c8y_Command) with esp_console (see the README's "Shell commands"; the command thread used 1.2 KB of stack, and defaults to 4 KB), and let the sample build images without Wi-Fi settings: they use the network stored on the device, so an image uploaded to Cumulocity carries no Wi-Fi password.

Not in this port yet

Crash dumps, the provisioning library (Improv), and plain FreeRTOS builds outside ESP-IDF.

Related

#12 reports an operation interrupted by a reset as failed. This PR's README lists that behavior as tested on the C6, so merge #12 first.

🤖 Generated with Claude Code

reubenmiller and others added 4 commits November 11, 2026 19:28
The connections and TLS of <tedge/port/net.h>, and the device key and CSR
of <tedge/port/crypto.h>, only need BSD sockets and mbedTLS 4. They move
from the POSIX port to ports/common/, so the ESP-IDF port can use them too.
The POSIX port keeps the interface queries, the key file, and its socket
options, through the hooks in tedge_common.h.

The CA bundle generation moves to ports/common/cmake/tedge_ca_bundle.cmake.
check-portability.py lets ports use ports/common, and holds ports/common to
the same rules as the core.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ports/freertos/ holds the kernel services on the FreeRTOS API (static
tasks, mutexes, queues, event groups, timers), which any FreeRTOS platform
can reuse. ports/freertos/esp-idf/tiny_edge/ is an ESP-IDF component that
adds NVS settings, esp_netif, SNTP, OTA with rollback, and ESP_LOG, and
uses ports/common/ for sockets, TLS, and the device key. Every option is
in Kconfig as CONFIG_TEDGE_*.

Threads that run a TLS handshake of their own get a 12 KB stack: the log
upload used 8.1 KB on an ESP32-C6 and overflowed 8 KB. Log lines longer
than the 127-byte upload buffer are cut with "..." and keep their line
break.

The samples/minimal application joins Wi-Fi, logs the registration URL,
and publishes a measurement. A CI workflow builds it for the ESP32-C6,
ESP32-S3, and ESP32. Core printf formats use PRId32/PRIu32, because
int32_t is long on the ESP-IDF toolchains.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
An image built without a Wi-Fi network uses the one ESP-IDF stored on the
device, so a firmware image uploaded to Cumulocity carries no Wi-Fi
password. The README lists what was tested on an ESP32-C6: parameter
changes and a firmware update, besides what was there before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The ESP-IDF port runs an allow-listed esp_console command that the
application registered, on a thread of its own, and reports what it
printed. While a command runs, stdout keeps only what the command's task
prints and passes everything else to the console. The allow-list, the
"help" answer, and the timeout are the core's, as on Zephyr; the default
allow-list is empty, so nothing runs until the integrator names commands.

The sample registers free, version, and uptime, and allows those three.
The command thread's stack defaults to 4 KB: the sample's commands used
1.2 KB on an ESP32-C6.

The README lists what was tested on an ESP32-C6 (shell commands, and a
firmware rollback), and that debug lines also need
CONFIG_LOG_MAXIMUM_LEVEL_DEBUG.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@reubenmiller
reubenmiller merged commit 57be8e1 into main Nov 11, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant