Chapter 123: ESP RainMaker Cloud Integration

Chapter Objectives

Upon completing this chapter, you will be able to:

  • Understand the architecture and core concepts of ESP RainMaker.
  • Set up an ESP32 project to use the ESP RainMaker SDK.
  • Define device models using Nodes, Devices, Parameters, and Services.
  • Implement firmware to connect an ESP32 device to the ESP RainMaker cloud.
  • Control ESP32 devices and view telemetry using the ESP RainMaker mobile applications.
  • Implement Over-The-Air (OTA) firmware updates using ESP RainMaker.
  • Understand user-device association and basic security aspects.
  • Identify common issues and troubleshoot ESP RainMaker integration problems.
  • Leverage ESP RainMaker for rapid IoT product development.

Introduction

In the previous chapters, we explored integrating ESP32 devices with major third-party cloud platforms like AWS IoT Core and Azure IoT Hub, and we also looked at the historical integration with Google Cloud IoT Core. While these platforms are powerful and versatile, they often involve a significant learning curve and setup complexity. Espressif, the creators of the ESP32, offers its own end-to-end IoT solution called ESP RainMaker.

ESP RainMaker is designed to simplify the development of connected products by providing a ready-to-use cloud backend, mobile applications (iOS and Android), and a device SDK. It allows developers to quickly build IoT solutions without needing to manage complex cloud infrastructure or develop mobile apps from scratch. This chapter will introduce you to ESP RainMaker, guiding you through its architecture, device modeling, and practical implementation to get your ESP32 devices connected and controlled via the cloud with minimal effort.

Theory

ESP RainMaker Architecture

ESP RainMaker provides a complete ecosystem for connecting your ESP32 devices to the cloud and controlling them via mobile applications. The architecture consists of several key components:

  1. ESP32 Device with RainMaker SDK:
    • Your ESP32 firmware incorporates the ESP RainMaker SDK.
    • The SDK handles Wi-Fi provisioning, secure communication with the RainMaker cloud, device modeling, parameter updates, and OTA firmware updates.
    • It abstracts much of the underlying complexity of MQTT communication and cloud interaction.
  2. ESP RainMaker Cloud:
    • A managed cloud service hosted by Espressif (built on AWS).
    • Acts as the central message broker and device management platform.
    • Handles device authentication, data storage for device state (shadows), message routing between devices and applications, and OTA update distribution.
  3. ESP RainMaker Mobile Applications (iOS/Android):
    • Ready-to-use mobile apps that users can download from app stores.
    • Allow users to:
      • Provision Wi-Fi credentials to new devices.
      • Claim/associate devices to their user accounts.
      • View device status and telemetry.
      • Control devices by changing parameter values.
      • Receive OTA firmware updates.
      • Utilize features like scheduling and scenes (if implemented).
    • The apps dynamically render UI based on the device model reported by the firmware.
  4. ESP RainMaker Admin Dashboard:
    • A web-based dashboard for developers and administrators.
    • Allows for:
      • Managing users and devices.
      • Monitoring device connectivity and data.
      • Managing firmware versions and deploying OTA updates.
      • Viewing logs and diagnostics.
graph TD
    subgraph "User Interaction"
        direction LR
        MobileApp["<center><b>ESP RainMaker Mobile App</b><br>(iOS / Android)</center>"]
        AdminDash["<center><b>ESP RainMaker Admin Dashboard</b><br>(Web)</center>"]
    end

    subgraph "ESP RainMaker Cloud (Espressif Managed - AWS Based)"
        direction TB
        RMCloud["<center><b>Cloud Backend</b><br>(Message Broker, Device Management, OTA Service, Data Storage, User Auth)</center>"]
    end

    subgraph "Physical Device"
        direction TB
        ESP32Device["<center><b>ESP32 Device</b><br>Firmware with<br><b>ESP RainMaker SDK</b></center>"]
        WiFi["<center><b>Wi-Fi Network</b><br>(Internet Access)</center>"]
        ESP32Device -- "Connects via" --> WiFi
    end

    MobileApp -- "Control, View Telemetry, Provision, OTA Trigger" --> RMCloud
    AdminDash -- "Manage Devices, Users, OTA Deploy" --> RMCloud
    
    RMCloud -- "Secure MQTT over TLS" --- WiFi
    WiFi -- "Secure MQTT over TLS" --- ESP32Device

    %% Data Flows
    ESP32Device -.->|"Telemetry (Reported Params)"| RMCloud
    RMCloud -.->|"Control (Desired Params)"| ESP32Device
    RMCloud -.->|OTA Firmware Chunks| ESP32Device
    MobileApp -.->|"Wi-Fi Credentials (Provisioning)"| ESP32Device

    classDef userInterface fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46
    classDef cloudPlatform fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#1E40AF
    classDef device fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef network fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E

    class MobileApp userInterface;
    class AdminDash userInterface;
    class RMCloud cloudPlatform;
    class ESP32Device device;
    class WiFi network;

Core Concepts

  • Node: A logical representation of your entire ESP32-based product or a physical ESP32 chip. A node can contain one or more “devices.”
  • Device: Represents a functional unit within a node. For example, a smart plug (node) might have one “device” which is a switch. A multi-gang smart switch (node) might have multiple “device” instances, each representing an individual switch.
  • Parameter: Represents an attribute or capability of a device. Parameters can be:
    • Reported: Values sent from the device to the cloud (e.g., current temperature, switch state).
    • Desired: Values sent from the cloud/app to the device (e.g., set target temperature, turn switch on).
    • Parameters have a name, data type (int, float, bool, string), properties (e.g., read-only, read-write), and UI type hints (e.g., esp.ui.slider, esp.ui.toggle).
  • Service: Represents a specific functionality offered by the node or device, often system-level. Examples:
    • esp.service.ota: For Over-The-Air firmware updates.
    • esp.service.schedule: For scheduling actions on devices.
    • esp.service.time: For time synchronization.
    • esp.service.system: For system-level information like Wi-Fi RSSI, MAC address, firmware version.
  • Device Model: The collective definition of a node, its devices, and their parameters and services. This model is defined in the ESP32 firmware and reported to the RainMaker cloud. The mobile app uses this model to render the appropriate UI for controlling the device.

Communication

  • Under the hood, ESP RainMaker uses MQTT over TLS for secure and efficient communication between the device SDK and the RainMaker cloud.
  • However, as a developer using the RainMaker SDK, you typically don’t interact directly with MQTT topics or messages. The SDK provides higher-level APIs for initializing the agent, defining devices and parameters, and handling parameter updates.

Security

  • Device Authentication: Each device gets unique credentials provisioned during an initial “claiming” process, typically facilitated by the mobile app.
  • Transport Security: All communication between the device and the cloud, and between the app and the cloud, is encrypted using TLS.
  • User Authentication: Users create accounts and log in via the mobile applications.
  • Firmware Signing for OTA: For secure OTA updates, firmware images can be signed, and the device can verify the signature before applying the update.

User-Device Association (Claiming)

sequenceDiagram
    participant User as User (with Mobile App)
    participant MobileApp as ESP RainMaker Mobile App
    participant ESP32Device as ESP32 Device (Provisioning Mode)
    participant RMCloud as ESP RainMaker Cloud

    User->>MobileApp: 1. Initiates "Add Device"
    MobileApp->>MobileApp: 2. Scans for devices<br> (SoftAP SSID / BLE)
    
    alt SoftAP Provisioning
        ESP32Device-->>MobileApp: Broadcasts SoftAP SSID (e.g., PROV_XXXX)
        MobileApp->>User: 3a. Displays "PROV_XXXX"
        User->>MobileApp: 4a. Selects device <br>& Enters Wi-Fi Credentials (Home SSID/Pass)
        MobileApp->>ESP32Device: 5a. Sends Wi-Fi Credentials <br>(over SoftAP connection)
    else BLE Provisioning
        ESP32Device-->>MobileApp: Advertises BLE Service
        MobileApp->>User: 3b. Discovers device via BLE
        User->>MobileApp: 4b. Selects device & Enters Wi-Fi Credentials
        MobileApp->>ESP32Device: 5b. Sends Wi-Fi Credentials (over BLE)
    end

    ESP32Device->>ESP32Device: 6. Attempts to connect to<br> provided Wi-Fi
    ESP32Device-->>RMCloud: 7. Connects to RainMaker Cloud<br> (if Wi-Fi successful)
    Note over ESP32Device,RMCloud: Device is now online but un-claimed

    MobileApp->>RMCloud: 8. User requests to "Claim" the device
    activate RMCloud
    RMCloud->>RMCloud: 9. Verifies device presence & user identity
    RMCloud-->>MobileApp: 10. Claiming Status (e.g., Success/Fail)
    deactivate RMCloud
    Note over RMCloud,ESP32Device: Cloud associates device with user account

    MobileApp->>User: 11. Shows device as "Claimed" / Added
    User->>MobileApp: 12. Can now control the device
    MobileApp->>RMCloud: Control Commands
    RMCloud->>ESP32Device: Control Commands
  • When a new RainMaker-enabled device powers up for the first time or after a reset, it typically enters a provisioning mode.
  • The user, through the RainMaker mobile app, scans for nearby devices (e.g., via Wi-Fi SoftAP or BLE).
  • The app then guides the user to provide Wi-Fi credentials to the device.
  • Once the device connects to the internet and the RainMaker cloud, the user can “claim” the device, associating it with their RainMaker user account. This prevents unauthorized users from controlling the device.

Self-Claiming (for Development)

For development and testing, RainMaker supports a “self-claiming” mode. If enabled, the device can automatically claim itself without requiring a mobile app. This is useful for initial firmware development before integrating with the full app-based claiming process.

Practical Examples

Let’s build a simple smart light that can be turned on/off and its brightness controlled via the ESP RainMaker app.

Prerequisites

  1. ESP-IDF Setup: ESP-IDF v5.x with VS Code.
  2. ESP RainMaker Account: Create an account using the ESP RainMaker mobile app (available on iOS App Store and Google Play Store) or via the ESP RainMaker Dashboard.
  3. ESP RainMaker Mobile App: Install the app on your smartphone.
  4. Wi-Fi Network: Credentials for a 2.4 GHz Wi-Fi network.

Adding the esp-rainmaker Component

The esp-rainmaker component is typically added to your project using the ESP-IDF component manager. Create a new project (e.g., rainmaker_light) and then in your project’s root directory:

Bash
idf.py add-dependency "espressif/esp-rainmaker^1.0" 
# Check the component registry for the latest version: https://components.espressif.com/components/espressif/esp-rainmaker

This will add the dependency to your main/idf_component.yml (or create it) and download the component when you run idf.py reconfigure or idf.py build.

Code Snippet: Smart Light Example

main/app_main.c:

graph TD
    subgraph "User & Mobile App"
        direction LR
        User["<center><b>User</b></center>"] -- "Taps Power Toggle / Adjusts Brightness" --> AppUI["<center><b>ESP RainMaker App UI</b><br>(Light Controls)</center>"]
    end

    subgraph "ESP RainMaker Cloud"
        direction TB
        RMCloud["<center><b>RainMaker Cloud Service</b></center>"]
    end

    subgraph "ESP32 Smart Light Device"
        direction TB
        RMSDK["<center><b>RainMaker SDK</b><br>on ESP32</center>"] --> WriteCB["<center><b>write_callback()</b><br><i>(Firmware Logic)</i></center>"]
        WriteCB --> Hardware["<center><b>Physical LED /<br>Brightness Control</b><br>(GPIO, PWM)</center>"]
        WriteCB -- "Update Local State" --> LocalState["<center><b>g_power_state,<br>g_brightness</b></center>"]
    end

    AppUI -- "1- Sends Desired State Change<br>(e.g., Power=true, Brightness=75)" --> RMCloud
    RMCloud -- "2- Relays Desired State<br>to Device (MQTT)" --> RMSDK
    RMSDK -- "3- Invokes Registered Callback" --> WriteCB
    WriteCB -- "4- Updates Hardware & Local Variables" --> Hardware
    LocalState -- "5- Reports New State Back<br><tt>esp_rmaker_param_update_and_report()</tt>" --> RMSDK
    RMSDK -- "6- Sends Reported State<br>to Cloud (MQTT)" --> RMCloud
    RMCloud -- "7- Updates App UI<br>with Reported State" --> AppUI
    
    classDef userInterface fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46
    classDef cloudPlatform fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#1E40AF
    classDef deviceSdk fill:#EDE9FE,stroke:#5B21B6,stroke-width:1px,color:#5B21B6
    classDef firmwareLogic fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E

    class User userInterface;
    class AppUI userInterface;
    class RMCloud cloudPlatform;
    class RMSDK deviceSdk;
    class WriteCB firmwareLogic;
    class Hardware firmwareLogic;
    class LocalState firmwareLogic;
C
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"

// ESP RainMaker specific headers
#include <esp_rmaker_core.h>
#include <esp_rmaker_standard_params.h>
#include <esp_rmaker_standard_devices.h>
#include <esp_rmaker_ota.h>
#include <esp_rmaker_schedule.h>
#include <esp_rmaker_common_events.h>

// Wi-Fi Provisioning (if you want to use it, otherwise hardcode for simplicity in example)
#include <wifi_provisioning/manager.h>

// GPIO for LED (example)
#include "driver/gpio.h"
#define OUTPUT_GPIO CONFIG_EXAMPLE_OUTPUT_GPIO // Configure this in Kconfig

static const char *TAG = "app_main";

// --- Device Parameter Variables ---
// These variables will hold the state of our light
static bool g_power_state = false;
static int g_brightness = 50; // Default brightness 0-100

/* Callback to handle write events from the ESP RainMaker cloud */
static esp_err_t write_callback(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 (ctx) {
        ESP_LOGI(TAG, "Received write context from: %s", esp_rmaker_device_cb_src_to_str(ctx->src));
    }

    const char *device_name = esp_rmaker_device_get_name(device);
    const char *param_name = esp_rmaker_param_get_name(param);

    if (strcmp(param_name, ESP_RMAKER_DEF_POWER_NAME) == 0) {
        ESP_LOGI(TAG, "Received value = %s for %s - %s",
                 val.val.b ? "true" : "false", device_name, param_name);
        g_power_state = val.val.b;
        // --- Control your actual light hardware here ---
        gpio_set_level(OUTPUT_GPIO, g_power_state); 
        ESP_LOGI(TAG, "Light is %s", g_power_state ? "ON" : "OFF");
        // Report the change back to RainMaker (optional if only cloud changes it, but good practice)
        esp_rmaker_param_update_and_report(param, esp_rmaker_bool(g_power_state));

    } else if (strcmp(param_name, ESP_RMAKER_DEF_BRIGHTNESS_NAME) == 0) {
        ESP_LOGI(TAG, "Received value = %d for %s - %s",
                 val.val.i, device_name, param_name);
        g_brightness = val.val.i;
        // --- Control your actual light brightness here ---
        ESP_LOGI(TAG, "Brightness set to %d%%", g_brightness);
        // Report the change back
        esp_rmaker_param_update_and_report(param, esp_rmaker_int(g_brightness));
    } else {
        /* Silently ignoring invalid params */
        return ESP_OK;
    }
    return ESP_OK;
}

// Event handler for Wi-Fi and RainMaker events
static void event_handler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data)
{
    if (event_base == WIFI_PROV_EVENT) {
        switch (event_id) {
            case WIFI_PROV_START:
                ESP_LOGI(TAG, "Provisioning Application Started");
                break;
            case WIFI_PROV_CRED_RECV: {
                wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data;
                ESP_LOGI(TAG, "Received Wi-Fi credentials" SSID_FORMAT ", Password: %s",
                         SSID_ARGS(wifi_sta_cfg->ssid),
                         (const char *) wifi_sta_cfg->password);
                break;
            }
            case WIFI_PROV_CRED_FAIL: {
                wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
                ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s"
                         "\n\tPlease reset to factory and retry provisioning",
                         (*reason == WIFI_PROV_STA_AUTH_ERROR) ?
                         "Wi-Fi station authentication failed" : "Wi-Fi access-point not found");
                break;
            }
            case WIFI_PROV_CRED_SUCCESS:
                ESP_LOGI(TAG, "Provisioning Successful");
                break;
            case WIFI_PROV_END:
                /* De-initialize manager once provisioning is finished */
                // wifi_prov_mgr_deinit(); // Keep it running if you want to allow re-provisioning
                ESP_LOGI(TAG, "Provisioning Finished.");
                break;
            default:
                break;
        }
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        ESP_LOGI(TAG, "Wi-Fi disconnected. Reconnecting...");
        esp_wifi_connect(); // ESP RainMaker SDK handles reconnection logic internally too
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ESP_LOGI(TAG, "Wi-Fi Connected. Got IP Address.");
    } else if (event_base == RMAKER_COMMON_EVENT) {
        switch(event_id) {
            case RMAKER_EVENT_INIT_DONE:
                ESP_LOGI(TAG, "RainMaker Initialised.");
                break;
            case RMAKER_EVENT_CLAIM_STARTED:
                ESP_LOGI(TAG, "RainMaker Claim Started.");
                break;
            case RMAKER_EVENT_CLAIM_SUCCESSFUL:
                ESP_LOGI(TAG, "RainMaker Claim Successful.");
                break;
            case RMAKER_EVENT_CLAIM_FAILED:
                ESP_LOGE(TAG, "RainMaker Claim Failed.");
                break;
            default:
                ESP_LOGW(TAG, "Unhandled RainMaker Common Event: %"PRId32, event_id);
        }
    } else if (event_base == RMAKER_OTA_EVENT) { // Handle OTA events
        switch(event_id) {
            case RMAKER_OTA_EVENT_STARTING:
                ESP_LOGI(TAG, "Starting OTA. Please wait ...");
                break;
            // Other OTA events like RMAKER_OTA_EVENT_IN_PROGRESS, RMAKER_OTA_EVENT_SUCCESSFUL, RMAKER_OTA_EVENT_FAILED, RMAKER_OTA_EVENT_REJECTED, RMAKER_OTA_EVENT_DELAYED
            default:
                ESP_LOGI(TAG, "OTA Event: %"PRId32, event_id);
                break;
        }
    } else {
        ESP_LOGW(TAG, "Unhandled event base %s with ID %"PRId32, event_base, event_id);
    }
}


void app_main()
{
    /* Initialize NVS. */
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

    /* Initialize Wi-Fi stack */
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* Register event handlers */
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(RMAKER_COMMON_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(RMAKER_OTA_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));


    /* Initialize ESP RainMaker */
    esp_rmaker_config_t rainmaker_cfg = {
        .enable_time_sync = true, // Automatically sync time using SNTP
    };
    esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "ESP RainMaker Light", "Lightbulb");
    if (!node) {
        ESP_LOGE(TAG, "Could not initialise node. Aborting!!!");
        vTaskDelay(5000/portTICK_PERIOD_MS);
        abort();
    }

    /* Create a Light device */
    // Using a standard device type helps the app render a familiar UI
    esp_rmaker_device_t *light_device = esp_rmaker_lightbulb_device_create("SmartLight", NULL, g_power_state);
    if (!light_device) {
        ESP_LOGE(TAG, "Failed to create light device.");
        // Handle error
    }
    
    // Add the device to the node
    esp_rmaker_node_add_device(node, light_device);

    /* Add the standard brightness parameter (ESP_RMAKER_DEF_BRIGHTNESS_NAME) */
    esp_rmaker_param_t *brightness_param = esp_rmaker_brightness_param_create(ESP_RMAKER_DEF_BRIGHTNESS_NAME, g_brightness);
    if (!brightness_param) {
         ESP_LOGE(TAG, "Failed to create brightness parameter.");
    } else {
        esp_rmaker_device_add_param(light_device, brightness_param);
        // You can also set UI type hints if not using standard params
        // esp_rmaker_param_add_ui_type(brightness_param, ESP_RMAKER_UI_SLIDER);
    }
    
    /* Assign the write callback for the device */
    // This single callback will handle writes for all parameters of this device
    esp_rmaker_device_add_cb(light_device, write_callback, NULL);

    /* Enable OTA */
    esp_rmaker_ota_config_t ota_config = {
        .server_cert = ESP_RMAKER_OTA_DEFAULT_SERVER_CERT, // Use default RainMaker OTA server cert
    };
    err = esp_rmaker_ota_enable(&ota_config, OTA_USING_PARAMS); // OTA_USING_PARAMS allows triggering OTA via a parameter
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable OTA: %s", esp_err_to_name(err));
    }

    /* Enable scheduling */
    err = esp_rmaker_schedule_enable();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable Schedule: %s", esp_err_to_name(err));
    }
    
    /* Enable System Service to report info like Wi-Fi RSSI, MAC, IP, firmware version */
    esp_rmaker_system_serv_config_t system_serv_config = {
        .flags = SYSTEM_SERV_FLAGS_ALL, // Report all available system info
        .reset_reboot_seconds = 2,      // Seconds to wait before rebooting on reset command
    };
    esp_rmaker_system_service_enable(&system_serv_config);


    /* Start the ESP RainMaker Core task */
    err = esp_rmaker_start();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Could not start ESP RainMaker!: %s", esp_err_to_name(err));
    }

    // Configure GPIO for LED
    gpio_reset_pin(OUTPUT_GPIO);
    gpio_set_direction(OUTPUT_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_level(OUTPUT_GPIO, g_power_state); // Set initial state

    /* Wi-Fi Provisioning */
    // If you want to use Wi-Fi provisioning (SoftAP or BLE based on menuconfig)
    // The RainMaker SDK can automatically start provisioning if no Wi-Fi credentials are found.
    // You select the provisioning type (SoftAP, BLE) in menuconfig.
    // For SoftAP, the device will create an AP like "PROV_XXXX". Connect to it with the app.
    // For BLE, the app will scan for BLE devices advertising the RainMaker service.
    // To trigger provisioning manually or to use specific schemes:
    wifi_prov_mgr_config_t prov_config = {
        .scheme = wifi_prov_scheme_softap, // or wifi_prov_scheme_ble
        .scheme_event_handler = WIFI_PROV_EVENT_HANDLER_NONE // Let RainMaker handle events
    };
    // Check if device is already provisioned. If not, start provisioning.
    bool provisioned = false;
    err = wifi_prov_mgr_is_provisioned(&provisioned);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Error checking provisioning status: %s", esp_err_to_name(err));
    }

    if (!provisioned) {
        ESP_LOGI(TAG, "Starting Wi-Fi Provisioning");
        // The RainMaker SDK typically starts provisioning if not already provisioned and connected.
        // If you need more control or are not using default RainMaker Wi-Fi handling:
        // ESP_ERROR_CHECK(wifi_prov_mgr_init(prov_config));
        // ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_1, NULL, "ESP_Light_Prov", NULL));
        // The RainMaker SDK usually handles this. If you have `esp_rmaker_wifi_init_sta()` or similar,
        // it might internally start provisioning if credentials are not found.
        // For this example, we assume RainMaker's default behavior will trigger provisioning if needed.
        // Or, you can hardcode Wi-Fi credentials for testing:
        /*
        wifi_config_t wifi_config = {
            .sta = {
                .ssid = "YOUR_SSID",
                .password = "YOUR_PASSWORD",
            },
        };
        esp_wifi_set_mode(WIFI_MODE_STA);
        esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
        esp_wifi_connect();
        */
    } else {
        ESP_LOGI(TAG, "Device already provisioned. Connecting to Wi-Fi...");
        // RainMaker SDK will attempt to connect using stored credentials.
    }
    // The esp_rmaker_start() should handle Wi-Fi connection if credentials are known or start provisioning.
}

main/Kconfig.projbuild (Create this file if it doesn’t exist):

Plaintext
menu "Example Configuration"
    config EXAMPLE_OUTPUT_GPIO
        int "Output GPIO for Light"
        default 2
        help
            GPIO number to use for controlling the light (e.g., an LED).
endmenu

sdkconfig.defaults (or configure via idf.py menuconfig):

Plaintext
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
CONFIG_NVS_ENCRYPTION=n # For simplicity in example, enable for production

# ESP RainMaker Configuration
CONFIG_ESP_RMAKER_SELF_CLAIM=n # Set to y for self-claiming during dev, n for app-based claiming
# CONFIG_ESP_RMAKER_ASSISTED_CLAIM=y # If using self_claim=n

# Wi-Fi Provisioning (Choose one)
CONFIG_WIFI_PROV_SCHEME_SOFTAP=y
# CONFIG_WIFI_PROV_SCHEME_BLE=n
CONFIG_WIFI_PROV_SECURITY_VERSION_1=y # For SoftAP provisioning password (if any)
# CONFIG_WIFI_PROV_SEC_MODE_NONE=n
# CONFIG_WIFI_PROV_SEC_MODE_WEP=n
# CONFIG_WIFI_PROV_SEC_MODE_WPA=n
# CONFIG_WIFI_PROV_SEC_MODE_WPA2=y
# CONFIG_WIFI_PROV_SEC_MODE_WPA_WPA2_MIXED=n


# Configure the GPIO for the light
CONFIG_EXAMPLE_OUTPUT_GPIO=2

# Wi-Fi Settings (if hardcoding, not recommended for production)
# CONFIG_ESP_WIFI_SSID="YourSSID"
# CONFIG_ESP_WIFI_PASSWORD="YourPassword"

# Ensure SNTP is enabled (RainMaker usually handles this if enable_time_sync is true)
CONFIG_LWIP_SNTP_ENABLE=y
CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1
CONFIG_LWIP_SNTP_SERVER_ADDRESS="pool.ntp.org"

# OTA Configuration
CONFIG_ESP_HTTPS_OTA_SEND_CUSTOM_HTTP_HEADER=y # Required by RainMaker OTA
CONFIG_ESP_RMAKER_OTA_ENABLE=y

# System Service
CONFIG_ESP_RMAKER_SYSTEM_SERVICE_ENABLE=y

Tip: For initial development, setting CONFIG_ESP_RMAKER_SELF_CLAIM=y in menuconfig (ESP RainMaker Config -> Enable Self Claiming) can be very helpful. The device will automatically claim itself, and you can see it in the Admin Dashboard without needing the phone app for claiming. Remember to set it back to n for app-based claiming.

Build Instructions

  1. Save all files.
  2. Open ESP-IDF Terminal in VS Code.
  3. Configure:idf.py menuconfig.
    • Set Example Configuration ---> Output GPIO for Light (e.g., GPIO 2 if you have an LED there).
    • Review ESP RainMaker Config options, especially Enable Self Claiming.
    • Review Wi-Fi Provisioning options if you are not hardcoding Wi-Fi.
  4. Build: idf.py build

Run/Flash/Observe Steps

  1. Connect ESP32 device.
  2. Flash: idf.py -p /dev/ttyUSB0 flash monitor (adjust port).
  3. Observe Logs:
    • You’ll see RainMaker initialization messages.
    • If Wi-Fi is not provisioned and self-claim is off, it will likely start Wi-Fi provisioning (e.g., SoftAP mode “PROV_XXXXXX” or BLE advertising).
    • If self-claim is on and Wi-Fi is configured/provisioned, it will connect and claim itself.
  4. Using the ESP RainMaker App:
    • If not self-claimed:
      • Open the ESP RainMaker app on your phone.
      • Log in or create an account.
      • Tap “+” to add a new device.
      • Follow the app’s instructions for provisioning (e.g., connect to the device’s SoftAP, provide your home Wi-Fi credentials).
      • Once provisioned and connected, the device should appear in your app.
    • Once the device appears in the app (or Admin Dashboard if self-claimed):
      • You should see controls for “SmartLight” (or whatever name you gave the device).
      • Toggle the power switch in the app. Observe the LED connected to OUTPUT_GPIO and the device logs.
      • Adjust the brightness slider. Observe the logs (the example doesn’t control a PWM LED for brightness, but logs the value).
  5. Test OTA:
    • Modify your app_main.c slightly (e.g., change a log message).
    • Increment the project version in your project’s CMakeLists.txt (e.g., project(my_project VERSION 1.1)).
    • Rebuild: idf.py build.
    • Go to the ESP RainMaker Admin Dashboard (https://dashboard.rainmaker.espressif.com/).
    • Navigate to “OTA Update”. Upload the new build/rainmaker_light.bin firmware.
    • Push the OTA update to your device. Observe the device logs for OTA progress and reboot.

Variant Notes

  • ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: All these variants have Wi-Fi and are well-supported by ESP RainMaker. The RainMaker SDK is optimized for ESP32 series chips.
    • Resource usage is generally manageable, but complex applications with many devices/parameters on more constrained variants (like ESP32-C3 with less RAM) should be tested for memory and performance.
    • BLE provisioning is available on variants that support BLE (ESP32, S3, C3, C6). SoftAP provisioning works on all Wi-Fi variants.
  • ESP32-H2: This variant has IEEE 802.15.4 (Thread/Zigbee) and BLE, but no built-in Wi-Fi.
    • Direct ESP RainMaker integration (which relies on Wi-Fi for cloud connectivity) is not possible for the ESP32-H2 acting as a standalone device connecting to the RainMaker cloud.
    • An ESP32-H2 could potentially be part of a RainMaker solution if it acts as an end-device in a Thread/Zigbee network, with another ESP32 Wi-Fi variant (e.g., ESP32-C6, ESP32-S3) acting as a border router/gateway. This gateway would then bridge the 802.15.4 devices to the RainMaker cloud. This is a more advanced setup and would require custom gateway logic or specific support in RainMaker for such topologies if available.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Wi-Fi Provisioning Fails Device SoftAP (PROV_XXXX) not visible, BLE provisioning fails, app cannot send credentials, or device fails to connect to the provided Wi-Fi. Logs: WIFI_PROV_CRED_FAIL. Verify Wi-Fi credentials are correct. Ensure ESP32 is trying to connect to a 2.4GHz network.
Check provisioning scheme in menuconfig (WIFI_PROV_SCHEME_SOFTAP or _BLE).
For SoftAP, ensure phone is connected to the PROV_XXXX AP during provisioning.
Check device logs for specific Wi-Fi connection error reasons (e.g., auth error, AP not found).
Try a different Wi-Fi network or router for testing.
Device Not Appearing in App/Dashboard (After Provisioning) Wi-Fi provisioning seems successful, device connects to Wi-Fi, but doesn’t appear in the RainMaker app or dashboard. Claiming might have failed. Check device logs for esp_rmaker_start() success and any cloud connection errors (MQTT, TLS).
Ensure device has internet access after Wi-Fi connection.
If using Self Claim (CONFIG_ESP_RMAKER_SELF_CLAIM=y), verify it’s enabled. If not, ensure app-based claiming process is completed.
Check RainMaker dashboard for any error logs related to device registration or authentication.
Parameter Updates Not Reflected / Callback Not Triggered Changing a parameter in the app (e.g., toggling power) doesn’t affect the device, or device state doesn’t update in the app. write_callback not being called. Ensure the write_callback is correctly registered with the device using esp_rmaker_device_add_cb().
Verify parameter names in the callback (e.g., ESP_RMAKER_DEF_POWER_NAME) match those defined for the device.
Log extensively within the callback to see if it’s entered and what data it receives.
After device changes state locally, call esp_rmaker_param_update_and_report() to inform the cloud and app.
Incorrect Device Model in App App shows wrong controls, missing parameters, or incorrect UI elements for the device. Verify the device model definition in firmware: esp_rmaker_node_init(), esp_rmaker_device_create(), esp_rmaker_param_create() calls.
Ensure correct standard types (e.g., esp_rmaker_lightbulb_device_create) or custom types are used.
Use esp_rmaker_param_add_ui_type() for custom parameters to guide app UI rendering.
After firmware changes to the model, the device must reconnect to RainMaker for the cloud to get the updated model. Sometimes clearing app cache or reinstalling the app helps refresh the UI.
OTA Update Failures OTA process starts but fails. Device logs might show errors related to download, signature verification, or flash write. Ensure esp_rmaker_ota_enable() is called with correct config (e.g., default server cert).
Verify sufficient free flash space for the new firmware image (check partition table).
Ensure a stable Wi-Fi connection during OTA.
If using firmware signing, ensure keys and signing process are correct.
Check RainMaker dashboard OTA status and device logs for specific error codes.
Self-Claiming Not Working CONFIG_ESP_RMAKER_SELF_CLAIM=y is set, device connects to Wi-Fi, but doesn’t appear in dashboard or app automatically. Ensure the device successfully connects to the RainMaker cloud after Wi-Fi. Check logs for MQTT/TLS errors.
Self-claiming requires the device to reach the cloud. Internet connectivity is essential.
Verify no previous claim exists for the device ID (MAC based) in your account that might conflict. A factory reset (nvs_flash_erase()) on device might be needed in some cases.
App Shows “Device Offline” Intermittently Device appears online then offline frequently in the app, even if logs show it’s connected. Check Wi-Fi signal stability. MQTT keep-alives might be too short or network too lossy.
Ensure your main application loop or tasks are not blocking the RainMaker SDK’s internal processing for too long.
Monitor for device reboots or crashes.
Build Errors Related to RainMaker Component Compilation fails with errors finding RainMaker headers or linking RainMaker SDK functions. Ensure espressif/esp-rainmaker is correctly added as a dependency (e.g., via idf.py add-dependency or in idf_component.yml).
Run idf.py reconfigure or idf.py fullclean && idf.py build to ensure components are correctly downloaded and build system is updated.

Exercises

  1. Multi-Parameter Device:
    • Extend the smart light example to include a “color” parameter (e.g., a string for color name like “red”, “blue”, “green”, or an integer for RGB).
    • Implement the write_callback to handle this new color parameter and log the selected color.
    • Use esp_rmaker_param_add_ui_type() to suggest a UI element (e.g., ESP_RMAKER_UI_DROPDOWN or ESP_RMAKER_UI_TEXT).
  2. Sensor Data Reporting:
    • Create a new RainMaker device representing a temperature sensor (e.g., using esp_rmaker_temp_sensor_device_create).
    • Periodically read the ESP32’s internal temperature sensor (using temprature_sens_read() after calibration if needed, or simulate data).
    • Report this temperature to RainMaker using esp_rmaker_param_update_and_report() on the temperature parameter. View the data in the app/dashboard.
  3. Custom Device Type:
    • Define a completely custom device, for example, a “Fan Controller” with parameters for “Speed” (integer, 0-3) and “Oscillate” (boolean).
    • Implement the write_callback to handle these custom parameters.
    • Add appropriate UI type hints for the app.
  4. Scheduling Implementation:
    • Using the smart light from the main example, use the ESP RainMaker app to create a schedule (e.g., turn the light ON at 7 PM and OFF at 11 PM).
    • Observe the device logs to see if the schedule triggers the write_callback at the specified times. (Ensure esp_rmaker_schedule_enable() is called).

Summary

  • ESP RainMaker offers an end-to-end solution (SDK, Cloud, Apps) for rapid IoT development with ESP32.
  • Key concepts include Nodes, Devices, Parameters, and Services, which define the device model.
  • The RainMaker SDK abstracts MQTT communication, simplifying cloud connectivity.
  • Mobile apps provide user-friendly interfaces for Wi-Fi provisioning, device claiming, control, and OTA updates.
  • The Admin Dashboard is used for device and firmware management.
  • RainMaker supports standard device types for consistent UI and custom types for flexibility.
  • OTA updates and scheduling are built-in services.
  • Most Wi-Fi enabled ESP32 variants are well-suited for RainMaker.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top