Skip to content

Quickstart with ESP-IDF

The kit is an ESP-IDF component. It needs ESP-IDF 5.1 or later and an ESP32 with Bluetooth LE.

Using an AI coding agent?

Fill these in, copy the prompt, and paste it into your agent (Claude Code, Cursor, Copilot) at the root of your firmware project. It follows the steps below and shows you the diff.

Prompt · ESP-IDF
Add Device Configs Wi-Fi setup to this ESP-IDF project.

Device Configs lets this device's owner put it on Wi-Fi from the Device Configs phone app: they scan the QR label on the unit, the app connects to that unit over Bluetooth LE with an encrypted session (Espressif protocomm security 2, a setup code per unit), and sends the Wi-Fi network, a token and a label. The device then says whether it joined, or why not. The firmware side is the "deviceconfigs" ESP-IDF component.

Product:
- Product id: <your product id>
- Bluetooth name prefix: <YOUR-PREFIX> (each unit advertises <YOUR-PREFIX>-<serial>)
- The kit (component folder): <path to the deviceconfigs kit folder>

Steps:
1. Check the project uses ESP-IDF 5.1 or later on an ESP32 chip with Bluetooth LE. If it doesn't, stop and tell me.
2. Make the component available: in the top-level CMakeLists.txt, before include($ENV{IDF_PATH}/tools/cmake/project.cmake), add
   list(APPEND EXTRA_COMPONENT_DIRS "<path to the deviceconfigs kit folder>")
   or copy the folder into the project's components/ folder. Add deviceconfigs to PRIV_REQUIRES of the component that holds app_main.
3. Add these to sdkconfig.defaults, keeping what is there (use NimBLE unless the project already uses Bluedroid):
   CONFIG_BT_ENABLED=y
   CONFIG_BT_NIMBLE_ENABLED=y
   CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=n
   CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=n
   CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
   Then delete sdkconfig (or set them in idf.py menuconfig) so they take effect.
4. Add a partition named dc_factory (type data, subtype nvs, size 0x3000) to the partition table. Keep every existing partition and keep the offsets valid; if the project uses a built-in table, switch to a custom partitions.csv (CONFIG_PARTITION_TABLE_CUSTOM=y).
5. In app_main: initialise NVS (erase and retry on ESP_ERR_NVS_NO_FREE_PAGES or ESP_ERR_NVS_NEW_VERSION_FOUND), call esp_netif_init() and esp_event_loop_create_default(), then
   #include "deviceconfigs.h"
   dc_config_t config = { .name_prefix = "<YOUR-PREFIX>", .product = "<your product id>" };
   ESP_ERROR_CHECK(dc_start(&config));
   dc_start() brings Wi-Fi up and keeps the device connected, so remove the project's own Wi-Fi setup: esp_wifi_init, hard-coded SSIDs and passwords, wifi_provisioning, SmartConfig. Wait for the network with dc_wait_online(portMAX_DELAY) before network work. Only if the project must keep owning Wi-Fi, set .app_owns_wifi = true and keep its reconnect logic (never call esp_wifi_init twice).
6. Where the device talks to its server, read the token with dc_get_token() and send it as the device's credential (for example an Authorization header). dc_get_label() is the name the owner gave the device.
7. If the device has an LED or a button: register a handler for DC_EVENT (DC_EVENT_SETUP_OPEN, DC_EVENT_WIFI_REJECTED, DC_EVENT_SETUP_DONE, DC_EVENT_ONLINE, DC_EVENT_OFFLINE) to show setup state, call dc_setup_open() on a press to re-run setup, and dc_factory_reset() on a long press.
8. Build with idf.py build and fix any errors.

Rules:
- Never log or print the Wi-Fi password, the token or a setup code.
- Don't add hard-coded Wi-Fi credentials or any shared default password.
- Don't change code unrelated to Wi-Fi setup.
- When you're done, show me the diff, where the dc_factory partition is (its offset), and the command that gives a unit its identity:
  python <Device Configs firmware folder>/tools/dc_factory.py --product <your product id> --partitions partitions.csv --port <serial port>

Reference: https://deviceconfigs.com/docs/quickstart/esp-idf/ (every docs page as plain text: https://deviceconfigs.com/llms-full.txt)
  1. Add the component. Copy firmware/components/deviceconfigs into your project’s components/ folder, or point to it from your top-level CMakeLists.txt:

    set(EXTRA_COMPONENT_DIRS path/to/firmware/components)
  2. Turn on Bluetooth LE and security 2. Add these to sdkconfig.defaults:

    CONFIG_BT_ENABLED=y
    CONFIG_BT_NIMBLE_ENABLED=y
    CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=n
    CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=n
    CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
  3. Make room for the unit’s identity. Add a 12 KB dc_factory partition to your partition table:

    # Name, Type, SubType, Offset, Size
    nvs, data, nvs, 0x9000, 0x6000,
    phy_init, data, phy, 0xf000, 0x1000,
    dc_factory, data, nvs, 0x10000, 0x3000,
    factory, app, factory, 0x20000, 0x1E0000,
  4. Start it. After the usual NVS, network and event loop setup:

    #include "deviceconfigs.h"
    dc_config_t config = { .name_prefix = "ACME-AIR", .product = "airmon-2" };
    ESP_ERROR_CHECK(dc_start(&config));
    dc_wait_online(portMAX_DELAY);

    name_prefix is your product’s Bluetooth prefix; the unit advertises ACME-AIR-<serial>. The kit brings Wi-Fi up, opens setup when the device has no network yet, and keeps it online after.

  5. Label a unit and flash. See Label each unit, then idf.py flash monitor.

char token[1025];
size_t len = sizeof(token);
if (dc_get_token(token, &len) == ESP_OK) {
// send it to your service: it says which product and unit this is
}
char label[33];
dc_get_label(label, sizeof(label)); // what the owner named it, e.g. "Kitchen"

The kit posts events on the default event loop (DC_EVENT): setup open, token stored, settings refused (wrong password or network not found), setup done, online, offline.

Field in dc_config_t Default What it does
setup_after_boot_s 0 Also open setup for this many seconds after each power-on on a device already set up.
keep_bluetooth false Keep Bluetooth’s memory after setup, for a product that uses Bluetooth itself.
app_owns_wifi false Your application brings Wi-Fi up and reconnects it.

For a button: dc_setup_open() restarts into setup, and dc_factory_reset() forgets the network, token and label. The setup code never changes.