Skip to main content

Migrate from ESP RainMaker Classic

ESP RainMaker Neo mirrors the esp_rmaker_* API namespace, so an existing ESP RainMaker Classic application ports without redesign: the node, its devices and its parameters are described exactly as before. This page is for firmware developers moving such an application, and it covers every difference you have to act on — build files, startup order, renamed symbols, deleted calls, and the Kconfig keys that change meaning.

Work through it end to end and you finish with a native ESP RainMaker Neo project.

What ports unchanged​

Most of the API you call every day keeps its name and signature. These are the categories that survive a port, with an example or two each:

  • Node lifecycle — esp_rmaker_node_init(), esp_rmaker_start(). esp_rmaker_config_t still carries the enable_time_sync field.
  • Data model construction — esp_rmaker_device_create(), esp_rmaker_node_add_device(), esp_rmaker_device_assign_primary_param().
  • Standard device and parameter creators — esp_rmaker_switch_device_create(), esp_rmaker_power_param_create(), esp_rmaker_name_param_create().
  • Value helpers — esp_rmaker_bool(), esp_rmaker_int(), esp_rmaker_str().
  • Parameter updates — esp_rmaker_param_update(), esp_rmaker_param_update_and_report().
  • Standard type macros — ESP_RMAKER_DEVICE_SWITCH, ESP_RMAKER_PARAM_POWER, ESP_RMAKER_UI_TOGGLE.
  • Optional service enables — esp_rmaker_timezone_service_enable(), esp_rmaker_local_ctrl_service_enable().
  • The serial console — esp_rmaker_console_init().

Treat that as the shape of the answer, not the whole of it. The rest of this page lists everything that does change, so a call you do not find here is one to check against esp_rmaker_core.h — the umbrella header is the authority on what the SDK currently exposes.

Device and parameter type strings mostly carry over too, but a handful changed or were dropped. Check yours against Type changes to watch for.

Build files​

Swap the dependency set in main/idf_component.yml:

PreviouslyESP RainMaker Neo
SDKespressif/esp_rainmakeresp_rmaker_neo, esp_rmaker_neo_ota
Provisioningespressif/rmaker_app_networkapp_network
Hold-to-resetespressif/rmaker_app_resetapp_reset
Diagnosticsespressif/rmaker_app_insights— none

The app_* components are example scaffolding rather than SDK API and may change between releases. Start your own project covers whether to copy them into your repository or depend on them in place.

Check your ESP-IDF version first. ESP RainMaker Neo requires ESP-IDF v6.0.2 or later. If your application is on v5.x, follow the ESP-IDF migration guides first and make sure the application builds and runs on v6.0.2 against ESP RainMaker Classic. Only then start on this page; debugging both migrations at once is what makes a port feel impossible.

The entry point is your choice. ESP RainMaker Classic applications define void app_main(void). The ESP RainMaker Neo examples instead define osal_err_t app_run(void) and let the app_entry component own the platform entry point. On ESP-IDF that component does nothing except call app_run() from app_main(), so an ESP-IDF-only port can keep its existing app_main and leave app_entry out. Adopt app_run() only if you want the same sources to build for POSIX.

Keep a factory partition. The node's permanent identity lives in fctry. If you are writing a new partition table rather than copying one, see Factory NVS.

Includes​

The standard headers collapse into one umbrella:

// Previously
#include <esp_rmaker_core.h>
#include <esp_rmaker_standard_types.h>
#include <esp_rmaker_standard_params.h>
#include <esp_rmaker_standard_devices.h>
#include <esp_rmaker_schedule.h>
#include <esp_rmaker_scenes.h>
#include <esp_rmaker_console.h>
#include <esp_rmaker_common_events.h>
#include <app_network.h>
#include <app_insights.h>

// ESP RainMaker Neo
#include <esp_rmaker_core.h> /* node, data model, standard types/params/devices/services */
#include <esp_rmaker_ota.h>
#include <osal_storage.h>
#include <app_network_neo.h>
#include <app_event_loop.h>

esp_rmaker_schedule.h, esp_rmaker_scenes.h and app_insights.h have no counterpart — delete them along with the calls they declared.

Expect to add a few ESP-IDF includes back by hand. The ESP RainMaker Classic headers pulled in more of the framework than the umbrella does, so driver code that compiled on the strength of an indirect include — driver/gpio.h is the usual one — now needs to include it itself.

Startup order​

This is the one structural change, and the only part of a port that is not a rename.

ESP RainMaker Classic starts the agent first and provisions afterwards. ESP RainMaker Neo finishes provisioning before esp_rmaker_node_init(), because provisioning is the most memory-hungry thing the firmware does and the SDK frees those resources before the rest of it comes up:

// Previously — agent first, provisioning last
esp_rmaker_start();
app_network_set_custom_mfg_data(MFG_DATA_DEVICE_TYPE_SWITCH, MFG_DATA_DEVICE_SUBTYPE_SWITCH);
app_network_start(POP_TYPE_RANDOM);

// ESP RainMaker Neo — provisioning first, agent last
app_network_init();
app_network_provision(NEO_MFG_DATA_DEVICE_TYPE_SWITCH, NEO_MFG_DATA_DEVICE_SUBTYPE_SWITCH);
/* node init, devices, parameters, services, OTA */
esp_rmaker_start();

app_network_provision() replaces both of those calls and wraps the whole sequence — esp_rmaker_pre_prov_init(), set manufacturer data, start provisioning, esp_rmaker_pre_prov_deinit(). There is no PoP argument: the PoP type comes from configuration. Code basics explains the four constraints that fix this order.

The ESP RainMaker Classic order fails on an unclaimed node with assisted claiming. esp_rmaker_node_init() reads the MQTT credentials, but claiming writes them only during provisioning.

Storage initialisation collapses at the same time. The nvs_flash_init() block with its ESP_ERR_NVS_NO_FREE_PAGES erase-and-retry becomes one call, which handles the retry internally:

osal_storage_init(NULL);

Accessors and macros​

PreviouslyESP RainMaker Neo
Parameter idesp_rmaker_param_get_name()esp_rmaker_param_get_id()
Device idesp_rmaker_device_get_name()esp_rmaker_device_get_id()
Parameter lookupesp_rmaker_device_get_param_by_name()esp_rmaker_device_get_param_by_id()
Request sourceesp_rmaker_device_cb_src_to_str()esp_rmaker_req_src_to_string()
Parameter id macrosESP_RMAKER_DEF_POWER_NAMEESP_RMAKER_DEF_POWER_ID
Name parameter macroESP_RMAKER_DEF_NAME_PARAMESP_RMAKER_DEF_NAME_PARAM_ID
Manufacturer dataMFG_DATA_DEVICE_TYPE_*NEO_MFG_DATA_DEVICE_TYPE_*
Node readmeesp_rmaker_node_add_readme()— none, delete

These return the parameter or device id, which is distinct from the user-facing display name held in the separate esp.param.name parameter; the accessors and macros were renamed to say so. esp_rmaker_req_src_to_string() is declared in esp_rmaker_state.h, since the request source is a state-level concept rather than a device-level one.

Two details worth checking in your own code. The Name macro is ESP_RMAKER_DEF_NAME_PARAM_ID, not ESP_RMAKER_DEF_NAME_ID — it is the one irregular member of the family. And the family has eleven members in total, so a parameter whose old macro is absent here (ESP_RMAKER_DEF_OTA_STATUS_NAME, for instance) needs a string literal instead.

You can often skip these renames altogether. Dispatching on esp_rmaker_param_get_type() against the type macros is the idiom the SDK examples use, and it does not depend on ids at all.

Write callbacks​

The parameter list is unchanged, and the return type is an alias of esp_err_t on ESP-IDF, so the only edit the compiler forces is the accessor rename. Switching the return type is recommended rather than required — see below:

// Previously
static esp_err_t write_cb(const esp_rmaker_device_t *device, const esp_rmaker_param_t *param,
const esp_rmaker_param_val_t val, void *priv_data,
esp_rmaker_write_ctx_t *ctx)
{
if (strcmp(esp_rmaker_param_get_type(param), ESP_RMAKER_PARAM_POWER) == 0) {
/* … drive the hardware, then esp_rmaker_param_update() … */
}
return ESP_OK;
}

// ESP RainMaker Neo — same parameters and same body
static esp_rmaker_error_t write_cb(const esp_rmaker_device_t *device, const esp_rmaker_param_t *param,
const esp_rmaker_param_val_t val, void *priv_data,
esp_rmaker_write_ctx_t *ctx)
{
if (strcmp(esp_rmaker_param_get_type(param), ESP_RMAKER_PARAM_POWER) == 0) {
/* … unchanged … */
}
return ESP_RMAKER_OK;
}

esp_rmaker_device_add_cb() registers it exactly as before.

ESP RainMaker Neo also offers a bulk callback, which receives every parameter of one request together, and recommends it even for single-parameter devices. It is worth adopting while you are in this code — but note that a bulk callback takes over the whole device, including esp.param.name, which you must then handle yourself. See Callbacks and events.

Services​

PreviouslyESP RainMaker Neo
Schedulesesp_rmaker_schedule_enable()delete — always initialised
Scenesesp_rmaker_scenes_enable()delete — not implemented
Diagnosticsapp_insights_enable()delete — unsupported
System reset flagSYSTEM_SERV_FLAG_WIFI_RESETSYSTEM_SERV_FLAG_NETWORK_RESET
Hold-to-resetapp_reset_button_register(btn, w, f)app_reset_button_register(btn)

Schedules need time sync. The schedule service is initialised no matter what, but arming is deferred until the clock synchronises — so schedules never fire on a node built with enable_time_sync = false. The ESP RainMaker Classic switch example ships exactly that combination, so if your application schedules anything, set enable_time_sync = true as part of the port and enable the timezone service. Automation triggers come up automatically too, and need no call. See Schedules and automations.

The system service needs a reset function. esp_rmaker_system_serv_config_t gains a network_reset_fn field, and esp_rmaker_system_service_enable() fails if a reset flag is set while it is NULL. Credential reset stays application-owned because the network stack varies. The examples pass app_network_reset_credentials:

esp_rmaker_system_serv_config_t system_serv_config = {
.flags = SYSTEM_SERV_FLAGS_ALL,
.reboot_seconds = 2,
.reset_seconds = 2,
.reset_reboot_seconds = 2,
.network_reset_fn = app_network_reset_credentials,
};

SYSTEM_SERV_FLAGS_ALL is unchanged, so only code naming individual flags needs remapping. System service has the rest.

Hold-to-reset thresholds moved to Kconfig — CONFIG_APP_RESET_NETWORK_TIME_MS (5 s) and CONFIG_APP_RESET_FACTORY_TIME_MS (10 s) — so the two timeout arguments are gone.

Challenge-response loses its master switch. CONFIG_ESP_RMAKER_ENABLE_CHALLENGE_RESPONSE has no counterpart: the provisioning-time endpoint is always present. The two on-network options are now CONFIG_ESP_RMAKER_NEO_ON_NETWORK_CHAL_RESP_ENABLE and CONFIG_ESP_RMAKER_NEO_LOCAL_CTRL_CHAL_RESP_ENABLE, and esp_rmaker_chal_resp_service_enable() starts the endpoint they select. The challenge-response-only service still cannot coexist with local control — both want the same protocomm_httpd singleton. See Feature overview.

OTA​

PreviouslyESP RainMaker Neo
Enableesp_rmaker_ota_enable(&cfg, OTA_USING_TOPICS)esp_rmaker_ota_enable(&cfg)
Default enableesp_rmaker_ota_enable_default()— none; esp_rmaker_ota_enable() with a zeroed config
Server certificateesp_rmaker_ota_config_t.server_cert— none

The transport argument is gone: transport is a build-time choice, so esp_rmaker_ota_type_t and OTA_USING_TOPICS have no equivalent. esp_rmaker_ota.h is the header in both generations, and RMAKER_OTA_EVENT still exists, so OTA event handling ports as it stands.

ESP RainMaker Neo has no esp_rmaker_ota_enable_default(). Replace the one-liner with esp_rmaker_ota_enable() and a configuration. A configuration with all fields zero gives the default behaviour.

esp_rmaker_ota_config_t ota_config = {
.ota_cb = NULL, /* NULL selects the default callback */
.ota_diag = ota_diag_fn, /* optional rollback diagnostics */
.priv = NULL,
};
esp_rmaker_ota_enable(&ota_config);

Set ota_diag when you want rollback diagnostics. With ota_diag set to NULL, the SDK marks the new image valid on the first MQTT connection. If MQTT does not connect within CONFIG_ESP_RMAKER_NEO_OTA_ROLLBACK_WAIT_PERIOD, the node rolls back to the previous image.

Call it before esp_rmaker_start() — it runs the first-boot rollback diagnostics. The transport itself changes on migration; see the caution below.

Events​

PreviouslyESP RainMaker Neo
Agent startedRMAKER_EVENT_STARTEDRMAKER_EVENT_CORE_STARTED
Network resetRMAKER_EVENT_WIFI_RESETRMAKER_EVENT_NETWORK_RESET
User-node mappingRMAKER_EVENT_USER_NODE_MAPPING_*— none, drop the cases
Config reportedRMAKER_EVENT_CONFIG_REPORTED— none, drop the case

The four event bases are the same, so a hand-written handler ports by fixing the renamed ids and deleting the cases with no counterpart. Register it before esp_rmaker_node_init() or you will miss RMAKER_EVENT_INIT_DONE.

During bring-up, one call replaces the four manual registrations and logs everything:

app_event_loop_register_default_handler();

Callbacks and events lists the full event set.

Logging and error types​

ESP_LOGx and esp_log.h still resolve on ESP-IDF, so logging can stay as it is. OSAL_LOGx is only needed if you want the sources to build for POSIX as well, and it comes from osal_log.h — include that explicitly if you switch, because the examples pick it up indirectly through app_entry.h.

Error types are renamed rather than replaced. SDK calls return esp_rmaker_error_t and the platform layer returns osal_err_t, but on ESP-IDF both are aliases of esp_err_t, and the success constants alias common ESP-IDF return codes, such as ESP_OK:

typedef esp_err_t osal_err_t; /* osal_err.h, ESP-IDF variant */
typedef osal_err_t esp_rmaker_error_t; /* esp_rmaker_error_types.h */

#define OSAL_ERR_OK ESP_OK
#define ESP_RMAKER_OK OSAL_ERR_OK
// ... and others

So existing code keeps working untouched: esp_err_t err = esp_rmaker_...(); and if (err == ESP_OK) are the same type and the same value, and a callback declared static esp_err_t write_cb(...) still matches the function-pointer type the SDK expects. Nothing forces an edit here.

Migrating the names anyway is worth doing, for two reasons. It stops your code depending on an alias that is only guaranteed on ESP-IDF — on POSIX osal_err_t is an int32_t with its own numeric space, so ESP_OK comparisons are not portable. And the SDK defines error values outside the ESP-IDF ranges for MQTT, NVS, OTA and RainMaker itself, which esp_err_to_name() cannot resolve; osal_err_strerror() names all of them.

Handle constructors still signal failure by returning NULL.

Audit sdkconfig.defaults​

ESP RainMaker Neo prefixes its Kconfig keys with ESP_RMAKER_NEO_, so most RainMaker keys have a new name. An ESP RainMaker Classic key with a one-to-one ESP RainMaker Neo equivalent converts automatically: each SDK component ships an sdkconfig.rename file, and the build reads the old name as the new one. The old names also stay defined in sdkconfig.h, so #if CONFIG_ESP_RMAKER_... checks in your sources keep working.

Do not rely on the conversion in a migrated project. Write the new names in sdkconfig.defaults*, so that the file shows the configuration that the build uses:

PreviouslyESP RainMaker Neo
Assisted claimingCONFIG_ESP_RMAKER_ASSISTED_CLAIMCONFIG_ESP_RMAKER_NEO_ASSISTED_CLAIM
OTA over MQTTCONFIG_ESP_RMAKER_OTA_USE_MQTTCONFIG_ESP_RMAKER_NEO_OTA_USE_MQTT
Local control securityCONFIG_ESP_RMAKER_LOCAL_CTRL_SECURITY_1 / ..._2CONFIG_ESP_RMAKER_NEO_LOCAL_CTRL_SECURITY_1 / ..._2
Parameter console commandsCONFIG_ESP_RMAKER_CONSOLE_PARAM_CMDS_ENABLECONFIG_ESP_RMAKER_NEO_CONSOLE_PARAM_CMDS_ENABLE
Maximum schedulesCONFIG_ESP_RMAKER_SCHEDULING_MAX_SCHEDULESCONFIG_ESP_RMAKER_NEO_SCHEDULING_MAX_SCHEDULES
Persistent MQTT sessionCONFIG_ESP_RMAKER_MQTT_PERSISTENT_SESSIONCONFIG_ESP_RMAKER_NEO_MQTT_USE_PERSISTENT_SESSION
Factory partitionCONFIG_ESP_RMAKER_FACTORY_PARTITION_NAME / ..._NAMESPACECONFIG_ESP_RMAKER_NEO_FACTORY_PARTITION_NAME / ..._NAMESPACE

The sdkconfig.rename files in esp_rmaker_neo, esp_rmaker_neo_common and esp_rmaker_neo_ota list every converted key.

Self claiming converts to no claiming

Set CONFIG_ESP_RMAKER_NEO_ASSISTED_CLAIM=y in a project that used self claiming, or pre-flash the device credentials. ESP RainMaker Neo has no self claiming. The line to look for is in the generated sdkconfig, not in sdkconfig.defaults: an ESP RainMaker Classic sdkconfig built with self claiming has # CONFIG_ESP_RMAKER_ASSISTED_CLAIM is not set, and if you carry that file over, the rename converts the line to CONFIG_ESP_RMAKER_NEO_ASSISTED_CLAIM=n. The node then has no claiming and gets no device credentials.

A defaults file that sets no claim key, such as the one in the ESP RainMaker Classic switch example, is safe: CONFIG_ESP_RMAKER_NEO_ASSISTED_CLAIM defaults to y. It depends on CONFIG_BT_ENABLED, so a build without Bluetooth has no claiming.

Keys with no conversion​

An ESP RainMaker Classic key with a different meaning or type has no sdkconfig.rename entry, so the build does not recognise it. ESP-IDF prints a note for an unknown CONFIG_ key and continues, so the line reads as set while doing nothing. Set the ESP RainMaker Neo key by hand, or delete the line:

PreviouslyESP RainMaker Neo
OTA over HTTPSCONFIG_ESP_RMAKER_OTA_USE_HTTPS (default)— none
ClaimingCONFIG_ESP_RMAKER_SELF_CLAIM / ..._NO_CLAIM (choice)CONFIG_ESP_RMAKER_NEO_ASSISTED_CLAIM (bool)
Default timezoneCONFIG_ESP_RMAKER_DEF_TIMEZONECONFIG_OSAL_TIMESYNC_DEFAULT_TZ
Local control auto-enableCONFIG_ESP_RMAKER_LOCAL_CTRL_AUTO_ENABLEesp_rmaker_local_ctrl_service_enable()
Local control compiled inCONFIG_ESP_RMAKER_LOCAL_CTRL_FEATURE_ENABLE— drop, always compiled in
Local control security 0CONFIG_ESP_RMAKER_LOCAL_CTRL_SECURITY_0— none
Provisioning challenge-responseCONFIG_ESP_RMAKER_ENABLE_CHALLENGE_RESPONSE— drop, always on
User id checkCONFIG_ESP_RMAKER_USER_ID_CHECK— drop
MQTT usernameCONFIG_ESP_RMAKER_MQTT_SEND_USERNAME— drop
Insights transportCONFIG_ESP_INSIGHTS_TRANSPORT_MQTT— drop

OTA transport is the clearest case. ESP RainMaker Classic defaults to HTTPS, and ESP RainMaker Neo downloads over MQTT file streams only, so a node on the ESP RainMaker Classic default changes transport whatever its defaults file says. Local control has no auto-enable key: call esp_rmaker_local_ctrl_service_enable() from your code.

This table is not exhaustive — it covers the keys commonly set in ESP RainMaker Classic example defaults. For any other RainMaker key your application sets, find its new name in sdkconfig.rename, or in idf.py menuconfig if it is absent there. Generic ESP-IDF keys — partition table, mbedTLS, NimBLE, FreeRTOS, LWIP, Wi-Fi — carry over untouched.

To confirm a key took effect, build and then grep the generated build/config/sdkconfig.h for the new name, or look it up in idf.py menuconfig.

The switch example, ported​

Both generations ship a switch example with the same device and the same two parameters, so they read as a direct before-and-after. The startup body reduces to this:

osal_err_t app_run(void)
{
esp_rmaker_console_init();
app_driver_init();

osal_storage_init(NULL); /* was the nvs_flash_init() block */
app_network_init();
app_network_provision(NEO_MFG_DATA_DEVICE_TYPE_SWITCH, /* moved up from the very end */
NEO_MFG_DATA_DEVICE_SUBTYPE_SWITCH);
app_event_loop_register_default_handler(); /* was four manual registrations */

esp_rmaker_config_t rainmaker_cfg = { .enable_time_sync = true }; /* was false */
esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "Switch", "switch");

/* Device and parameters: unchanged apart from the _ID macros */
switch_device = esp_rmaker_device_create("Switch", ESP_RMAKER_DEVICE_SWITCH, NULL);
esp_rmaker_device_add_cb(switch_device, write_cb, NULL);
esp_rmaker_device_add_param(switch_device,
esp_rmaker_name_param_create(ESP_RMAKER_DEF_NAME_PARAM_ID, "Switch"));
esp_rmaker_param_t *power_param =
esp_rmaker_power_param_create(ESP_RMAKER_DEF_POWER_ID, DEFAULT_POWER);
esp_rmaker_device_add_param(switch_device, power_param);
esp_rmaker_device_assign_primary_param(switch_device, power_param);
esp_rmaker_node_add_device(node, switch_device);

esp_rmaker_timezone_service_enable();
esp_rmaker_system_service_enable(&system_serv_config); /* now needs network_reset_fn */
/* esp_rmaker_schedule_enable() — deleted, always initialised */
/* esp_rmaker_scenes_enable() — deleted, not implemented */
/* app_insights_enable() — deleted, unsupported */

/* esp_rmaker_ota_enable_default() — deleted, pass a config */
esp_rmaker_ota_config_t ota_config = { .ota_cb = NULL, .ota_diag = ota_diag_fn };
esp_rmaker_ota_enable(&ota_config);

esp_rmaker_start();
return OSAL_ERR_OK;
}

The app_run() wrapper is the dual-platform convention described above; keep app_main if you only target ESP-IDF.

Nodes already in the field​

The steps above port the source code. A node that already runs ESP RainMaker Classic firmware in the field also needs its stored identity moved. You send the ESP RainMaker Neo image to that node as a normal ESP RainMaker Classic OTA update, and the new image does the rest at boot.

What changes in the factory partition​

Most of the fctry partition stays valid. The client certificate and the private key keep their values, and ESP RainMaker Neo reads them from the same fields. Only two fields need action.

mqtt_host points at the ESP RainMaker Classic MQTT host, so the migration replaces it with the target host. node_id is absent on a self-claimed node, because self claiming does not write it, so the migration copies the certificate CN into it.

Enable the migration​

Set these keys in sdkconfig.defaults:

CONFIG_ESP_RMAKER_NEO_MQTT_HOST_MIGRATION=y
CONFIG_ESP_RMAKER_NEO_MQTT_HOST_MIGRATION_TARGET="<iot-endpoint-host>"
CONFIG_ESP_RMAKER_NEO_MQTT_HOST_MIGRATION_TIMEOUT=90

CONFIG_ESP_RMAKER_NEO_MQTT_HOST_MIGRATION_TARGET is the ESP RainMaker Neo MQTT host for your deployment. CONFIG_ESP_RMAKER_NEO_MQTT_HOST_MIGRATION_TIMEOUT is the time in seconds from the first network connection to the first MQTT connection to the target host. The range is 30 s to 3600 s.

A self-claimed node needs the migration to boot at all. The default node ID provider does not use the certificate CN, so a fctry partition with no node_id boots only when CONFIG_ESP_RMAKER_NEO_MQTT_HOST_MIGRATION=y.

What the node does​

The migration check runs in esp_rmaker_node_init(), after assisted claiming and before the SDK reads the MQTT connection parameters. In order, the node:

  • Uses the certificate CN as the node ID, in memory only, if fctry has no node_id. If there is also no certificate CN, esp_rmaker_node_init() fails with ESP_RMAKER_NOT_FOUND.
  • Uses the target host, in memory only, if the stored mqtt_host is not the target host.
  • Connects to the target host.
  • Writes node_id and then mqtt_host to fctry after the first MQTT connection, once the running image is valid. If a write fails, it tries again at the next MQTT reconnect.

After the write-back, the stored values match the target, and later boots do not change fctry.

The node does not write to fctry while the running image can roll back. Thus, if the power fails before the image is valid, the bootloader rolls back to the ESP RainMaker Classic image, and that image finds its original fctry contents.

If the node cannot connect​

If the timeout expires before the first MQTT connection, the node logs its node ID and the target host. The next step depends on the image state:

  • Image pending verify — the node rolls back to the previous image and reboots. It does not post an event.
  • Any other image — the node posts RMAKER_EVENT_MQTT_HOST_MIGRATION_TIMEOUT. The MQTT retries continue, and fctry does not change.

Rollback is a property of the firmware already in the field, and you cannot add it now. The image is pending verify only when the ESP RainMaker Classic firmware and bootloader on the device were built with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE. If they were not, the node never takes the rollback path: on timeout it posts RMAKER_EVENT_MQTT_HOST_MIGRATION_TIMEOUT and keeps retrying, and it writes fctry back on the first MQTT connection that succeeds. A node that cannot reach the target host stays on the ESP RainMaker Neo image. See Rollback and diagnostics.

When rollback is available and you called esp_rmaker_ota_enable() as the OTA section describes, the OTA timer is the one that usually rolls back. CONFIG_ESP_RMAKER_NEO_OTA_ROLLBACK_WAIT_PERIOD (default 90 s, range 30 s to 600 s) starts when OTA is enabled, before the migration timeout starts at the first network connection, and both default to 90 s. The migration rollback is the fallback for an application without the OTA component. To give a slow network more time, raise both values, not the migration timeout alone.

Keep the partition table identical​

Do not change the partition table

Use the same partitions.csv and the same CONFIG_PARTITION_TABLE_OFFSET as the ESP RainMaker Classic firmware. An OTA update does not write the partition table, so the new image must run against the table that is already on the device.

Make sure that the ESP RainMaker Neo build matches these items too:

  • The offsets and sizes of nvs, otadata, fctry and the OTA slots.
  • CONFIG_ESP_RMAKER_NEO_FACTORY_PARTITION_NAME and CONFIG_ESP_RMAKER_NEO_FACTORY_NAMESPACE. Both defaults are the same as in ESP RainMaker Classic: fctry and rmaker_creds.
  • CONFIG_APP_NETWORK_FACTORY_NVS_PARTITION_NAME and CONFIG_APP_NETWORK_PROV_DATA_NAMESPACE, with the same values. These options no longer follow the RainMaker options, so set them in sdkconfig.defaults. See Factory NVS.
  • The flash size.

Also make sure that the ESP RainMaker Neo image fits in one OTA slot of the existing table.

Known limitation​

The ESP RainMaker Classic OTA job that delivers the ESP RainMaker Neo image does not get a completion report. The new image connects only to the target host, so the ESP RainMaker Classic backend never receives the success status for that job.

Checklist​

  • Swap the dependencies in main/idf_component.yml; drop the Insights component.
  • Decide on app_main or app_run(), and keep an fctry partition.
  • Collapse the standard includes to esp_rmaker_core.h.
  • Move provisioning ahead of esp_rmaker_node_init() via app_network_provision().
  • Replace the nvs_flash_init() block with osal_storage_init(NULL).
  • Rename ESP_RMAKER_DEF_*_NAME to _ID, and *_get_name() to *_get_id().
  • Optional: move return types to esp_rmaker_error_t / ESP_RMAKER_OK (aliases on ESP-IDF).
  • Delete the schedule, scenes and Insights enable calls.
  • Set enable_time_sync = true if you use schedules.
  • Set network_reset_fn on the system service config.
  • Drop the transport argument from esp_rmaker_ota_enable(), and replace esp_rmaker_ota_enable_default() with it.
  • Fix renamed event ids and delete the ones with no counterpart.
  • Write the ESP_RMAKER_NEO_ names in sdkconfig.defaults*, and set or delete the keys with no conversion.
  • Review your device and parameter type strings against the reference.
  • For in-field nodes: enable CONFIG_ESP_RMAKER_NEO_MQTT_HOST_MIGRATION and keep the partition table identical.