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_tstill carries theenable_time_syncfield. - 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:
| Previously | ESP RainMaker Neo | |
|---|---|---|
| SDK | espressif/esp_rainmaker | esp_rmaker_neo, esp_rmaker_neo_ota |
| Provisioning | espressif/rmaker_app_network | app_network |
| Hold-to-reset | espressif/rmaker_app_reset | app_reset |
| Diagnostics | espressif/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
| Previously | ESP RainMaker Neo | |
|---|---|---|
| Parameter id | esp_rmaker_param_get_name() | esp_rmaker_param_get_id() |
| Device id | esp_rmaker_device_get_name() | esp_rmaker_device_get_id() |
| Parameter lookup | esp_rmaker_device_get_param_by_name() | esp_rmaker_device_get_param_by_id() |
| Request source | esp_rmaker_device_cb_src_to_str() | esp_rmaker_req_src_to_string() |
| Parameter id macros | ESP_RMAKER_DEF_POWER_NAME | ESP_RMAKER_DEF_POWER_ID |
| Name parameter macro | ESP_RMAKER_DEF_NAME_PARAM | ESP_RMAKER_DEF_NAME_PARAM_ID |
| Manufacturer data | MFG_DATA_DEVICE_TYPE_* | NEO_MFG_DATA_DEVICE_TYPE_* |
| Node readme | esp_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
| Previously | ESP RainMaker Neo | |
|---|---|---|
| Schedules | esp_rmaker_schedule_enable() | delete — always initialised |
| Scenes | esp_rmaker_scenes_enable() | delete — not implemented |
| Diagnostics | app_insights_enable() | delete — unsupported |
| System reset flag | SYSTEM_SERV_FLAG_WIFI_RESET | SYSTEM_SERV_FLAG_NETWORK_RESET |
| Hold-to-reset | app_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
| Previously | ESP RainMaker Neo | |
|---|---|---|
| Enable | esp_rmaker_ota_enable(&cfg, OTA_USING_TOPICS) | esp_rmaker_ota_enable(&cfg) |
| Default enable | esp_rmaker_ota_enable_default() | — none; esp_rmaker_ota_enable() with a zeroed config |
| Server certificate | esp_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
| Previously | ESP RainMaker Neo | |
|---|---|---|
| Agent started | RMAKER_EVENT_STARTED | RMAKER_EVENT_CORE_STARTED |
| Network reset | RMAKER_EVENT_WIFI_RESET | RMAKER_EVENT_NETWORK_RESET |
| User-node mapping | RMAKER_EVENT_USER_NODE_MAPPING_* | — none, drop the cases |
| Config reported | RMAKER_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:
| Previously | ESP RainMaker Neo | |
|---|---|---|
| Assisted claiming | CONFIG_ESP_RMAKER_ASSISTED_CLAIM | CONFIG_ESP_RMAKER_NEO_ASSISTED_CLAIM |
| OTA over MQTT | CONFIG_ESP_RMAKER_OTA_USE_MQTT | CONFIG_ESP_RMAKER_NEO_OTA_USE_MQTT |
| Local control security | CONFIG_ESP_RMAKER_LOCAL_CTRL_SECURITY_1 / ..._2 | CONFIG_ESP_RMAKER_NEO_LOCAL_CTRL_SECURITY_1 / ..._2 |
| Parameter console commands | CONFIG_ESP_RMAKER_CONSOLE_PARAM_CMDS_ENABLE | CONFIG_ESP_RMAKER_NEO_CONSOLE_PARAM_CMDS_ENABLE |
| Maximum schedules | CONFIG_ESP_RMAKER_SCHEDULING_MAX_SCHEDULES | CONFIG_ESP_RMAKER_NEO_SCHEDULING_MAX_SCHEDULES |
| Persistent MQTT session | CONFIG_ESP_RMAKER_MQTT_PERSISTENT_SESSION | CONFIG_ESP_RMAKER_NEO_MQTT_USE_PERSISTENT_SESSION |
| Factory partition | CONFIG_ESP_RMAKER_FACTORY_PARTITION_NAME / ..._NAMESPACE | CONFIG_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.
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:
| Previously | ESP RainMaker Neo | |
|---|---|---|
| OTA over HTTPS | CONFIG_ESP_RMAKER_OTA_USE_HTTPS (default) | — none |
| Claiming | CONFIG_ESP_RMAKER_SELF_CLAIM / ..._NO_CLAIM (choice) | CONFIG_ESP_RMAKER_NEO_ASSISTED_CLAIM (bool) |
| Default timezone | CONFIG_ESP_RMAKER_DEF_TIMEZONE | CONFIG_OSAL_TIMESYNC_DEFAULT_TZ |
| Local control auto-enable | CONFIG_ESP_RMAKER_LOCAL_CTRL_AUTO_ENABLE | esp_rmaker_local_ctrl_service_enable() |
| Local control compiled in | CONFIG_ESP_RMAKER_LOCAL_CTRL_FEATURE_ENABLE | — drop, always compiled in |
| Local control security 0 | CONFIG_ESP_RMAKER_LOCAL_CTRL_SECURITY_0 | — none |
| Provisioning challenge-response | CONFIG_ESP_RMAKER_ENABLE_CHALLENGE_RESPONSE | — drop, always on |
| User id check | CONFIG_ESP_RMAKER_USER_ID_CHECK | — drop |
| MQTT username | CONFIG_ESP_RMAKER_MQTT_SEND_USERNAME | — drop |
| Insights transport | CONFIG_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
fctryhas nonode_id. If there is also no certificate CN,esp_rmaker_node_init()fails withESP_RMAKER_NOT_FOUND. - Uses the target host, in memory only, if the stored
mqtt_hostis not the target host. - Connects to the target host.
- Writes
node_idand thenmqtt_hosttofctryafter 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, andfctrydoes 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
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,fctryand the OTA slots. CONFIG_ESP_RMAKER_NEO_FACTORY_PARTITION_NAMEandCONFIG_ESP_RMAKER_NEO_FACTORY_NAMESPACE. Both defaults are the same as in ESP RainMaker Classic:fctryandrmaker_creds.CONFIG_APP_NETWORK_FACTORY_NVS_PARTITION_NAMEandCONFIG_APP_NETWORK_PROV_DATA_NAMESPACE, with the same values. These options no longer follow the RainMaker options, so set them insdkconfig.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_mainorapp_run(), and keep anfctrypartition. - Collapse the standard includes to
esp_rmaker_core.h. - Move provisioning ahead of
esp_rmaker_node_init()viaapp_network_provision(). - Replace the
nvs_flash_init()block withosal_storage_init(NULL). - Rename
ESP_RMAKER_DEF_*_NAMEto_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 = trueif you use schedules. - Set
network_reset_fnon the system service config. - Drop the transport argument from
esp_rmaker_ota_enable(), and replaceesp_rmaker_ota_enable_default()with it. - Fix renamed event ids and delete the ones with no counterpart.
- Write the
ESP_RMAKER_NEO_names insdkconfig.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_MIGRATIONand keep the partition table identical.
Related
- Code basics — the startup order this page rearranges
- Callbacks and events — bulk callbacks and the event set
- Custom & standard types — type strings that changed
- System service — the newly required reset function
- OTA firmware updates — how MQTT file-stream updates work