Merge branch 'example/add_rainmaker_bridge' into 'main'

example/bridge: Add rainmaker bridge example

See merge request app-frameworks/esp-matter!1237
This commit is contained in:
Shu Chen
2025-12-15 06:38:09 +00:00
31 changed files with 4000 additions and 1 deletions
@@ -245,6 +245,38 @@ static esp_err_t plugin_init_callback_endpoint(endpoint_t *endpoint)
return ESP_OK;
}
static esp_err_t cluster_server_init(endpoint_t *endpoint)
{
if (!endpoint) {
ESP_LOGE(TAG, "endpoint cannot be NULL");
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(TAG, "Cluster server init for the new added endpoint");
/* Take lock if not already taken */
lock::status_t lock_status = lock::chip_stack_lock(portMAX_DELAY);
VerifyOrReturnError(lock_status != lock::FAILED, ESP_FAIL, ESP_LOGE(TAG, "Could not get task context"));
cluster_t *cluster = cluster::get_first(endpoint);
while (cluster) {
uint8_t flags = cluster::get_flags(cluster);
cluster::initialization_callback_t init_callback = cluster::get_init_callback(cluster);
if (init_callback) {
init_callback(endpoint::get_id(endpoint));
}
if ((flags & CLUSTER_FLAG_SERVER) && (flags & CLUSTER_FLAG_INIT_FUNCTION)) {
cluster::function_cluster_init_t init_function =
(cluster::function_cluster_init_t)cluster::get_function(cluster, CLUSTER_FLAG_INIT_FUNCTION);
if (init_function) {
init_function(endpoint::get_id(endpoint));
}
}
cluster = cluster::get_next(cluster);
}
if (lock_status == lock::SUCCESS) {
lock::chip_stack_unlock();
}
return ESP_OK;
}
static bridge_device_type_callback_t device_type_callback;
esp_err_t set_device_type(device_t *bridged_device, uint32_t device_type_id, void *priv_data)
@@ -258,6 +290,9 @@ esp_err_t set_device_type(device_t *bridged_device, uint32_t device_type_id, voi
err = device_type_callback(bridged_device->endpoint, device_type_id, priv_data);
if (err != ESP_OK)
return err;
cluster_server_init(bridged_device->endpoint);
return plugin_init_callback_endpoint(bridged_device->endpoint);
}
@@ -412,7 +447,7 @@ esp_err_t initialize(node_t *node, bridge_device_type_callback_t device_type_cb)
ESP_LOGE(TAG, "node could not be NULL");
return ESP_ERR_INVALID_ARG;
}
if (!device_type_cb) {
ESP_LOGE(TAG, "device_type_callback cannot be NULL");
return ESP_ERR_INVALID_ARG;
+6
View File
@@ -134,3 +134,9 @@ examples/sensors:
- if: IDF_TARGET in ["esp32c3"]
temporary: true
reason: the other targets are not tested yet
examples/bridge_apps/esp_rainmaker_bridge:
enable:
- if: IDF_TARGET in ["esp32s3"]
temporary: true
reason: the other targets are not tested yet
@@ -0,0 +1,33 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
if(NOT DEFINED ENV{ESP_MATTER_PATH})
message(FATAL_ERROR "Please set ESP_MATTER_PATH to the path of esp-matter repo")
endif(NOT DEFINED ENV{ESP_MATTER_PATH})
set(PROJECT_VER "1.0")
set(PROJECT_VER_NUMBER 1)
set(ESP_MATTER_PATH $ENV{ESP_MATTER_PATH})
set(MATTER_SDK_PATH ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip)
# This should be done before using the IDF_TARGET variable.
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS
"${ESP_MATTER_PATH}/examples/common"
"${MATTER_SDK_PATH}/config/esp32/components"
"${ESP_MATTER_PATH}/components"
${extra_components_dirs_append}
$ENV{IDF_PATH}/examples/common_components/protocol_examples_common
"./example_components/rainmaker_api"
"./example_components/app_network")
project(rainmaker_bridge)
idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++17;-Os;-DCHIP_HAVE_CONFIG_H;-fpermissive;-Wno-overloaded-virtual" APPEND)
idf_build_set_property(C_COMPILE_OPTIONS "-Os" APPEND)
# For RISCV chips, project_include.cmake sets -Wno-format, but does not clear various
# flags that depend on -Wformat
idf_build_set_property(COMPILE_OPTIONS "-Wno-format-nonliteral;-Wno-format-security;-Wno-error=cpp" APPEND)
@@ -0,0 +1,140 @@
# Rainmaker Bridge
This example demonstrates a Matter-Rainmaker Bridge that bridges Rainmaker devices to the Matter fabric.
The Matter Bridge device runs on ESP32-S3.
See the [docs](https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html) for more information about building and flashing the firmware.
💡 Important: `create_bridge_devices` callback can be used to add data model elements (e.g., attributes, commands, etc.) to the bridge endpoint.
## 1. Additional Environment Setup
### 1.1 Hardware connection
This example runs on ESP32-S3 devkit by default.
### 1.2 Build and flash the Bridge (ESP32-S3)
For Standalone DevKit boards without Thread Border Router:
```
cd ${ESP_MATTER_PATH}/examples/bridge_apps/esp_rainmaker_bridge
idf.py set-target esp32s3
idf.py -p <port> build flash
```
For ***ESP Thread Border Router/Zigbee Gateway Board*** with Thread Border Router:
```
cd ${ESP_MATTER_PATH}/examples/bridge_apps/esp_rainmaker_bridge
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.otbr" set-target esp32s3
idf.py -p <port> build flash
```
## 2. Commissioning Setup
### 2.1 Set up Rainmaker Bridge using Rainmaker App
#### 2.1.1 Pair the bridge using Rainmaker App
Use the Rainmaker App to scan the QR code printed in the device log to pair the bridge.
```
I (16104) NimBLE: GAP procedure initiated: advertise;
I (16104) NimBLE: disc_mode=2
I (16114) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=256 adv_itvl_max=256
I (16124) NimBLE:
I (16104) network_prov_mgr: Provisioning started with service name : PROV_54a900
I (16134) app_wifi: Provisioning started
I (16134) app_network: Scan this QR code from the ESP RainMaker phone app for Provisioning.
I (16144) QRCODE: Encoding below text with ECC LVL 0 & QR Code Version 5
I (16144) QRCODE: {"ver":"v1","name":"PROV_54a900","pop":"47d4fb71","transport":"ble"}
█▀▀▀▀▀█ █ ▄▄█ ▀▀ ▄█ ▀▄ █▀▀▀▀▀█
█ ███ █ ██▄ █▄ ▄▀▀▄ ▀▀▄██ █ ███ █
█ ▀▀▀ █ ▄██ ▀▀█▀█▄▀▄ ▀▀▀▀ █ ▀▀▀ █
▀▀▀▀▀▀▀ ▀ █▄▀ ▀ ▀▄▀▄█▄▀▄█ ▀▀▀▀▀▀▀
██ ▄█▀▀▄▄ ▀ ▀ ▀█▄▀▄▀▄▀▄▄▄▄ █ ▀█▀▀
▄▀█▀█▀▀ █▀ ▄▀ ██▄ ▄██▀██▀▄██▀█
▄█▀█▄▀▀▀▄ ▄▀▄▄█▄█▀▀▀▄▀▄▀ ▄▀▄▀ ▄▄▀
▀█ ▀▄▀▄█▀██▀▀ ▀▄▄█▄ ██▀▄█ ▀ █▀▀▄
▀█▀▄█▀▀██▀ ▀▄▄▄█▀▀▀█▀ █▀▄▀▀ ▀
▄█▄▀▀▀▀ ▄██ ▄▀ ▀▀█▄▄ ▄█▀█ ▄█▄█▀▀▄
███ █▀▀█▀ ▀▄ ▄█▄▀█ ▄█ ▀▀▀▀ ▀ ▄█▀
█▀▀ ▀▄▄█▀ ▀█▄▄ ▄ █▄▀ █▀▀▀▀▄▄
▀▀▀▀ ▀▀▀█ █ ▀▄▄▄▄██▀█▀ ▄█▀▀▀██▄▀
█▀▀▀▀▀█ ▄▀▀ ▄ █▀██▀ ▄█▀█ ▀ ██▀▀
█ ███ █ ▀▀▄▀█▄█▄█▀█▀ ▀█ ███▀█▀▄▄█
█ ▀▀▀ █ ▄▄███▀ █▀▄ ▄███▀ ▀█▀▄ ▀
▀▀▀▀▀▀▀ ▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀
I (16344) app_network: If QR code is not visible, copy paste the below URL in a browser.
https://rainmaker.espressif.com/qrcode.html?data={"ver":"v1","name":"PROV_54a900","pop":"47d4fb71","transport":"ble"}
I (16364) app_network: Provisioning will auto stop after 30 minute(s).
```
#### 2.1.2 Update Rainmaker Controller Params
After successful Bridge pairing, it is necessary to use the parameter update function to grant the Bridge the Rainmaker controller permission. This enables the Bridge to have the authority to control other Rainmaker devices.
![main](./docs/1_setup.png)
#### 2.1.3 Update Thread Dataset
If you need to bridge a Rainmaker over Thread device, you must update the Thread Border Router dataset after the Bridge is successfully paired.
![main](./docs/1_setup.png) ![main](./docs/3_dataset.png)
### 2.2 Pair Rainmaker end devices using Rainmaker App
Follow this [guide](https://github.com/espressif/esp-rainmaker/blob/master/README.md) to set up a Rainmaker device.
### 2.3 Pair the bridge using chip-tool through onnetwork method
Use the command below to pair the bridge.
```
./chip-tool pairing onnetwork 0x1234 20202021
```
### 2.4 Control the bulb with chip-tool
Now you can control the Rainmaker device using chip-tool.
```
./chip-tool onoff toggle 0x1234 0x2
```
## 3. Device Performance
### 3.1 Memory usage
**Config: Enable SPIRAM(2MB), 8MB flash, enable Thread Border Router**
The following is the Memory and Flash Usage.
- `Bootup` == Device just finished booting up. Device is not
commissioned or connected to wifi yet.
- `After Commissioning` == Device is connected to wifi and is also
commissioned and is rebooted.
- `After Adding a Bridged device` == A Rainmaker Color Light is added
on the Bridge.
- device used: ESP32-S3-DevKitC-1
- tested on:
[d0faa92c](https://github.com/espressif/esp-matter/commit/d0faa92c9336205de21a4b325c956893736c4d64)
(2025-12-11)
- IDF: v5.4.1 [4c2820d3](https://github.com/espressif/esp-idf/tree/v5.4.1)
| | Bootup | After Rainmaker Commissioning | After Matter Commissioning | After Adding a Bridged device |
|:- |:-: |:-: |:-: |:-: |
|**Free Internal Memory** |165KB |168KB |170KB |170KB |
|**Free SPIRAM Memory** |1886KB |1823KB |1858KB |1853KB |
**Flash Usage**: Firmware binary size: 1.98MB
This should give you a good idea about the amount of free memory that is
available for you to run your application's code.
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,10 @@
set(priv_req qrcode nvs_flash esp_event rmaker_common vfs network_provisioning openthread)
idf_component_register(SRCS "app_wifi_internal.c" "app_network.c"
INCLUDE_DIRS "."
PRIV_INCLUDE_DIRS "private_include"
PRIV_REQUIRES ${priv_req})
if(CONFIG_APP_WIFI_SHOW_DEMO_INTRO_TEXT)
target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-D RMAKER_DEMO_PROJECT_NAME=\"${CMAKE_PROJECT_NAME}\"")
endif()
@@ -0,0 +1,84 @@
menu "ESP RainMaker App Wi-Fi Provisioning"
config APP_NETWORK_PROV_SHOW_QR
bool "Show provisioning QR code"
default y
help
Show the QR code for provisioning.
config APP_NETWORK_PROV_MAX_POP_MISMATCH
int
default 5
range 0 20
prompt "Max wrong pop attempts allowed"
help
Set the maximum wrong pop attempts allowed before stopping provisioning.
Set 0 for the feature to be disabled.
This safeguards the device from brute-force attempt by limiting the wrong pop allowed.
choice APP_NETWORK_PROV_TRANSPORT
bool "Provisioning Transport method"
default APP_NETWORK_PROV_TRANSPORT_BLE
help
Wi-Fi/Network provisioning component offers both, SoftAP and BLE transports. Choose any one.
config APP_NETWORK_PROV_TRANSPORT_SOFTAP
bool "Soft AP"
depends on !IDF_TARGET_ESP32H2
config APP_NETWORK_PROV_TRANSPORT_BLE
bool "BLE"
select BT_ENABLED
depends on !IDF_TARGET_ESP32S2
endchoice
config APP_NETWORK_PROV_TRANSPORT
int
default 1 if APP_NETWORK_PROV_TRANSPORT_SOFTAP
default 2 if APP_NETWORK_PROV_TRANSPORT_BLE
config APP_NETWORK_RESET_PROV_ON_FAILURE
bool
default y
prompt "Reset provisioned credentials and state machine after session failure"
help
Enable reseting provisioned credentials and state machine after session failure.
This will restart the provisioning service after retries are exhausted.
config APP_NETWORK_PROV_MAX_RETRY_CNT
int
default 5
prompt "Max retries before reseting provisioning state machine"
depends on APP_NETWORK_RESET_PROV_ON_FAILURE
help
Set the Maximum retry to avoid reconnecting to an inexistent network or if credentials
are misconfigured. Provisioned credentials are erased and internal state machine
is reset after this threshold is reached.
config APP_NETWORK_SHOW_DEMO_INTRO_TEXT
bool "Show intro text for demos"
default n
help
Show some intro text for demos in order to help users understand more about ESP RainMaker.
config APP_NETWORK_PROV_TIMEOUT_PERIOD
int "Provisioning Timeout"
default 30
help
Timeout (in minutes) after which the provisioning will auto stop. A reboot will be required
to restart provisioning. It is always recommended to set this to some non zero value, especially
if you are not using PoP. Set to 0 if you do not want provisioning to auto stop.
config APP_NETWORK_PROV_NAME_PREFIX
string "Provisioning Name Prefix"
default "PROV"
help
Provisioning Name Prefix.
config APP_WIFI_PROV_COMPAT
bool "Stay compatible with App Wi-Fi component"
depends on ESP_RMAKER_NETWORK_OVER_WIFI
default y
help
Stay compatible with Previous App Wi-Fi component
endmenu
@@ -0,0 +1,435 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <sdkconfig.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_idf_version.h>
#include <esp_rmaker_utils.h>
#include <app_network.h>
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
#include <app_wifi_internal.h>
#include <esp_netif_types.h>
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
#include <esp_mac.h>
#include <network_provisioning/manager.h>
#ifdef CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE
#include <network_provisioning/scheme_ble.h>
#else /* CONFIG_APP_NETWORK_PROV_TRANSPORT_SOFTAP */
#include <network_provisioning/scheme_softap.h>
#endif /* CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE */
#ifdef CONFIG_APP_NETWORK_PROV_SHOW_QR
#include <qrcode.h>
#endif
#include <nvs.h>
#include <nvs_flash.h>
#include <esp_timer.h>
#include <app_network.h>
ESP_EVENT_DEFINE_BASE(APP_NETWORK_EVENT);
static const char *TAG = "app_network";
static const int NETWORK_CONNECTED_EVENT = BIT0;
static EventGroupHandle_t network_event_group;
#define PROV_QR_VERSION "v1"
#define PROV_TRANSPORT_SOFTAP "softap"
#define PROV_TRANSPORT_BLE "ble"
#define QRCODE_BASE_URL "https://rainmaker.espressif.com/qrcode.html"
#define CREDENTIALS_NAMESPACE "rmaker_creds"
#define RANDOM_NVS_KEY "random"
#define POP_STR_SIZE 9
static esp_timer_handle_t prov_stop_timer;
/* Timeout period in minutes */
#define APP_NETWORK_PROV_TIMEOUT_PERIOD CONFIG_APP_NETWORK_PROV_TIMEOUT_PERIOD
/* Autofetch period in micro-seconds */
static uint64_t prov_timeout_period = (APP_NETWORK_PROV_TIMEOUT_PERIOD * 60 * 1000000LL);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 3)
#define APP_PROV_STOP_ON_CREDS_MISMATCH
#elif (CONFIG_APP_NETWORK_PROV_MAX_RETRY_CNT > 0)
#warning "Provisioning window stop on max credentials failures, needs IDF version >= 5.1.3"
#endif
#ifdef CONFIG_APP_NETWORK_SHOW_DEMO_INTRO_TEXT
#define ESP_RAINMAKER_GITHUB_EXAMPLES_PATH "https://github.com/espressif/esp-rainmaker/blob/master/examples"
#define ESP_RAINMAKER_INTRO_LINK "https://rainmaker.espressif.com"
#define ESP_RMAKER_PHONE_APP_LINK "http://bit.ly/esp-rmaker"
char esp_rainmaker_ascii_art[] = \
" ______ _____ _____ _____ _____ _ _ __ __ _ ________ _____\n"\
" | ____|/ ____| __ \\ | __ \\ /\\ |_ _| \\ | | \\/ | /\\ | |/ / ____| __ \\\n"\
" | |__ | (___ | |__) | | |__) | / \\ | | | \\| | \\ / | / \\ | ' /| |__ | |__) |\n"\
" | __| \\___ \\| ___/ | _ / / /\\ \\ | | | . ` | |\\/| | / /\\ \\ | < | __| | _ /\n"\
" | |____ ____) | | | | \\ \\ / ____ \\ _| |_| |\\ | | | |/ ____ \\| . \\| |____| | \\ \\\n"\
" |______|_____/|_| |_| \\_\\/_/ \\_\\_____|_| \\_|_| |_/_/ \\_\\_|\\_\\______|_| \\_\\\n";
static void intro_print(bool provisioned)
{
printf("####################################################################################################\n");
printf("%s\n", esp_rainmaker_ascii_art);
printf("Welcome to ESP RainMaker %s demo application!\n", RMAKER_DEMO_PROJECT_NAME);
if (!provisioned) {
printf("Follow these steps to get started:\n");
printf("1. Download the ESP RainMaker phone app by visiting this link from your phone's browser:\n\n");
printf(" %s\n\n", ESP_RMAKER_PHONE_APP_LINK);
printf("2. Sign up and follow the steps on screen to add the device to your Wi-Fi/Thread network.\n");
printf("3. You are now ready to use the device and control it locally as well as remotely.\n");
printf(" You can also use the Boot button on the board to control your device.\n");
}
printf("\nIf you want to reset network credentials, or reset to factory, press and hold the Boot button.\n");
printf("\nThis application uses ESP RainMaker, which is based on ESP IDF.\n");
printf("Check out the source code for this application here:\n %s/%s\n",
ESP_RAINMAKER_GITHUB_EXAMPLES_PATH, RMAKER_DEMO_PROJECT_NAME);
printf("\nPlease visit %s for additional information.\n\n", ESP_RAINMAKER_INTRO_LINK);
printf("####################################################################################################\n");
}
#else
static void intro_print(bool provisioned)
{
/* Do nothing */
}
#endif /* !APP_NETWORK_SHOW_DEMO_INTRO_TEXT */
#ifdef CONFIG_APP_NETWORK_PROV_SHOW_QR
static esp_err_t qrcode_display(const char *text)
{
#define MAX_QRCODE_VERSION 5
esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT();
cfg.max_qrcode_version = MAX_QRCODE_VERSION;
return esp_qrcode_generate(&cfg, text);
}
#endif
static uint8_t *custom_mfg_data = NULL;
static size_t custom_mfg_data_len = 0;
esp_err_t app_network_set_custom_mfg_data(uint16_t device_type, uint8_t device_subtype)
{
int8_t mfg_data[] = {MFG_DATA_HEADER, MGF_DATA_APP_ID, MFG_DATA_VERSION, MFG_DATA_CUSTOMER_ID};
size_t mfg_data_len = sizeof(mfg_data) + 4; // 4 bytes of device type, subtype, and extra-code
custom_mfg_data = (uint8_t *)MEM_ALLOC_EXTRAM(mfg_data_len);
if (custom_mfg_data == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory to custom mfg data");
return ESP_ERR_NO_MEM;
}
memcpy(custom_mfg_data, mfg_data, sizeof(mfg_data));
custom_mfg_data[8] = 0xff & (device_type >> 8);
custom_mfg_data[9] = 0xff & device_type;
custom_mfg_data[10] = device_subtype;
custom_mfg_data[11] = 0;
custom_mfg_data_len = mfg_data_len;
ESP_LOG_BUFFER_HEXDUMP("tag", custom_mfg_data, mfg_data_len, 3);
return ESP_OK;
}
static void app_network_print_qr(const char *name, const char *pop, const char *transport)
{
if (!name || !transport) {
ESP_LOGW(TAG, "Cannot generate QR code payload. Data missing.");
return;
}
char payload[150];
if (pop) {
snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \
",\"pop\":\"%s\",\"transport\":\"%s\"}",
PROV_QR_VERSION, name, pop, transport);
} else {
snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\"" \
",\"transport\":\"%s\"}",
PROV_QR_VERSION, name, transport);
}
#ifdef CONFIG_APP_NETWORK_PROV_SHOW_QR
ESP_LOGI(TAG, "Scan this QR code from the ESP RainMaker phone app for Provisioning.");
qrcode_display(payload);
#endif /* CONFIG_APP_NETWORK_PROV_SHOW_QR */
ESP_LOGI(TAG, "If QR code is not visible, copy paste the below URL in a browser.\n%s?data=%s", QRCODE_BASE_URL, payload);
esp_event_post(APP_NETWORK_EVENT, APP_NETWORK_EVENT_QR_DISPLAY, payload, strlen(payload) + 1, portMAX_DELAY);
}
/* Free random_bytes after use only if function returns ESP_OK */
static esp_err_t read_random_bytes_from_nvs(uint8_t **random_bytes, size_t *len)
{
nvs_handle handle;
esp_err_t err;
*len = 0;
if ((err = nvs_open_from_partition(CONFIG_ESP_RMAKER_FACTORY_PARTITION_NAME, CREDENTIALS_NAMESPACE,
NVS_READONLY, &handle)) != ESP_OK) {
ESP_LOGD(TAG, "NVS open for %s %s %s failed with error %d", CONFIG_ESP_RMAKER_FACTORY_PARTITION_NAME, CREDENTIALS_NAMESPACE, RANDOM_NVS_KEY, err);
return ESP_FAIL;
}
if ((err = nvs_get_blob(handle, RANDOM_NVS_KEY, NULL, len)) != ESP_OK) {
ESP_LOGD(TAG, "Error %d. Failed to read key %s.", err, RANDOM_NVS_KEY);
nvs_close(handle);
return ESP_ERR_NOT_FOUND;
}
*random_bytes = calloc(*len, 1);
if (*random_bytes) {
nvs_get_blob(handle, RANDOM_NVS_KEY, *random_bytes, len);
nvs_close(handle);
return ESP_OK;
}
nvs_close(handle);
return ESP_ERR_NO_MEM;
}
static char *custom_pop;
esp_err_t app_network_set_custom_pop(const char *pop)
{
/* NULL PoP is not allowed here. Use POP_TYPE_NONE instead. */
if (!pop) {
return ESP_ERR_INVALID_ARG;
}
/* Freeing up the PoP in case it is already allocated */
if (custom_pop) {
free(custom_pop);
custom_pop = NULL;
}
custom_pop = strdup(pop);
if (!custom_pop) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
static esp_err_t get_device_service_name(char *service_name, size_t max)
{
uint8_t *nvs_random = NULL;
const char *ssid_prefix = CONFIG_APP_NETWORK_PROV_NAME_PREFIX;
size_t nvs_random_size = 0;
if ((read_random_bytes_from_nvs(&nvs_random, &nvs_random_size) != ESP_OK) || nvs_random_size < 3) {
uint8_t mac_addr[6];
esp_read_mac(mac_addr, ESP_MAC_BASE);
snprintf(service_name, max, "%s_%02x%02x%02x", ssid_prefix, mac_addr[3], mac_addr[4], mac_addr[5]);
} else {
snprintf(service_name, max, "%s_%02x%02x%02x", ssid_prefix, nvs_random[nvs_random_size - 3],
nvs_random[nvs_random_size - 2], nvs_random[nvs_random_size - 1]);
}
if (nvs_random) {
free(nvs_random);
}
return ESP_OK;
}
static char *get_device_pop(app_network_pop_type_t pop_type)
{
if (pop_type == POP_TYPE_NONE) {
return NULL;
} else if (pop_type == POP_TYPE_CUSTOM) {
if (!custom_pop) {
ESP_LOGE(TAG, "Custom PoP not set. Please use app_wifi_set_custom_pop().");
return NULL;
}
return strdup(custom_pop);
}
char *pop = calloc(1, POP_STR_SIZE);
if (!pop) {
ESP_LOGE(TAG, "Failed to allocate memory for PoP.");
return NULL;
}
if (pop_type == POP_TYPE_MAC) {
uint8_t mac_addr[6];
esp_err_t err = esp_read_mac(mac_addr, ESP_MAC_BASE);
if (err == ESP_OK) {
snprintf(pop, POP_STR_SIZE, "%02x%02x%02x%02x", mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
return pop;
} else {
ESP_LOGE(TAG, "Failed to get MAC address to generate PoP.");
goto pop_err;
}
} else if (pop_type == POP_TYPE_RANDOM) {
uint8_t *nvs_random = NULL;
size_t nvs_random_size = 0;
if ((read_random_bytes_from_nvs(&nvs_random, &nvs_random_size) != ESP_OK) || nvs_random_size < 4) {
ESP_LOGE(TAG, "Failed to read random bytes from NVS to generate PoP.");
if (nvs_random) {
free(nvs_random);
}
goto pop_err;
} else {
snprintf(pop, POP_STR_SIZE, "%02x%02x%02x%02x", nvs_random[0], nvs_random[1], nvs_random[2], nvs_random[3]);
free(nvs_random);
return pop;
}
}
pop_err:
free(pop);
return NULL;
}
static void network_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
#ifdef APP_PROV_STOP_ON_CREDS_MISMATCH
static int failed_cnt = 0;
#endif
#ifdef APP_PROV_STOP_ON_CREDS_MISMATCH
if (event_base == PROTOCOMM_SECURITY_SESSION_EVENT) {
switch (event_id) {
case PROTOCOMM_SECURITY_SESSION_SETUP_OK:
ESP_LOGI(TAG, "Secured session established!");
break;
case PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS:
/* fall-through */
case PROTOCOMM_SECURITY_SESSION_CREDENTIALS_MISMATCH:
ESP_LOGE(TAG, "Received incorrect PoP or invalid security params! event: %d", (int) event_id);
if (CONFIG_APP_NETWORK_PROV_MAX_POP_MISMATCH &&
(++failed_cnt >= CONFIG_APP_NETWORK_PROV_MAX_POP_MISMATCH)) {
/* stop provisioning for security reasons */
network_prov_mgr_stop_provisioning();
ESP_LOGW(TAG, "Max PoP attempts reached! Provisioning disabled for security reasons. Please reboot device to restart provisioning");
esp_event_post(APP_NETWORK_EVENT, APP_NETWORK_EVENT_PROV_CRED_MISMATCH, NULL, 0, portMAX_DELAY);
}
break;
default:
break;
}
}
#endif /* APP_PROV_STOP_ON_CREDS_MISMATCH */
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Connected with IP Address:" IPSTR, IP2STR(&event->ip_info.ip));
/* Signal main application to continue execution */
xEventGroupSetBits(network_event_group, NETWORK_CONNECTED_EVENT);
}
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_END) {
if (prov_stop_timer) {
esp_timer_stop(prov_stop_timer);
esp_timer_delete(prov_stop_timer);
prov_stop_timer = NULL;
}
network_prov_mgr_deinit();
}
}
void app_network_init()
{
/* Initialize the event loop, if not done already. */
esp_err_t err = esp_event_loop_create_default();
/* If the default event loop is already initialized, we get ESP_ERR_INVALID_STATE */
if (err != ESP_OK) {
if (err == ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "Event loop creation failed with ESP_ERR_INVALID_STATE. Proceeding since it must have been created elsewhere.");
} else {
ESP_LOGE(TAG, "Failed to create default event loop, err = %x", err);
return;
}
}
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
ESP_ERROR_CHECK(wifi_init());
#endif
network_event_group = xEventGroupCreate();
#ifdef APP_PROV_STOP_ON_CREDS_MISMATCH
ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_SECURITY_SESSION_EVENT, ESP_EVENT_ANY_ID, &network_event_handler, NULL));
#endif
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &network_event_handler, NULL));
#endif
ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_PROV_EVENT, NETWORK_PROV_END, &network_event_handler, NULL));
}
static void app_network_prov_stop(void *priv)
{
ESP_LOGW(TAG, "Provisioning timed out. Please reboot device to restart provisioning.");
network_prov_mgr_stop_provisioning();
esp_event_post(APP_NETWORK_EVENT, APP_NETWORK_EVENT_PROV_TIMEOUT, NULL, 0, portMAX_DELAY);
}
esp_err_t app_network_start_timer(void)
{
if (prov_timeout_period == 0) {
return ESP_OK;
}
esp_timer_create_args_t prov_stop_timer_conf = {
.callback = app_network_prov_stop,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "app_wifi_prov_stop_tm"
};
if (esp_timer_create(&prov_stop_timer_conf, &prov_stop_timer) == ESP_OK) {
esp_timer_start_once(prov_stop_timer, prov_timeout_period);
ESP_LOGI(TAG, "Provisioning will auto stop after %d minute(s).",
APP_NETWORK_PROV_TIMEOUT_PERIOD);
return ESP_OK;
} else {
ESP_LOGE(TAG, "Failed to create Provisioning auto stop timer.");
}
return ESP_FAIL;
}
esp_err_t app_network_start(app_network_pop_type_t pop_type)
{
/* Do we want a proof-of-possession (ignored if Security 0 is selected):
* - this should be a string with length > 0
* - NULL if not used
*/
char *pop = get_device_pop(pop_type);
if ((pop_type != POP_TYPE_NONE) && (pop == NULL)) {
return ESP_ERR_NO_MEM;
}
/* What is the Device Service Name that we want
* This translates to :
* - device name when scheme is network_prov_scheme_ble/wifi_prov_scheme_ble
*/
char service_name[12];
get_device_service_name(service_name, sizeof(service_name));
/* What is the service key (Wi-Fi password)
* NULL = Open network
* This is ignored when scheme is network_prov_scheme_ble/wifi_prov_scheme_ble
*/
const char *service_key = NULL;
esp_err_t err = ESP_OK;
bool provisioned = false;
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
err = wifi_start(pop, service_name, service_key, custom_mfg_data, custom_mfg_data_len, &provisioned);
#endif
if (err != ESP_OK) {
free(pop);
return err;
}
if (!provisioned) {
#ifdef CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE
app_network_print_qr(service_name, pop, PROV_TRANSPORT_BLE);
#else /* CONFIG_APP_NETWORK_PROV_TRANSPORT_SOFTAP */
app_network_print_qr(service_name, pop, PROV_TRANSPORT_SOFTAP);
#endif /* CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE */
app_network_start_timer();
}
free(pop);
intro_print(provisioned);
if (custom_mfg_data) {
free(custom_mfg_data);
custom_mfg_data = NULL;
custom_mfg_data_len = 0;
}
/* Wait for Network connection */
xEventGroupWaitBits(network_event_group, NETWORK_CONNECTED_EVENT, false, true, portMAX_DELAY);
return err;
}
@@ -0,0 +1,108 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_err.h>
#include <esp_event.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MFG_DATA_HEADER 0xe5, 0x02
#define MGF_DATA_APP_ID 'N', 'o', 'v'
#define MFG_DATA_VERSION 'a'
#define MFG_DATA_CUSTOMER_ID 0x00, 0x01
#define MGF_DATA_DEVICE_TYPE_LIGHT 0x0005
#define MGF_DATA_DEVICE_TYPE_SWITCH 0x0080
#define MGF_DATA_DEVICE_TYPE_MATTER_CONTROLLER 0xFFF1
#define MFG_DATA_DEVICE_SUBTYPE_SWITCH 0x01
#define MFG_DATA_DEVICE_SUBTYPE_LIGHT 0x01
#define MFG_DATA_DEVICE_SUBTYPE_MATTER_CONTROLLER 0x01
#define MFG_DATA_DEVICE_EXTRA_CODE 0x00
/** ESP RainMaker Event Base */
ESP_EVENT_DECLARE_BASE(APP_NETWORK_EVENT);
/** App Network Events */
typedef enum {
/** QR code available for display. Associated data is the NULL terminated QR payload. */
APP_NETWORK_EVENT_QR_DISPLAY = 1,
/** Provisioning timed out */
APP_NETWORK_EVENT_PROV_TIMEOUT,
/** Provisioning has restarted due to failures (Invalid SSID/Passphrase) */
APP_NETWORK_EVENT_PROV_RESTART,
/** Provisioning closed due to invalid credentials */
APP_NETWORK_EVENT_PROV_CRED_MISMATCH,
} app_network_event_t;
/** Types of Proof of Possession */
typedef enum {
/** Use MAC address to generate PoP */
POP_TYPE_MAC,
/** Use random stream generated and stored in fctry partition during claiming process as PoP */
POP_TYPE_RANDOM,
/** Do not use any PoP.
* Use this option with caution. Consider using `CONFIG_APP_NETWORK_PROV_TIMEOUT_PERIOD` with this.
*/
POP_TYPE_NONE,
/** Use a custom PoP.
* Set a custom PoP using app_network_set_custom_pop() first.
*/
POP_TYPE_CUSTOM
} app_network_pop_type_t;
/** Initialize Wi-Fi/Thread
*
* This initializes Wi-Fi/Thread stack and the network provisioning manager
*/
void app_network_init();
/** Start Wi-Fi/Thread
*
* This will start provisioning if the node is not provisioned and will connect to any network
* if node is provisioned. Function will return successfully only after network is connected
*
* @param[in] pop_type The type for Proof of Possession (PoP) pin
*
* @return ESP_OK on success (Network connected).
* @return error in case of failure.
*/
esp_err_t app_network_start(app_network_pop_type_t pop_type);
/** Set custom manufacturing data
*
* This can be used to add some custom manufacturing data in BLE advertisements during
* provisioning. This can be used by apps to filter the scanned BLE devices and show
* only the relevant one. Supported by Nova Home app for light and switch
*
* @param[in] device_type Type of the device, like light or switch
* @param[in] device_subtype Sub Type of the device (application specific)
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t app_network_set_custom_mfg_data(uint16_t device_type, uint8_t device_subtype);
/** Set custom PoP
*
* This can be used to set a custom Proof of Possession (PoP) pin for provisioning.
* Applicable only if POP_TYPE_CUSTOM is used for app_network_start().
*
* @param[in] pop A NULL terminated PoP string (typically 8 characters alphanumeric)
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t app_network_set_custom_pop(const char *pop);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,209 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <sdkconfig.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_idf_version.h>
#include <esp_rmaker_utils.h>
#include <app_network.h>
#include <app_wifi_internal.h>
#include <esp_netif.h>
#include <network_provisioning/manager.h>
#ifdef CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE
#include <network_provisioning/scheme_ble.h>
#else /* CONFIG_APP_NETWORK_PROV_TRANSPORT_SOFTAP */
#include <network_provisioning/scheme_softap.h>
#endif /* CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE */
#include <app_wifi_internal.h>
#include <app_network.h>
#define APP_PROV_STOP_ON_CREDS_MISMATCH
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
static const char* TAG = "app_wifi";
/* Event handler for catching system events */
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
#ifdef CONFIG_APP_NETWORK_RESET_PROV_ON_FAILURE
static int retries = 0;
#endif
if (event_base == NETWORK_PROV_EVENT) {
switch (event_id) {
case NETWORK_PROV_START:
ESP_LOGI(TAG, "Provisioning started");
break;
case NETWORK_PROV_WIFI_CRED_RECV: {
wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data;
ESP_LOGI(TAG, "Received Wi-Fi credentials"
"\n\tSSID : %s\n\tPassword : %s",
(const char *) wifi_sta_cfg->ssid,
(const char *) wifi_sta_cfg->password);
break;
}
case NETWORK_PROV_WIFI_CRED_FAIL: {
network_prov_wifi_sta_fail_reason_t *reason = (network_prov_wifi_sta_fail_reason_t *)event_data;
ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s"
"\n\tPlease reset to factory and retry provisioning",
(*reason == NETWORK_PROV_WIFI_STA_AUTH_ERROR) ?
"Wi-Fi station authentication failed" : "Wi-Fi access-point not found");
#ifdef CONFIG_APP_NETWORK_RESET_PROV_ON_FAILURE
retries++;
if (retries >= CONFIG_APP_NETWORK_PROV_MAX_RETRY_CNT) {
ESP_LOGI(TAG, "Failed to connect with provisioned AP, resetting provisioned credentials");
network_prov_mgr_reset_wifi_sm_state_on_failure();
esp_event_post(APP_NETWORK_EVENT, APP_NETWORK_EVENT_PROV_RESTART, NULL, 0, portMAX_DELAY);
retries = 0;
}
#endif // CONFIG_APP_NETWORK_RESET_PROV_ON_FAILURE
break;
}
case NETWORK_PROV_WIFI_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
#ifdef CONFIG_APP_NETWORK_RESET_PROV_ON_FAILURE
retries = 0;
#endif
break;
default:
break;
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected. Connecting to the AP again...");
esp_wifi_connect();
}
}
static void wifi_init_sta()
{
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
}
#endif // CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
esp_err_t wifi_init(void)
{
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
/* Initialize TCP/IP */
esp_netif_init();
/* Register our event handler for Wi-Fi, IP and Provisioning related events */
ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_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));
/* Initialize Wi-Fi including netif with default config */
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
return ESP_OK;
#else
return ESP_ERR_NOT_SUPPORTED;
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
}
esp_err_t wifi_start(const char *pop, const char *service_name, const char *service_key, uint8_t *mfg_data,
size_t mfg_data_len, bool *provisioned)
{
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
/* Configuration for the provisioning manager */
network_prov_mgr_config_t config = {
/* What is the Provisioning Scheme that we want ?
* network_prov_scheme_softap or network_prov_scheme_ble */
#ifdef CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE
.scheme = network_prov_scheme_ble,
#else /* CONFIG_APP_NETWORK_PROV_TRANSPORT_SOFTAP */
.scheme = network_prov_scheme_softap,
#endif /* CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE */
/* Any default scheme specific event handler that you would
* like to choose. Since our example application requires
* neither BT nor BLE, we can choose to release the associated
* memory once provisioning is complete, or not needed
* (in case when device is already provisioned). Choosing
* appropriate scheme specific event handler allows the manager
* to take care of this automatically. This can be set to
* NETWORK_PROV_EVENT_HANDLER_NONE when using network_prov_scheme_softap*/
#ifdef CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE
.scheme_event_handler = NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM
#else /* CONFIG_APP_NETWORK_PROV_TRANSPORT_SOFTAP */
.scheme_event_handler = NETWORK_PROV_EVENT_HANDLER_NONE,
#endif /* CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE */
};
/* Initialize provisioning manager with the
* configuration parameters set above */
ESP_ERROR_CHECK(network_prov_mgr_init(config));
/* Let's find out if the device is provisioned */
network_prov_mgr_is_wifi_provisioned(provisioned);
/* If device is not yet provisioned start provisioning service */
if (!(*provisioned)) {
ESP_LOGI(TAG, "Starting provisioning");
#if CONFIG_ESP_WIFI_SOFTAP_SUPPORT
esp_netif_create_default_wifi_ap();
#endif
/* What is the security level that we want (0 or 1):
* - NETWORK_PROV_SECURITY_0/WIFI_PROV_SECURITY_0 is simply plain text communication.
* - NETWORK_PROV_SECURITY_1/WIFI_PROV_SECURITY_1 is secure communication which consists of secure handshake
* using X25519 key exchange and proof of possession (pop) and AES-CTR
* for encryption/decryption of messages.
*/
network_prov_security_t security = NETWORK_PROV_SECURITY_1;
#ifdef CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE
/* This step is only useful when scheme is wifi_prov_scheme_ble. This will
* set a custom 128 bit UUID which will be included in the BLE advertisement
* and will correspond to the primary GATT service that provides provisioning
* endpoints as GATT characteristics. Each GATT characteristic will be
* formed using the primary service UUID as base, with different auto assigned
* 12th and 13th bytes (assume counting starts from 0th byte). The client side
* applications must identify the endpoints by reading the User Characteristic
* Description descriptor (0x2901) for each characteristic, which contains the
* endpoint name of the characteristic */
uint8_t custom_service_uuid[] = {
/* This is a random uuid. This can be modified if you want to change the BLE uuid. */
/* 12th and 13th bit will be replaced by internal bits. */
0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf,
0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02,
};
esp_err_t err = network_prov_scheme_ble_set_service_uuid(custom_service_uuid);
if (err != ESP_OK) {
ESP_LOGE(TAG, "wifi_prov_scheme_ble_set_service_uuid failed %d", err);
return err;
}
if (mfg_data) {
err = network_prov_scheme_ble_set_mfg_data(mfg_data, mfg_data_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set mfg data, err=0x%x", err);
return err;
}
}
#endif /* CONFIG_APP_NETWORK_PROV_TRANSPORT_BLE */
/* Start provisioning service */
ESP_ERROR_CHECK(network_prov_mgr_start_provisioning(security, pop, service_name, service_key));
} else {
ESP_LOGI(TAG, "Already provisioned, starting Wi-Fi STA");
/* We don't need the manager as device is already provisioned,
* so let's release it's resources */
network_prov_mgr_deinit();
/* Start Wi-Fi station */
wifi_init_sta();
}
return ESP_OK;
#else
return ESP_ERR_NOT_SUPPORTED;
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
}
@@ -0,0 +1,50 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_err.h>
#include <esp_event.h>
#include <esp_idf_version.h>
#include "app_network.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Initialize Wi-Fi
*
* This initializes Wi-Fi and the network/wifi provisioning manager
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t wifi_init();
/** Start Wi-Fi
*
* This will start provisioning if the node is not provisioned and will connect to Wi-Fi
* if node is provisioned. Function will return successfully only after Wi-Fi is connect
*
* @param[in] pop The Proof of Possession (PoP) pin
* @param[in] service_name The service name of network/wifi provisioning. This translates to
* - Wi-Fi SSID when scheme is network_prov_scheme_softap/wifi_prov_scheme_softap
* - device name when scheme is network_prov_scheme_ble/wifi_prov_scheme_ble
* @param[in] service_key The service key of network/wifi provisioning. This translates to
* - Wi-Fi password when scheme is network_prov_scheme_softap/wifi_prov_scheme_softap (NULL = Open network)
* @param[in] mfg_data The manufacture specific data of network/wifi provisioning.
* @param[in] mfg_data The manufacture specific data length of network/wifi provisioning.
* @param[out] provisioned Whether the device is provisioned.
*
* @return ESP_OK on success (Wi-Fi connected).
* @return error in case of failure.
*/
esp_err_t wifi_start(const char *pop, const char *service_name, const char *service_key, uint8_t *mfg_data,
size_t mfg_data_len, bool *provisioned);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,5 @@
set(requires esp_http_client json mbedtls esp_wifi wifi_provisioning nvs_flash protobuf-c esp_app_format pthread)
idf_component_register(SRCS "rainmaker_api.cpp"
INCLUDE_DIRS "."
REQUIRES ${requires})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,394 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <esp_err.h>
#include <esp_http_client.h>
#include <esp_log.h>
#include <esp_crt_bundle.h>
#ifdef __cplusplus
#include <string>
#include <functional>
#include <memory>
#include <cstring>
#include <cstdlib>
#include <cJSON.h>
extern "C" {
#endif
/**
* @brief Group operation types for node management
*/
typedef enum {
ESP_RAINMAKER_API_ADD_NODE_TO_GROUP = 0, /* Add node to group */
ESP_RAINMAKER_API_REMOVE_NODE_FROM_GROUP, /* Remove node from group */
} esp_rainmaker_api_group_operation_type_t;
/**
* @brief Node mapping operation types
*/
typedef enum {
ESP_RAINMAKER_API_ADD_NODE_MAPPING = 0, /* Add node mapping */
ESP_RAINMAKER_API_REMOVE_NODE_MAPPING, /* Remove node mapping */
} esp_rainmaker_api_node_mapping_operation_type_t;
/**
* @brief Node mapping status types
*/
typedef enum {
ESP_RAINMAKER_API_NODE_MAPPING_STATUS_REQUESTED = 0, /* Node mapping status requested */
ESP_RAINMAKER_API_NODE_MAPPING_STATUS_CONFIRMED, /* Node mapping status confirmed */
ESP_RAINMAKER_API_NODE_MAPPING_STATUS_TIMEOUT, /* Node mapping status timeout */
ESP_RAINMAKER_API_NODE_MAPPING_STATUS_DISCARDED, /* Node mapping status discarded */
ESP_RAINMAKER_API_NODE_MAPPING_STATUS_INTERNAL_ERROR, /* Node mapping status internal error */
} esp_rainmaker_api_node_mapping_status_type_t;
/* Login to Rainmaker cloud using refresh token
* This function attempts to login to the Rainmaker cloud using the stored refresh token.
* If successful, it will store the access token for subsequent API calls.
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_login(void);
/* Get user ID
* This function retrieves the user ID associated with the Rainmaker account.
* Returns user ID string
*/
char *esp_rainmaker_api_get_user_id(void);
/* Get all nodes associated with the account
* This function retrieves all nodes (devices) associated with the Rainmaker account.
* It will automatically handle pagination and create corresponding device objects.
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_refresh_nodes(void);
/* Get nodes list associated with the account
* This function retrieves all nodes (devices) associated with the Rainmaker account.
* The caller is responsible for freeing the returned string.
* Returns JSON string with nodes list (caller must free), nullptr on error
*/
char* esp_rainmaker_api_get_nodes_list(void);
/* Get node config
* This function retrieves the config of a node (device) in the Rainmaker cloud.
* The caller is responsible for freeing the returned string.
* @param node_id Node ID
* Returns JSON string with node config (caller must free), nullptr on error
*/
char* esp_rainmaker_api_get_node_config(const char* node_id);
/* Set node parameters
* This function updates the parameters of a node (device) in the Rainmaker cloud.
* The parameters should be provided as a JSON string.
* @param payload JSON string containing parameter updates
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_set_node_params(const char* payload);
/* Get node parameters
* This function retrieves the current parameters of a specific node (device).
* The caller is responsible for freeing the returned string.
* @param node_id ID of the node to query
* Returns JSON string with node parameters (caller must free), nullptr on error
*/
char* esp_rainmaker_api_get_node_params(const char* node_id);
/* Set refresh token for authentication
* This function stores the refresh token that will be used for authentication.
* The refresh token is used to obtain access tokens for API calls.
* @param refresh_token The refresh token string
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_set_refresh_token(const char* refresh_token);
/* Set base URL for Rainmaker API
* This function sets the base URL for the Rainmaker API.
* @param base_url The base URL string
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_set_base_url(const char* base_url);
/* Delete stored refresh token
* This function clears the stored refresh token and access token.
* This should be called when logging out or when the tokens are no longer valid.
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_delete_refresh_token(void);
/* Get all groups
* This function retrieves all groups associated with the Rainmaker account.
* The caller is responsible for freeing the returned string.
* Returns JSON string with groups (caller must free), nullptr on error
*/
char* esp_rainmaker_api_get_group(void);
/* Create a new group
* This function creates a new group in the Rainmaker cloud.
* @param group_name Name of the group to create
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_create_group(const char* group_name);
/* Delete a group
* This function deletes an existing group from the Rainmaker cloud.
* @param group_id ID of the group to delete
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_delete_group(const char* group_id);
/* Add or remove node from group
* This function adds or removes a node (device) from a group.
* The operation type determines whether to add or remove the node.
* @param node_id ID of the node to operate on
* @param group_id ID of the target group
* @param operation_type Operation type (add or remove)
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_operate_node_to_group(const char* node_id, const char* group_id,
esp_rainmaker_api_group_operation_type_t operation_type);
/* Set node mapping
* This function sets the node mapping for a user.
* @param user_id User ID
* @param secret_key Secret key
* @param node_id Node ID
* @param operation_type Operation type (add or remove)
* @param request_id Request ID to store the request ID
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_set_node_mapping(const char* user_id, const char* secret_key, const char* node_id,
esp_rainmaker_api_node_mapping_operation_type_t operation_type, char *request_id);
/* Get node mapping status
* This function retrieves the status of a node mapping request.
* @param request_id Request ID
* Returns node mapping status
*/
esp_rainmaker_api_node_mapping_status_type_t esp_rainmaker_api_get_node_mapping_status(const char *request_id);
/* Get node connection status
* This function retrieves the connection status of a node.
* @param node_id Node ID
* @param connection_status Pointer to store connection status
* Returns ESP_OK on success, error code otherwise
*/
esp_err_t esp_rainmaker_api_get_node_connection_status(const char *node_id, bool *connection_status);
#ifdef __cplusplus
}
/**
* @brief Rainmaker API client class (Singleton)
*
* This class provides a unified interface for interacting with ESP Rainmaker cloud services.
* It handles authentication, node management, and group operations.
*/
class RainmakerApi
{
public:
/**
* @brief Get singleton instance
* @return Reference to the singleton instance
*/
static RainmakerApi& GetInstance() {
static RainmakerApi instance;
return instance;
}
/* Disable copy constructor and assignment operator */
RainmakerApi(const RainmakerApi&) = delete;
RainmakerApi& operator=(const RainmakerApi&) = delete;
/**
* @brief Login to Rainmaker cloud using refresh token
* @return ESP_OK on success, error code otherwise
*/
esp_err_t Login(void);
/**
* @brief Get user info
* @return ESP_OK on success, error code otherwise
*/
esp_err_t GetUserInfo(void);
/**
* @brief Get all nodes associated with the account
* @return ESP_OK on success, error code otherwise
*/
esp_err_t RefreshNodes(void);
/**
* @brief Get nodes list associated with the account
* @return JSON string with node list (caller must free), nullptr on error
*/
char* GetNodeList(void);
/**
* @brief Get node config
* @param node_id Node ID
* @return JSON string with node config (caller must free), nullptr on error
*/
char* GetNodeConfig(const char* node_id);
/**
* @brief Set node parameters
* @param payload JSON payload containing parameter updates
* @return ESP_OK on success, error code otherwise
*/
esp_err_t SetNodeParams(const char* payload);
/**
* @brief Get node parameters
* @param node_id Node ID to query
* @return JSON string with node parameters (caller must free), nullptr on error
*/
char* GetNodeParams(const char* node_id);
/**
* @brief Set refresh token for authentication
* @param refresh_token The refresh token string
* @return ESP_OK on success, error code otherwise
*/
esp_err_t SetRefreshToken(const char* refresh_token);
/**
* @brief Set base URL for Rainmaker API
* @param base_url The base URL string
* @return ESP_OK on success, error code otherwise
*/
esp_err_t SetBaseUrl(const char* base_url);
/**
* @brief Delete stored refresh token
* @return ESP_OK on success, error code otherwise
*/
esp_err_t DeleteRefreshToken(void);
/**
* @brief Get all groups
* @return JSON string with groups (caller must free), nullptr on error
*/
char* GetGroup(void);
/**
* @brief Create a new group
* @param group_name Name of the group to create
* @return ESP_OK on success, error code otherwise
*/
esp_err_t CreateGroup(const char* group_name);
/**
* @brief Delete a group
* @param group_id ID of the group to delete
* @return ESP_OK on success, error code otherwise
*/
esp_err_t DeleteGroup(const char* group_id);
/**
* @brief Add or remove node from group
* @param node_id ID of the node
* @param group_id ID of the group
* @param operation_type Operation type (add or remove)
* @return ESP_OK on success, error code otherwise
*/
esp_err_t OperateNodeToGroup(const char* node_id, const char* group_id,
esp_rainmaker_api_group_operation_type_t operation_type);
/**
* @brief Set node mapping
* @param user_id User ID
* @param secret_key Secret key
* @param node_id Node ID
* @param operation_type Operation type (add or remove)
* @param request_id Request ID to store the request ID
* @return ESP_OK on success, error code otherwise
*/
esp_err_t SetNodeMapping(const char* user_id, const char* secret_key, const char* node_id,
esp_rainmaker_api_node_mapping_operation_type_t operation_type, char *request_id);
/**
* @brief Get node mapping status
* @param request_id Request ID
* @return Node mapping status
*/
esp_rainmaker_api_node_mapping_status_type_t GetNodeMappingStatus(const char *request_id);
/**
* @brief Get node connection status
* @param node_id Node ID
* @param connection_status Pointer to store connection status
* @return ESP_OK on success, error code otherwise
*/
esp_err_t GetNodeConnectionStatus(const char *node_id, bool *connection_status);
/**
* @brief Get user id string (for C API)
*/
const std::string& GetUserId() const;
private:
/**
* @brief Private constructor for singleton pattern
*/
RainmakerApi();
/**
* @brief Private destructor
*/
~RainmakerApi();
std::string access_token_; /* Current access token */
std::string refresh_token_; /* Stored refresh token */
std::string base_url_; /* Base URL for Rainmaker API */
std::string user_id_; /* User ID */
/**
* @brief Recursively get nodes with pagination
* @param start_id Starting node ID for pagination
* @return ESP_OK on success, error code otherwise
*/
esp_err_t GetNodesRecursive(const char* start_id);
/**
* @brief Cleanup HTTP client resources
* @param client HTTP client handle
* @param post_data Optional POST data to free
*/
static void CleanupHttpClient(void* client, char* post_data = nullptr);
/**
* @brief Make HTTP request
* @param client HTTP client handle
* @param post_data Optional POST data
* @return ESP_OK on success, error code otherwise
*/
static esp_err_t MakeHttpRequest(esp_http_client_handle_t client, const char* post_data = nullptr);
/**
* @brief Handle HTTP response and check for authentication errors
* @param client HTTP client handle
* @param response_data Pointer to store response data
* @param retry_func Optional retry function to call on authentication error
* @return ESP_OK on success, error code otherwise
*/
esp_err_t HandleHttpResponse(esp_http_client_handle_t client, char** response_data,
std::function<esp_err_t()> retry_func);
/**
* @brief Read HTTP response data
* @param client HTTP client handle
* @param response_data Pointer to store response data (caller must free)
* @return ESP_OK on success, error code otherwise
*/
esp_err_t ReadHttpResponse(esp_http_client_handle_t client, char** response_data);
};
#endif /* __cplusplus */
@@ -0,0 +1,6 @@
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "." "${ESP_MATTER_PATH}/examples/common/utils"
LDFRAGMENTS "linker.lf")
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
target_compile_options(${COMPONENT_LIB} PRIVATE "-DCHIP_HAVE_CONFIG_H")
@@ -0,0 +1,60 @@
menu "Rainmaker Bridge"
config RAINMAKER_PARAMS_GET_PERIOD_MS
int
default 10000
range 1000 60000
help
Set the period to get rainmaker devices params in rainmaker bridge.
config RAINMAKER_TASK_STACK_SIZE
int
default 10240
range 1024 65536
help
Set the stack size for the rainmaker task.
endmenu
menu "Thread BR Example"
depends on OPENTHREAD_BORDER_ROUTER
choice THREAD_BR_BOARD_TYPE
prompt "Thread border router board type"
default ESP_THREAD_BR_BOARD_DEV_KIT
help
The board running the border router.
config ESP_THREAD_BR_BOARD_DEV_KIT
bool "ESP Thread border router dev kit"
help
Integrated border router dev kit
config M5STACK_THREAD_BR_BOARD
bool "M5Stack Thread border router board"
help
M5Stack CoreS3 with Module Gateway H2
endchoice
menu "Board Configuration"
config PIN_TO_RCP_RESET
int "Pin to RCP reset"
default "7"
config PIN_TO_RCP_BOOT
int "Pin to RCP boot"
default "18" if M5STACK_THREAD_BR_BOARD
default "8"
config PIN_TO_RCP_TX
int "Pin to RCP TX"
default "10" if M5STACK_THREAD_BR_BOARD
default "17"
config PIN_TO_RCP_RX
int "Pin to RCP RX"
default "17" if M5STACK_THREAD_BR_BOARD
default "18"
endmenu
endmenu
@@ -0,0 +1,158 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <esp_err.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <esp_matter.h>
#include <esp_matter_console.h>
#include <esp_matter_ota.h>
#include <common_macros.h>
#include <app_bridged_device.h>
#include <rainmaker_bridge.h>
static const char *TAG = "app_main";
using namespace esp_matter;
using namespace esp_matter::attribute;
using namespace esp_matter::endpoint;
uint16_t aggregator_endpoint_id = chip::kInvalidEndpointId;
static void app_event_cb(const ChipDeviceEvent *event, intptr_t arg)
{
switch (event->Type) {
case chip::DeviceLayer::DeviceEventType::kInterfaceIpAddressChanged:
ESP_LOGI(TAG, "Interface IP Address Changed");
break;
case chip::DeviceLayer::DeviceEventType::kCommissioningComplete:
ESP_LOGI(TAG, "Commissioning complete");
break;
case chip::DeviceLayer::DeviceEventType::kFailSafeTimerExpired:
ESP_LOGI(TAG, "Commissioning failed, fail safe timer expired");
break;
case chip::DeviceLayer::DeviceEventType::kCommissioningSessionStarted:
ESP_LOGI(TAG, "Commissioning session started");
break;
case chip::DeviceLayer::DeviceEventType::kCommissioningSessionStopped:
ESP_LOGI(TAG, "Commissioning session stopped");
break;
case chip::DeviceLayer::DeviceEventType::kCommissioningWindowOpened:
ESP_LOGI(TAG, "Commissioning window opened");
break;
case chip::DeviceLayer::DeviceEventType::kCommissioningWindowClosed:
ESP_LOGI(TAG, "Commissioning window closed");
break;
default:
break;
}
}
// This callback is called for every attribute update. The callback implementation shall
// handle the desired attributes and return an appropriate error code. If the attribute
// is not of your interest, please do not return an error code and strictly return ESP_OK.
static esp_err_t app_attribute_update_cb(callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id,
uint32_t attribute_id, esp_matter_attr_val_t *val, void *priv_data)
{
esp_err_t err = ESP_OK;
if (type == PRE_UPDATE) {
err = rainmaker_bridge_attribute_update(endpoint_id, cluster_id, attribute_id, val);
}
return err;
}
// This callback is invoked after the creation or resumption of a bridge endpoint.
// It can be used to add data model elements (e.g., attributes, commands, etc.) to the bridge endpoint.
esp_err_t create_bridge_devices(esp_matter::endpoint_t *ep, uint32_t device_type_id, void *priv_data)
{
esp_err_t err = ESP_OK;
switch (device_type_id) {
case ESP_MATTER_ON_OFF_LIGHT_DEVICE_TYPE_ID: {
on_off_light::config_t on_off_light_conf;
err = on_off_light::add(ep, &on_off_light_conf);
break;
}
case ESP_MATTER_DIMMABLE_LIGHT_DEVICE_TYPE_ID: {
dimmable_light::config_t dimmable_light_conf;
dimmable_light_conf.level_control_lighting.start_up_current_level = nullptr;
err = dimmable_light::add(ep, &dimmable_light_conf);
break;
}
case ESP_MATTER_COLOR_TEMPERATURE_LIGHT_DEVICE_TYPE_ID: {
color_temperature_light::config_t color_temperature_light_conf;
color_temperature_light_conf.level_control_lighting.start_up_current_level = nullptr;
err = color_temperature_light::add(ep, &color_temperature_light_conf);
break;
}
case ESP_MATTER_EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID: {
extended_color_light::config_t extended_color_light_conf;
extended_color_light_conf.level_control_lighting.start_up_current_level = nullptr;
err = extended_color_light::add(ep, &extended_color_light_conf);
cluster_t *color_cluster = cluster::get(ep, chip::app::Clusters::ColorControl::Id);
cluster::color_control::feature::hue_saturation::config_t hs_config;
cluster::color_control::feature::hue_saturation::add(color_cluster, &hs_config);
break;
}
case ESP_MATTER_ON_OFF_LIGHT_SWITCH_DEVICE_TYPE_ID: {
on_off_light_switch::config_t switch_config;
err = on_off_light_switch::add(ep, &switch_config);
break;
}
default: {
ESP_LOGE(TAG, "Unsupported bridged matter device type");
return ESP_ERR_INVALID_ARG;
}
}
return err;
}
extern "C" void app_main()
{
esp_err_t err = ESP_OK;
/* Initialize the ESP NVS layer */
nvs_flash_init();
/* Create a Matter node and add the mandatory Root Node device type on endpoint 0 */
node::config_t node_config;
node_t *node = node::create(&node_config, app_attribute_update_cb, NULL);
ABORT_APP_ON_FAILURE(node != nullptr, ESP_LOGE(TAG, "Failed to create Matter node"));
aggregator::config_t aggregator_config;
endpoint_t *aggregator = endpoint::aggregator::create(node, &aggregator_config, ENDPOINT_FLAG_NONE, NULL);
ABORT_APP_ON_FAILURE(aggregator != nullptr, ESP_LOGE(TAG, "Failed to create aggregator endpoint"));
aggregator_endpoint_id = endpoint::get_id(aggregator);
rainmaker_init();
/* Matter start */
err = esp_matter::start(app_event_cb);
ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to start Matter, err:%d", err));
err = app_bridge_initialize(node, create_bridge_devices);
ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to resume the bridged endpoints: %d", err));
#if CONFIG_ENABLE_CHIP_SHELL
esp_matter::console::diagnostics_register_commands();
esp_matter::console::wifi_register_commands();
esp_matter::console::factoryreset_register_commands();
esp_matter::console::init();
#endif
}
@@ -0,0 +1,70 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_err.h>
#if CONFIG_OPENTHREAD_BORDER_ROUTER
#include <esp_openthread_types.h>
#include <esp_rcp_update.h>
#endif
#include <sdkconfig.h>
#ifdef __cplusplus
extern "C" {
#endif
#if CONFIG_OPENTHREAD_BORDER_ROUTER
#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \
{ \
.radio_mode = RADIO_MODE_UART_RCP, \
.radio_uart_config = { \
.port = 1, \
.uart_config = \
{ \
.baud_rate = 460800, \
.data_bits = UART_DATA_8_BITS, \
.parity = UART_PARITY_DISABLE, \
.stop_bits = UART_STOP_BITS_1, \
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \
.rx_flow_ctrl_thresh = 0, \
.source_clk = UART_SCLK_DEFAULT, \
}, \
.rx_pin = CONFIG_PIN_TO_RCP_TX, \
.tx_pin = CONFIG_PIN_TO_RCP_RX, \
}, \
}
#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \
{ \
.host_connection_mode = HOST_CONNECTION_MODE_CLI_USB, \
.host_usb_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(), \
}
#define ESP_OPENTHREAD_DEFAULT_PORT_CONFIG() \
{ \
.storage_partition_name = "nvs", .netif_queue_size = 10, \
.task_queue_size = 10, \
}
#ifdef CONFIG_AUTO_UPDATE_RCP
#define ESP_OPENTHREAD_RCP_UPDATE_CONFIG() \
{ \
.rcp_type = RCP_TYPE_ESP32H2_UART, .uart_rx_pin = CONFIG_PIN_TO_RCP_TX, .uart_tx_pin = CONFIG_PIN_TO_RCP_RX, \
.uart_port = 1, .uart_baudrate = 115200, .reset_pin = CONFIG_PIN_TO_RCP_RESET, \
.boot_pin = CONFIG_PIN_TO_RCP_BOOT, .update_baudrate = 460800, \
.firmware_dir = "/" CONFIG_RCP_PARTITION_NAME "/ot_rcp", .target_chip = ESP32H2_CHIP, \
}
#endif
#endif
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,7 @@
dependencies:
espressif/esp_rainmaker:
version: "~1.7.9" # Will update to latest version after the esp_rcp_update update to latest version.
espressif/qrcode:
version: "*"
espressif/button:
version: "^4"
@@ -0,0 +1,31 @@
[mapping:CHIP]
archive: libCHIP.a
entries:
if SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY = y:
* (extram_bss)
else:
* (default)
[mapping:esp_matter]
archive: libesp_matter.a
entries:
if SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY = y:
* (extram_bss)
else:
* (default)
[mapping:main]
archive: libmain.a
entries:
if SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY = y:
* (extram_bss)
else:
* (default)
[mapping:openthread_bss]
archive: libopenthread.a
entries:
if SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY = y && OPENTHREAD_ENABLED = y:
* (extram_bss)
else:
* (default)
@@ -0,0 +1,544 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <esp_check.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_event.h>
#include <esp_matter.h>
#include <esp_matter_bridge.h>
#include <esp_rmaker_standard_types.h>
#include "esp_rmaker_standard_params.h"
#include <json_parser.h>
#include <json_generator.h>
#include <app_bridged_device.h>
#include <rainmaker_api.h>
#include <rainmaker_controller_std.h>
#include <app_network.h>
#include <common_macros.h>
#include <rainmaker_bridge.h>
#include <mdns.h>
#if CONFIG_OPENTHREAD_BORDER_ROUTER
#include <app_thread_config.h>
#include <esp_rmaker_thread_br.h>
#include <platform/ESP32/OpenthreadLauncher.h>
#ifdef CONFIG_AUTO_UPDATE_RCP
#include <esp_rcp_update.h>
#include <esp_spiffs.h>
#endif
#endif
#define INVALID_MATTER_DEVICE_TYPE 0xFFFF
static const char *TAG = "rainmaker_bridge";
using namespace chip;
using namespace chip::app::Clusters;
using namespace esp_matter;
using namespace esp_matter::cluster;
extern uint16_t aggregator_endpoint_id;
static esp_err_t attribute_update(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, int value)
{
esp_err_t err = ESP_OK;
attribute_t *attribute = attribute::get(endpoint_id, cluster_id, attribute_id);
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
attribute::get_val(attribute, &val);
if (cluster_id == OnOff::Id) {
if (attribute_id == OnOff::Attributes::OnOff::Id) {
if (val.val.b != (bool)value) {
val.val.b = (bool)value;
} else {
return err;
}
}
} else if (cluster_id == LevelControl::Id) {
if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) {
if (val.val.u8 != REMAP_TO_RANGE(value, RMAKER_LEVEL_MAX_VALUE, MATTER_LEVEL_MAX_VALUE)) {
val.val.u8 = REMAP_TO_RANGE(value, RMAKER_LEVEL_MAX_VALUE, MATTER_LEVEL_MAX_VALUE);
} else {
return err;
}
}
} else if (cluster_id == ColorControl::Id) {
if (attribute_id == ColorControl::Attributes::CurrentHue::Id) {
if (val.val.u8 != REMAP_TO_RANGE(value, RMAKER_HUE_MAX_VALUE, MATTER_HUE_MAX_VALUE)) {
val.val.u8 = REMAP_TO_RANGE(value, RMAKER_HUE_MAX_VALUE, MATTER_HUE_MAX_VALUE);
} else {
return err;
}
} else if (attribute_id == ColorControl::Attributes::CurrentSaturation::Id) {
if (val.val.u8 != REMAP_TO_RANGE(value, RMAKER_SATURATION_MAX_VALUE, MATTER_SATURATION_MAX_VALUE)) {
val.val.u8 = REMAP_TO_RANGE(value, RMAKER_SATURATION_MAX_VALUE, MATTER_SATURATION_MAX_VALUE);
} else {
return err;
}
} else if (attribute_id == ColorControl::Attributes::ColorTemperatureMireds::Id) {
if (val.val.u16 != REMAP_TO_RANGE_INVERSE(value, STANDARD_TEMPERATURE_FACTOR)) {
val.val.u16 = REMAP_TO_RANGE_INVERSE(value, STANDARD_TEMPERATURE_FACTOR);
} else {
return err;
}
}
} else if (cluster_id == WindowCovering::Id) {
/* windowcovering was not supported yet */
return err;
}
attribute::report(endpoint_id, cluster_id, attribute_id, &val);
return err;
}
static esp_err_t get_attribute_value_from_rainmaker_device(uint16_t endpoint_id, const char *node_id, const char *node_name)
{
int attribute_value;
jparse_ctx_t jctx;
bool power;
char* receive_buffer = esp_rainmaker_api_get_node_params(node_id);
if (receive_buffer == NULL) {
return ESP_FAIL;
}
if (json_parse_start(&jctx, receive_buffer, strlen(receive_buffer)) != 0) {
free(receive_buffer);
return ESP_FAIL;
}
if (node_name[0] != 0) {
if (json_obj_get_object(&jctx, node_name) == 0) {
if (json_obj_get_int(&jctx, "Brightness", &attribute_value) == 0) {
if (attribute_value > 0) {
attribute_update(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, attribute_value);
}
}
if (json_obj_get_int(&jctx, "Hue", &attribute_value) == 0) {
attribute_update(endpoint_id, ColorControl::Id, ColorControl::Attributes::CurrentHue::Id, attribute_value);
}
if (json_obj_get_bool(&jctx, "Power", &power) == 0) {
attribute_value = power;
attribute_update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, attribute_value);
}
if (json_obj_get_int(&jctx, "Saturation", &attribute_value) == 0) {
attribute_update(endpoint_id, ColorControl::Id, ColorControl::Attributes::CurrentSaturation::Id, attribute_value);
}
if (json_obj_get_int(&jctx, "CCT", &attribute_value) == 0) {
attribute_update(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id, attribute_value);
}
} else {
ESP_LOGE(TAG, "No light param found in json ");
}
json_obj_leave_object(&jctx);
} else {
/* Todo: add other device types later */
}
json_parse_end(&jctx);
free(receive_buffer);
return ESP_OK;
}
static uint32_t matter_get_device_type_from_rainmaker_device(const char *input_buf, size_t buf_length, const char *node_name)
{
jparse_ctx_t jctx;
int param_count;
int device_count;
char type[32];
char param_type[32];
uint32_t matter_device_type_id = INVALID_MATTER_DEVICE_TYPE;
if (json_parse_start(&jctx, input_buf, buf_length) != 0) {
return INVALID_MATTER_DEVICE_TYPE;
}
if (json_obj_get_array(&jctx, "devices", &device_count) == 0) {
if (json_arr_get_object(&jctx, 0) == 0) {
if (json_obj_get_string(&jctx, "type", type, sizeof(type)) == 0) {
/* get device type */
if ((strcmp(type, ESP_RMAKER_DEVICE_LIGHTBULB) == 0) || (strcmp(type, ESP_RMAKER_DEVICE_LIGHT) == 0)) {
matter_device_type_id = ESP_MATTER_ON_OFF_LIGHT_DEVICE_TYPE_ID;
if (json_obj_get_array(&jctx, "params", &param_count) == 0) {
for (int i = 0; i < param_count; i++) {
if (json_arr_get_object(&jctx, i) == 0) {
if (json_obj_get_string(&jctx, "type", param_type, sizeof(param_type)) == 0) {
/* get param type */
if (((strcmp(param_type, ESP_RMAKER_PARAM_HUE) == 0) || (strcmp(param_type, ESP_RMAKER_PARAM_SATURATION) == 0)) &&
(matter_device_type_id < ESP_MATTER_EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID)) {
matter_device_type_id = ESP_MATTER_EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID;
} else if ((strcmp(param_type, ESP_RMAKER_PARAM_CCT) == 0) &&
(matter_device_type_id < ESP_MATTER_COLOR_TEMPERATURE_LIGHT_DEVICE_TYPE_ID)) {
matter_device_type_id = ESP_MATTER_COLOR_TEMPERATURE_LIGHT_DEVICE_TYPE_ID;
} else if (((strcmp(param_type, ESP_RMAKER_PARAM_BRIGHTNESS) == 0) || (strcmp(param_type, ESP_RMAKER_PARAM_INTENSITY) == 0)) &&
(matter_device_type_id < ESP_MATTER_DIMMABLE_LIGHT_DEVICE_TYPE_ID)) {
matter_device_type_id = ESP_MATTER_DIMMABLE_LIGHT_DEVICE_TYPE_ID;
}
}
json_arr_leave_object(&jctx);
}
}
json_obj_leave_array(&jctx);
} else {
ESP_LOGE(TAG, "No parameters found in json ");
}
} else {
/* Todo: add other device types */
ESP_LOGW(TAG, "unsupported device type\n");
}
} else {
ESP_LOGE(TAG, "No type found in json ");
}
if (json_obj_get_string(&jctx, "name", node_name, 32) != 0) {
ESP_LOGE(TAG, "Get node name failed");
}
json_obj_leave_object(&jctx);
} else {
ESP_LOGE(TAG, "No devices found in array ");
}
json_obj_leave_array(&jctx);
} else {
ESP_LOGE(TAG, "No devices found in json ");
}
json_parse_end(&jctx);
return matter_device_type_id;
}
static esp_err_t rainmaker_bridge_match_device(const char *node_id, const char *node_name, uint32_t device_type_id)
{
node_t *node = node::get();
if (app_bridge_get_matter_endpointid_by_rainmaker_node_id(node_id) == chip::kInvalidEndpointId) {
app_bridged_device_t *bridged_device = app_bridge_create_bridged_device(node, aggregator_endpoint_id, device_type_id,
ESP_MATTER_BRIDGED_DEVICE_TYPE_RAINMAKER,
app_bridge_rainmaker_address(node_id, node_name), NULL);
ESP_RETURN_ON_FALSE(bridged_device, ESP_FAIL, TAG, "Failed to create bridged device (rainmaker device)");
} else {
ESP_LOGI(TAG, "Bridged node for %s rainmaker device has been created", node_id);
}
return ESP_OK;
}
static esp_err_t rainmaker_bridge_get_param_from_device(const char* node_id, uint16_t endpoint_id)
{
node_t *node = node::get();
endpoint_t *dev_endpoint = endpoint::get(node, endpoint_id);
uint8_t device_type_count = endpoint::get_device_type_count(dev_endpoint);
uint32_t dev_type_id;
uint8_t dev_type_ver;
for (uint8_t i = 0; i < device_type_count; ++i) {
if ((ESP_OK == endpoint::get_device_type_at_index(dev_endpoint, i, dev_type_id, dev_type_ver))) {
ESP_LOGI(TAG, "Endpoint Id: %d--Node Id: %s Device Type: %ld\n", endpoint_id, node_id, dev_type_id);
if (dev_type_id != endpoint::bridged_node::get_device_type_id()) {
break;
}
}
}
switch(dev_type_id) {
case ESP_MATTER_EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID:
case ESP_MATTER_COLOR_TEMPERATURE_LIGHT_DEVICE_TYPE_ID:
case ESP_MATTER_DIMMABLE_LIGHT_DEVICE_TYPE_ID:
case ESP_MATTER_ON_OFF_LIGHT_DEVICE_TYPE_ID:
{
get_attribute_value_from_rainmaker_device(endpoint_id, node_id, app_bridge_get_rainmaker_node_name_by_matter_endpointid(endpoint_id));
}
break;
/* Todo: add other device types */
default:
break;
}
return ESP_OK;
}
static esp_err_t rainmaker_bridge_update_online_state(const char* node_id, uint16_t endpoint_id)
{
esp_err_t err = ESP_OK;
bool connection_status = false;
attribute_t *attribute = attribute::get(endpoint_id, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::Reachable::Id);
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
attribute::get_val(attribute, &val);
err = esp_rainmaker_api_get_node_connection_status(node_id, &connection_status);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to get node %s connection status", node_id);
} else {
ESP_LOGI(TAG, "node %s connection status: %d", node_id, connection_status);
if (val.val.b != connection_status) {
val.val.b = connection_status;
attribute::update(endpoint_id, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::Reachable::Id, &val);
}
}
return err;
}
static esp_err_t rainmaker_bridge_add_new_device(const char *node_id)
{
uint32_t matter_device_type = 0;
char node_name[32] = {0};
if (node_id != NULL) {
char* receive_buffer = esp_rainmaker_api_get_node_config(node_id);
if (receive_buffer == NULL) {
ESP_LOGE(TAG, "Get Node %s config failed\n", node_id);
return ESP_FAIL;
}
matter_device_type = matter_get_device_type_from_rainmaker_device(receive_buffer, strlen(receive_buffer), node_name);
if ((matter_device_type != INVALID_MATTER_DEVICE_TYPE) && (node_name[0] != 0)) {
rainmaker_bridge_match_device(node_id, node_name, matter_device_type);
} else {
ESP_LOGW(TAG, "Node %s device type 0x%lx is invalid\n", node_id, matter_device_type);
free(receive_buffer);
return ESP_ERR_NOT_FOUND;
}
free(receive_buffer);
}
return ESP_OK;
}
static esp_err_t rainmaker_bridge_delete_device(uint16_t endpoint_id)
{
const char* node_id = app_bridge_get_rainmaker_node_id_by_matter_endpointid(endpoint_id);
if (node_id == NULL) {
ESP_LOGI(TAG, "Can't find rainmaker device from ep: %d", endpoint_id);
return ESP_FAIL;
}
app_bridged_device_t *bridged_device = app_bridge_get_device_by_rainmaker_node_id(node_id);
if (bridged_device) {
app_bridge_remove_device(bridged_device);
ESP_LOGI(TAG, "Bridged rainmaker device removed: %s", node_id);
} else {
ESP_LOGI(TAG, "Bridged rainmaker device not found: %s", node_id);
}
return ESP_OK;
}
static void matter_check_and_remove_not_exist_device()
{
uint16_t matter_endpoint_id_array[MAX_BRIDGED_DEVICE_COUNT];
esp_matter_bridge::get_bridged_endpoint_ids(matter_endpoint_id_array);
for (int i = 0; i < MAX_BRIDGED_DEVICE_COUNT; i++) {
if (matter_endpoint_id_array[i] != chip::kInvalidEndpointId) {
const char *node_id = app_bridge_get_rainmaker_node_id_by_matter_endpointid(matter_endpoint_id_array[i]);
if (node_id != NULL) {
char *buffer = esp_rainmaker_api_get_node_params(node_id);
if (buffer == NULL) {
ESP_LOGI(TAG, "Remove not exist Rainmaker device Node: %s Endpoint: %d\n", node_id, matter_endpoint_id_array[i]);
rainmaker_bridge_delete_device(matter_endpoint_id_array[i]);
} else {
free(buffer);
}
}
}
}
}
static esp_err_t rainmaker_sync_nodes(char *out_buf, size_t out_buf_len)
{
jparse_ctx_t jctx;
int total_count;
char node[32];
static uint8_t check_count = 0;
if (json_parse_start(&jctx, out_buf, out_buf_len) != 0) {
return ESP_FAIL;
}
if (json_obj_get_array(&jctx, "nodes", &total_count) == 0) {
for (int i = 0; i < total_count; i++) {
if (json_arr_get_string(&jctx, i, node, sizeof(node)) == 0) {
uint16_t endpoint_id = app_bridge_get_matter_endpointid_by_rainmaker_node_id(node);
if (endpoint_id == chip::kInvalidEndpointId) {
if (rainmaker_bridge_add_new_device(node) == ESP_OK) {
endpoint_id = app_bridge_get_matter_endpointid_by_rainmaker_node_id(node);
}
}
ESP_LOGI(TAG, "Exist node: %s--endpoint id: %d\n", node, endpoint_id);
if (endpoint_id != chip::kInvalidEndpointId) {
rainmaker_bridge_get_param_from_device(node, endpoint_id);
rainmaker_bridge_update_online_state(node, endpoint_id);
}
}
}
json_obj_leave_array(&jctx);
} else {
ESP_LOGE(TAG, "No node found in json ");
}
json_parse_end(&jctx);
check_count++;
if (check_count >= 5) {
matter_check_and_remove_not_exist_device();
check_count = 0;
}
return ESP_OK;
}
esp_err_t rainmaker_bridge_attribute_update(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val)
{
char param_buffer[128] = {0};
const char* node_id = app_bridge_get_rainmaker_node_id_by_matter_endpointid(endpoint_id);
const char* node_name = app_bridge_get_rainmaker_node_name_by_matter_endpointid(endpoint_id);
if (node_id == NULL) {
return ESP_OK;
}
esp_err_t err = ESP_OK;
if (cluster_id == OnOff::Id) {
if (attribute_id == OnOff::Attributes::OnOff::Id) {
snprintf(param_buffer, sizeof(param_buffer), "[{\"node_id\":\"%s\",\"payload\":{\"%s\":{\"Power\":%s}}}]", node_id, node_name, val->val.b ? "true" : "false");
}
} else if (cluster_id == LevelControl::Id) {
if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) {
attribute_t *attribute = attribute::get(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id);
esp_matter_attr_val_t val_onoff = esp_matter_invalid(NULL);
attribute::get_val(attribute, &val_onoff);
if (val_onoff.val.b == false) {
return ESP_OK;
}
snprintf(param_buffer, sizeof(param_buffer), "[{\"node_id\":\"%s\",\"payload\":{\"%s\":{\"Brightness\":%d}}}]", node_id, node_name,
REMAP_TO_RANGE(val->val.u8, MATTER_LEVEL_MAX_VALUE, RMAKER_LEVEL_MAX_VALUE));
}
} else if (cluster_id == ColorControl::Id) {
if (attribute_id == ColorControl::Attributes::CurrentHue::Id) {
snprintf(param_buffer, sizeof(param_buffer), "[{\"node_id\":\"%s\",\"payload\":{\"%s\":{\"Hue\":%d}}}]", node_id, node_name,
REMAP_TO_RANGE(val->val.u8, MATTER_HUE_MAX_VALUE, RMAKER_HUE_MAX_VALUE));
} else if (attribute_id == ColorControl::Attributes::CurrentSaturation::Id) {
snprintf(param_buffer, sizeof(param_buffer), "[{\"node_id\":\"%s\",\"payload\":{\"%s\":{\"Saturation\":%d}}}]", node_id, node_name,
REMAP_TO_RANGE(val->val.u8, MATTER_SATURATION_MAX_VALUE, RMAKER_SATURATION_MAX_VALUE));
} else if (attribute_id == ColorControl::Attributes::ColorTemperatureMireds::Id) {
snprintf(param_buffer, sizeof(param_buffer), "[{\"node_id\":\"%s\",\"payload\":{\"%s\":{\"CCT\":%d}}}]", node_id, node_name,
REMAP_TO_RANGE_INVERSE(val->val.u16, STANDARD_TEMPERATURE_FACTOR));
}
}
if (param_buffer[0] == 0) {
return ESP_OK;
}
esp_rainmaker_api_set_node_params(param_buffer);
return err;
}
static void rainmaker_bridge_task(void *pvParameters)
{
while(true) {
vTaskDelay(pdMS_TO_TICKS(CONFIG_RAINMAKER_PARAMS_GET_PERIOD_MS));
char* nodes_buffer = esp_rainmaker_api_get_nodes_list();
if (nodes_buffer == NULL) {
continue;
}
rainmaker_sync_nodes(nodes_buffer, strlen(nodes_buffer));
free(nodes_buffer);
}
}
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_BASE_URL) == 0) {
if (val.type != RMAKER_VAL_TYPE_STRING || !val.val.s) {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(TAG, "Set base url: %s\n", val.val.s);
ESP_RETURN_ON_ERROR(esp_rainmaker_api_set_base_url(val.val.s), TAG, "Failed to set base_url");
} else if (strcmp(esp_rmaker_param_get_type(param), ESP_RMAKER_PARAM_USER_TOKEN) == 0) {
if (val.type != RMAKER_VAL_TYPE_STRING || !val.val.s) {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(TAG, "Set user token: %s\n", val.val.s);
ESP_RETURN_ON_ERROR(esp_rainmaker_api_set_refresh_token(val.val.s), TAG, "Failed to set user_token");
}
return ESP_OK;
}
#ifdef CONFIG_AUTO_UPDATE_RCP
static esp_err_t init_spiffs()
{
esp_err_t err = ESP_OK;
esp_vfs_spiffs_conf_t rcp_fw_conf = {.base_path = "/" CONFIG_RCP_PARTITION_NAME,
.partition_label = CONFIG_RCP_PARTITION_NAME,
.max_files = 10, .format_if_mount_failed = false};
err = esp_vfs_spiffs_register(&rcp_fw_conf);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount rcp firmware storage");
}
return err;
}
#endif // CONFIG_AUTO_UPDATE_RCP
/**
* @brief Initialize the Rainmaker bridge
*
*/
void rainmaker_init()
{
/* Network Init */
app_network_init();
/* Initialize the ESP RainMaker Agent.
* Note that this should be called after app_network_init() but before app_network_start()
* */
esp_rmaker_config_t rainmaker_cfg = {
.enable_time_sync = false,
};
esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "ESP RainMaker Device", "RainmakerController");
if (!node) {
ESP_LOGE(TAG, "Could not initialise node. Aborting!!!");
vTaskDelay(5000/portTICK_PERIOD_MS);
abort();
}
esp_rmaker_device_t *device = esp_rmaker_device_create("Rainmaker Controller", ESP_RMAKER_DEVICE_CONTROLLER, NULL);
esp_rmaker_device_add_param(device, esp_rmaker_name_param_create(ESP_RMAKER_DEF_NAME_PARAM, "RainmakerController"));
esp_rmaker_node_add_device(node, device);
esp_rmaker_node_add_device(node, rainmaker_controller_service_create("RainmakerCTL", write_cb, NULL, NULL));
#if CONFIG_OPENTHREAD_BORDER_ROUTER
esp_openthread_platform_config_t thread_cfg = {
.radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(),
.host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(),
.port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG()
};
#ifdef CONFIG_AUTO_UPDATE_RCP
ESP_ERROR_CHECK(init_spiffs());
esp_rcp_update_config_t rcp_update_cfg = ESP_OPENTHREAD_RCP_UPDATE_CONFIG();
esp_rmaker_thread_br_enable(&thread_cfg, &rcp_update_cfg);
#else
esp_rmaker_thread_br_enable(&thread_cfg, NULL);
#endif // CONFIG_AUTO_UPDATE_RCP
#endif // CONFIG_OPENTHREAD_BORDER_ROUTER
esp_rmaker_start();
app_network_start(POP_TYPE_RANDOM);
/* create task to get node and params from rainmaker side */
xTaskCreate(rainmaker_bridge_task, "rainmaker_main", CONFIG_RAINMAKER_TASK_STACK_SIZE, xTaskGetCurrentTaskHandle(), 5, NULL);
}
@@ -0,0 +1,29 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_matter_attribute_utils.h>
#include <app_bridged_device.h>
#include <stdint.h>
#define MATTER_HUE_MAX_VALUE 254
#define MATTER_SATURATION_MAX_VALUE 254
#define MATTER_LEVEL_MAX_VALUE 254
#define STANDARD_TEMPERATURE_FACTOR 1000000
#define RMAKER_HUE_MAX_VALUE 360
#define RMAKER_SATURATION_MAX_VALUE 100
#define RMAKER_LEVEL_MAX_VALUE 100
esp_err_t rainmaker_bridge_attribute_update(uint16_t endpoint_id, uint32_t cluster_id,
uint32_t attribute_id, esp_matter_attr_val_t *val);
/* Init Rainmaker */
void rainmaker_init();
@@ -0,0 +1,36 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <esp_rmaker_core.h>
#include <rainmaker_controller_std.h>
esp_rmaker_param_t *rainmaker_controller_base_url_param_create(const char *param_name)
{
esp_rmaker_param_t *param = esp_rmaker_param_create(param_name, ESP_RMAKER_PARAM_BASE_URL,
esp_rmaker_str(""), PROP_FLAG_READ | PROP_FLAG_WRITE | PROP_FLAG_PERSIST);
return param;
}
esp_rmaker_param_t *rainmaker_controller_user_token_param_create(const char *param_name)
{
esp_rmaker_param_t *param = esp_rmaker_param_create(param_name, ESP_RMAKER_PARAM_USER_TOKEN,
esp_rmaker_str(""), PROP_FLAG_WRITE | PROP_FLAG_PERSIST);
return param;
}
esp_rmaker_device_t *rainmaker_controller_service_create(const char *serv_name, esp_rmaker_device_write_cb_t write_cb,
esp_rmaker_device_read_cb_t read_cb, void *priv_data)
{
esp_rmaker_device_t *service = esp_rmaker_service_create(serv_name, ESP_RMAKER_SERVICE_CONTROLLER, priv_data);
if (service) {
esp_rmaker_device_add_cb(service, write_cb, read_cb);
esp_rmaker_device_add_param(service, rainmaker_controller_base_url_param_create(ESP_RMAKER_DEF_BASE_URL_NAME));
esp_rmaker_device_add_param(service, rainmaker_controller_user_token_param_create(ESP_RMAKER_DEF_USER_TOKEN_NAME));
}
return service;
}
@@ -0,0 +1,30 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <esp_err.h>
#include <esp_rmaker_core.h>
// Rainmaker Controller device
#define ESP_RMAKER_DEVICE_CONTROLLER "esp.device.controller"
// Rainmaker Controller service
#define ESP_RMAKER_SERVICE_CONTROLLER "esp.service.rmaker-user-auth"
// Rainmaker Controller parameters
#define ESP_RMAKER_DEF_BASE_URL_NAME "BaseURL"
#define ESP_RMAKER_PARAM_BASE_URL "esp.param.base-url"
#define ESP_RMAKER_DEF_USER_TOKEN_NAME "UserToken"
#define ESP_RMAKER_PARAM_USER_TOKEN "esp.param.user-token"
esp_rmaker_param_t *rainmaker_controller_base_url_param_create(const char *param_name);
esp_rmaker_param_t *rainmaker_controller_user_token_param_create(const char *param_name);
esp_rmaker_device_t *rainmaker_controller_service_create(const char *serv_name, esp_rmaker_device_write_cb_t write_cb,
esp_rmaker_device_read_cb_t read_cb, void *priv_data);
@@ -0,0 +1,11 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table
esp_secure_cert, 0x3F, , 0xD000, 0x2000, encrypted
nvs, data, nvs, 0x10000, 0xF000,
nvs_keys, data, nvs_keys, 0x1F000, 0x1000, encrypted
ota_0, app, ota_0, 0x20000, 0x250000,
ota_1, app, ota_1, 0x270000, 0x250000,
fctry, data, nvs, 0x4C0000, 0x6000,
phy_init, data, phy, 0x4C6000, 0x1000,
otadata, data, ota, 0x4C7000, 0x2000,
rcp_fw, data, spiffs, 0x4C9000, 0x9A000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table
3 esp_secure_cert, 0x3F, , 0xD000, 0x2000, encrypted
4 nvs, data, nvs, 0x10000, 0xF000,
5 nvs_keys, data, nvs_keys, 0x1F000, 0x1000, encrypted
6 ota_0, app, ota_0, 0x20000, 0x250000,
7 ota_1, app, ota_1, 0x270000, 0x250000,
8 fctry, data, nvs, 0x4C0000, 0x6000,
9 phy_init, data, phy, 0x4C6000, 0x1000,
10 otadata, data, ota, 0x4C7000, 0x2000,
11 rcp_fw, data, spiffs, 0x4C9000, 0x9A000,
@@ -0,0 +1,59 @@
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
# Enable BT
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_TASK_STACK_SIZE=5120
CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255
# Enable FreeRTOS legacy API
CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y
# Enable lwip ipv6 autoconfig
CONFIG_LWIP_IPV6_AUTOCONFIG=y
# Use a custom partition table
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
# Enable chip shell
CONFIG_ENABLE_CHIP_SHELL=y
# Enable lwIP route hooks
CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
# Watchdog
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
# Enable HKDF in mbedtls
CONFIG_MBEDTLS_HKDF_C=y
# Increase LwIP IPv6 address number to 6 (MAX_FABRIC + 1)
# unique local addresses for fabrics(MAX_FABRIC), a link local address(1)
CONFIG_LWIP_IPV6_NUM_ADDRESSES=6
# Increase matter console stack size
CONFIG_ESP_MATTER_CONSOLE_TASK_STACK=3072
# mbedtls
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
CONFIG_MBEDTLS_DYNAMIC_FREE_PEER_CERT=y
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y
# Disable BLE for matter
CONFIG_ENABLE_CHIPOBLE=n
# Use SPIRAM and external alloc
CONFIG_SPIRAM=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_MODE_QUAD=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=512
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=16384
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y
CONFIG_ESP_MATTER_MEM_ALLOC_MODE_EXTERNAL=y
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
@@ -0,0 +1,7 @@
CONFIG_IDF_TARGET="esp32s3"
# System event stack size
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
# Increase timer stack size
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
@@ -0,0 +1,61 @@
# Temporary Fix for Timer Overflows
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120
# For additional security on reset to factory
CONFIG_ESP_RMAKER_USER_ID_CHECK=y
# Increase the MQTT task stack size to use generate new Thread dataset command
CONFIG_MQTT_USE_CUSTOM_CONFIG=y
CONFIG_MQTT_TASK_STACK_SIZE=7168
# mbedTLS
CONFIG_MBEDTLS_CMAC_C=y
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y
CONFIG_MBEDTLS_ECJPAKE_C=y
# OpenThread
CONFIG_OPENTHREAD_ENABLED=y
CONFIG_OPENTHREAD_BORDER_ROUTER=y
CONFIG_OPENTHREAD_RADIO_SPINEL_UART=y
CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC=n
CONFIG_OPENTHREAD_LOG_LEVEL_NOTE=y
CONFIG_OPENTHREAD_SRP_CLIENT=n
CONFIG_OPENTHREAD_DNS_CLIENT=n
CONFIG_THREAD_NETWORK_COMMISSIONING_DRIVER=n
CONFIG_ENABLE_MATTER_OVER_THREAD=n
CONFIG_AUTO_UPDATE_RCP=y
# lwIP
CONFIG_LWIP_IPV6_FORWARD=y
CONFIG_LWIP_IPV6_NUM_ADDRESSES=12
CONFIG_LWIP_MULTICAST_PING=y
CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM=y
CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM=y
CONFIG_LWIP_IPV6_AUTOCONFIG=y
# mDNS
CONFIG_USE_MINIMAL_MDNS=n
CONFIG_MDNS_MULTIPLE_INSTANCE=y
# ESP System Settings
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3584
# USB console for Thread border board
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
# Firmware size optimization
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
CONFIG_COMPILER_SAVE_RESTORE_LIBCALLS=y
CONFIG_NEWLIB_NANO_FORMAT=y
CONFIG_ESP_ERR_TO_NAME_LOOKUP=n
# ESP Thread border router board
CONFIG_ESP_THREAD_BR_BOARD_DEV_KIT=y
CONFIG_ENABLE_ROUTE_HOOK=n
@@ -165,6 +165,17 @@ app_bridged_device_address_t app_bridge_espnow_address(uint8_t espnow_macaddr[6]
return bridged_address;
}
app_bridged_device_address_t app_bridge_rainmaker_address(const char* rainmaker_node_id, const char* rainmaker_node_name)
{
app_bridged_device_address_t bridged_address = {
.rainmaker_node_id = {0},
.rainmaker_node_name = {0},
};
memcpy(bridged_address.rainmaker_node_id, rainmaker_node_id, 32);
memcpy(bridged_address.rainmaker_node_name, rainmaker_node_name, 32);
return bridged_address;
}
/** Bridged Device APIs */
app_bridged_device_t *app_bridge_create_bridged_device(node_t *node, uint16_t parent_endpoint_id,
uint32_t matter_device_type_id,
@@ -404,4 +415,57 @@ uint8_t *app_bridge_get_espnow_macaddr_by_matter_endpointid(uint16_t matter_endp
}
return NULL;
}
/** Rainmaker Device APIs */
app_bridged_device_t *app_bridge_get_device_by_rainmaker_node_id(char rainmaker_node_id[32])
{
app_bridged_device_t *current_dev = g_bridged_device_list;
while (current_dev) {
if ((current_dev->dev_type == ESP_MATTER_BRIDGED_DEVICE_TYPE_RAINMAKER) && current_dev->dev &&
!memcmp(current_dev->dev_addr.rainmaker_node_id, rainmaker_node_id, 32)) {
return current_dev;
}
current_dev = current_dev->next;
}
return NULL;
}
uint16_t app_bridge_get_matter_endpointid_by_rainmaker_node_id(char rainmaker_node_id[32])
{
app_bridged_device_t *current_dev = g_bridged_device_list;
while (current_dev) {
if ((current_dev->dev_type == ESP_MATTER_BRIDGED_DEVICE_TYPE_RAINMAKER) && current_dev->dev &&
!memcmp(current_dev->dev_addr.rainmaker_node_id, rainmaker_node_id, strlen(rainmaker_node_id))) {
return esp_matter::endpoint::get_id(current_dev->dev->endpoint);
}
current_dev = current_dev->next;
}
return chip::kInvalidEndpointId;
}
char* app_bridge_get_rainmaker_node_id_by_matter_endpointid(uint16_t matter_endpointid)
{
app_bridged_device_t *current_dev = g_bridged_device_list;
while (current_dev) {
if ((current_dev->dev_type == ESP_MATTER_BRIDGED_DEVICE_TYPE_RAINMAKER) && current_dev->dev &&
(esp_matter::endpoint::get_id(current_dev->dev->endpoint) == matter_endpointid)) {
return current_dev->dev_addr.rainmaker_node_id;
}
current_dev = current_dev->next;
}
return NULL;
}
char* app_bridge_get_rainmaker_node_name_by_matter_endpointid(uint16_t matter_endpointid)
{
app_bridged_device_t *current_dev = g_bridged_device_list;
while (current_dev) {
if ((current_dev->dev_type == ESP_MATTER_BRIDGED_DEVICE_TYPE_RAINMAKER) && current_dev->dev &&
(esp_matter::endpoint::get_id(current_dev->dev->endpoint) == matter_endpointid)) {
return current_dev->dev_addr.rainmaker_node_name;
}
current_dev = current_dev->next;
}
return NULL;
}
#endif
@@ -26,6 +26,8 @@ typedef enum {
ESP_MATTER_BRIDGED_DEVICE_TYPE_BLEMESH,
/** ESP-NOW */
ESP_MATTER_BRIDGED_DEVICE_TYPE_ESPNOW,
/** Rainmaker */
ESP_MATTER_BRIDGED_DEVICE_TYPE_RAINMAKER,
} app_bridged_device_type_t;
/* Bridged Device Address */
@@ -43,6 +45,11 @@ typedef union {
struct {
uint8_t espnow_macaddr[6];
};
/** Rainmaker */
struct {
char rainmaker_node_id[32];
char rainmaker_node_name[32];
};
} app_bridged_device_address_t;
/* Bridged Device */
@@ -66,6 +73,8 @@ app_bridged_device_address_t app_bridge_blemesh_address(uint16_t blemesh_addr);
app_bridged_device_address_t app_bridge_espnow_address(uint8_t espnow_macaddr[6], uint16_t espnow_initiator_attr);
app_bridged_device_address_t app_bridge_rainmaker_address(const char* rainmaker_node_id, const char* rainmaker_node_name);
/** Bridged Device APIs */
app_bridged_device_t *app_bridge_create_bridged_device(node_t *node, uint16_t parent_endpoint_id,
uint32_t matter_device_type_id,
@@ -97,3 +106,12 @@ app_bridged_device_t *app_bridge_get_device_by_espnow_macaddr(uint8_t espnow_mac
uint16_t app_bridge_get_matter_endpointid_by_espnow_macaddr(uint8_t espnow_macaddr[6]);
uint8_t* app_bridge_get_espnow_macaddr_by_matter_endpointid(uint16_t matter_endpointid);
/** Rainmaker Device APIs */
app_bridged_device_t *app_bridge_get_device_by_rainmaker_node_id(char rainmaker_node_id[32]);
uint16_t app_bridge_get_matter_endpointid_by_rainmaker_node_id(char rainmaker_node_id[32]);
char* app_bridge_get_rainmaker_node_id_by_matter_endpointid(uint16_t matter_endpointid);
char* app_bridge_get_rainmaker_node_name_by_matter_endpointid(uint16_t matter_endpointid);