Examples¶
Every example is a complete ESP-IDF project of its own under
components/<component>/examples/<name>/, built the same way as any firmware
on espOS: it includes cmake/espos_project.cmake, calls
espos_project_prologue() and espos_project_ui_partition(), and its
main.c boots with espos_start(). What each one adds on top is the point
of the example and fits in about a hundred lines. Each README says what the
example does, what it needs wired, what appears in Signal K, and — for
readers coming from SensESP — which SensESP example it replaces, and carries
a label: Essential (read these first), Newbie (one concept, no
surprises) or Advanced.
The index in the repository is
examples/README.md;
the table below is the same list with the page it pairs with on this site.
Build any of them¶
cd components/espos_sk/examples/analog_input # or any other example directory
idf.py set-target esp32c6 # esp32 / esp32s3 / esp32c3 / esp32c6 / esp32c5 / esp32p4
idf.py build flash monitor
Every example builds for esp32c6; the ones that drive a peripheral also
build for esp32p4, with the pins as #defines at the top of main.c. On
a small or shared machine use the locked wrapper instead of a bare build:
/path/to/espOS/scripts/build.sh build from inside the example directory
(Development). The first configure generates a
development signing key with a warning; that is expected
(OTA → Signing key).
from_registry is the exception, deliberately: it has no prologue, so nothing
generates a key for it and the build stops until you run
espsecure generate-signing-key --version 2 --scheme rsa3072 secure_boot_signing_key.pem
once, BEFORE the first configure. A key
invented by a build step is a key nobody kept, and a registry consumer's
project is not espOS's to put one in — see
its README.
The examples¶
| Example | Component | What it shows | Read with |
|---|---|---|---|
minimal |
espos_core |
Essential. The whole of an espOS application: espos_start(NULL), then environment.inside.temperature once a second, a constant (293.65 K) standing in for the sensor. No wiring. Replaces SensESP's minimal_app and constant_sensor. The getting started target. |
Concepts |
from_registry |
espos_core |
Essential. The same application as minimal, built the way a project outside this repository builds it: espOS from the component registry, a plain IDF root CMakeLists.txt, its own partition table and sdkconfig. Start your own firmware from this one. |
Releasing |
two_phase_boot |
espos_core |
Advanced. espos_init() and espos_start_network() instead of one call, with the application's own work between them, and the pattern for a blocking espOS call — a worker task fed through a queue. Replaces freertos_tasks. |
Concepts → espos_start(), Threading contracts |
custom_settings |
espos_config |
Newbie. An application's own settings: declared once in main/config/app.json, used through generated key constants, applied live from the web UI, carried across a renamed key by a migration. Replaces SensESP's ConfigItem. |
Configuration store, Add a setting |
app_endpoint_and_page |
espos_httpd |
Advanced. A firmware's own REST endpoints on the espOS server, a live value on the SSE stream the web UI already listens to, and a page of your own in that UI. Replaces SensESP's frontend plugins. | REST API, Web UI, An app endpoint and a UI tab |
health_and_led |
espos_health |
Newbie. A status LED driven from the device's health table, and an application condition the watchdog policy acts on — and what it never restarts for. Replaces SensESP's SystemStatusLed. |
Device health |
analog_input |
espos_sk |
Essential. One ADC pin read every second — factory-calibrated, averaged, scaled by two settings from the web UI — published to a Signal K path: the shape of every "sensor on a wire" device. Replaces SensESP's analog_input, repeat_sensor_analog_input and its tank-level tutorial. |
Your first sensor, Tank level |
pulse_counter |
espos_sk |
Newbie. Pulses on a GPIO counted by the PCNT peripheral with its glitch filter, published as revolutions per second, with a running total kept in the config store across reboots — engine RPM, shaft speed, chain or flow. Replaces rpm_counter, pcnt_rpm_counter, chain_counter, time_counter. |
Signal K → Delta stream |
digital_switch |
espos_sk |
Newbie. A GPIO output whose state is a Signal K switch path, toggled by a debounced button; published on change and every 10 s so a late dashboard still shows the truth. Replaces SensESP's smart_switch. |
Signal K → Inbound |
listener_relay |
espos_sk |
Newbie. A relay driven by a value the server streams: subscribe to environment.outside.illuminance, switch on below 50 lux and off above 100 (hysteresis), publish the relay's state back. Replaces SensESP's listener. |
Concepts → Threading contracts |
json_and_meta |
espos_sk |
Advanced. The three calls a plain number does not cover, framed as a windlass controller: espos_sk_publish_json() for an object, espos_sk_declare_meta() for a path only this device knows, espos_sk_notify() for a condition of the device. No hardware. Replaces raw_json and the metadata example. |
Signal K → Delta stream, Device health |
tls_server |
espos_sk |
Advanced. The Signal K connection over https and wss: CONFIG_ESPOS_SK_TLS in the build, sk.scheme defaults to auto and sk.tls_trust to tofu, so a self-signed boat server is pinned on first use and works with no configuration |
Signal K → TLS, Security |
sensor_graph |
espos_flow |
Newbie. The whole of a sensor firmware as four lines of wiring, in C++: a poll, a calibration, a Signal K sink, connected with >>. The same device as analog_input above, which is not obsolete — the graph is sugar over exactly those calls. A fake reading keeps it about the wiring and lets it build for every target. |
Data flow, Migrating from SensESP |
dusk_relay |
espos_sk_flow |
Newbie. listener_relay as a graph: a value the server streams, through a hysteresis node, to a relay, published back. The queue and the worker task the C version needs are gone — Listener posts into a mailbox and the flow loop is the worker. |
Data flow, Transforms and formulas |
smart_switch |
espos_sk_flow |
Advanced. A relay a phone can switch and a button that switches it back: the example that could not be written before inbound PUT existed. Shows why a device must publish a path before the server will route a PUT to it, and the difference between a spec path and one of your own. | Signal K → Inbound PUT, Sensors |
ble_gateway |
espos_ble |
Advanced. A BLE-to-Signal K gateway, and a whole firmware in one call: advertisements batched to signalk-server's BLE provider API, GATT sessions driven by the server over a control WebSocket. The gateway decodes nothing — what a device is is decided by bt-sensors-plugin-sk on the server. Was the separate espos-ble-gateway repository until it became twenty lines; what is worth reading is its two sdkconfig fragments. |
BLE gateway |
n2k_candump |
espos_n2k |
Advanced. An NMEA 2000 gateway: CAN frames off the bus, out over TCP as candump ASCII for canboatjs. Decodes nothing on the device — PGN decoding changes more often than firmware should. Read it for the wiring, which is what actually goes wrong: a transceiver and termination are not optional, and both fail looking exactly like a software fault. GET /api/v1/n2k tells a quiet bus from a misconfigured one. |
N2K gateway |
ethernet |
espos_eth |
Newbie. An espOS device on a cable, for the Waveshare ESP32-P4 PoE board: the whole firmware is espos_start(), WiFi is off, and SignalK, OTA and the web UI run over wired Ethernet. Read it for the power caution — USB and PoE never at once — and for checking the route over the network, since the serial console is out of reach once the board is on PoE. |
Network |
ble_provisioning |
espos_prov |
Advanced. WiFi credentials handed to a sealed device over BLE, with no access point and no captive portal — for the cases the setup portal cannot serve. Ships the Python client that speaks the protocol, because Espressif's phone app does not: espos_prov uses protocomm as a transport only, so espOS's own WiFi state machine stays the one thing that owns the radio. Read the client's srp6a.py for the handshake, where ESP-IDF's own header comments are wrong. |
BLE provisioning |
duty_cycle |
espos_power |
Advanced. A device on a battery: wake, publish two readings, flush the stream, deep-sleep, again. The code is a few lines; read it for the rules that keep a mostly-sleeping device reachable — off until you turn it on, an awake window after every power-on or update, and no sleep while an update is unconfirmed, because every wake is a boot and would roll it back. | Power |
The reference application in main/ at the repository root is not an
example but the app espOS's own CI builds on every target; it exercises every
descriptor type and every optional component, which is why it is larger than
any example.
Where to go from an example¶
- Something in the runtime behaves unexpectedly: Troubleshooting.
- The example does nearly what you want: the tutorials walk from an example to a device of your own, one addition at a time.
- You have a SensESP project: Migrating from SensESP maps its concepts onto these examples.