diff --git a/ESP32-Rainmaker-Switch/.devcontainer/Dockerfile b/ESP32-Rainmaker-Switch/.devcontainer/Dockerfile new file mode 100644 index 00000000..8d7b92d6 --- /dev/null +++ b/ESP32-Rainmaker-Switch/.devcontainer/Dockerfile @@ -0,0 +1,47 @@ +FROM espressif/idf + +ARG DEBIAN_FRONTEND=nointeractive +ARG CONTAINER_USER=esp +ARG USER_UID=1050 +ARG USER_GID=$USER_UID + +RUN apt-get update \ + && apt install -y -q \ + cmake \ + git \ + libglib2.0-0 \ + libnuma1 \ + libpixman-1-0 \ + && rm -rf /var/lib/apt/lists/* + +# QEMU +ENV QEMU_REL=esp_develop_8.2.0_20240122 +ENV QEMU_SHA256=e7c72ef5705ad1444d391711088c8717fc89f42e9bf6d1487f9c2a326b8cfa83 +ENV QEMU_DIST=qemu-xtensa-softmmu-${QEMU_REL}-x86_64-linux-gnu.tar.xz +ENV QEMU_URL=https://github.com/espressif/qemu/releases/download/esp-develop-8.2.0-20240122/${QEMU_DIST} + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN wget --no-verbose ${QEMU_URL} \ + && echo "${QEMU_SHA256} *${QEMU_DIST}" | sha256sum --check --strict - \ + && tar -xf $QEMU_DIST -C /opt \ + && rm ${QEMU_DIST} + +ENV PATH=/opt/qemu/bin:${PATH} + +RUN groupadd --gid $USER_GID $CONTAINER_USER \ + && adduser --uid $USER_UID --gid $USER_GID --disabled-password --gecos "" ${CONTAINER_USER} \ + && usermod -a -G root $CONTAINER_USER && usermod -a -G dialout $CONTAINER_USER + +RUN chmod -R 775 /opt/esp/python_env/ + +USER ${CONTAINER_USER} +ENV USER=${CONTAINER_USER} +WORKDIR /home/${CONTAINER_USER} + +RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc + +ENTRYPOINT [ "/opt/esp/entrypoint.sh" ] + +CMD ["/bin/bash", "-c"] \ No newline at end of file diff --git a/ESP32-Rainmaker-Switch/.devcontainer/devcontainer.json b/ESP32-Rainmaker-Switch/.devcontainer/devcontainer.json new file mode 100644 index 00000000..09d3b422 --- /dev/null +++ b/ESP32-Rainmaker-Switch/.devcontainer/devcontainer.json @@ -0,0 +1,36 @@ +{ + "name": "ESP-IDF QEMU", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.espIdfPath": "/opt/esp/idf", + "idf.customExtraPaths": "", + "idf.pythonBinPath": "/opt/esp/python_env/idf5.4_py3.12_env/bin/python", + "idf.toolsPath": "/opt/esp", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension" + ] + }, + "codespaces": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.espIdfPath": "/opt/esp/idf", + "idf.customExtraPaths": "", + "idf.pythonBinPath": "/opt/esp/python_env/idf5.4_py3.12_env/bin/python", + "idf.toolsPath": "/opt/esp", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension", + "espressif.esp-idf-web" + ] + } + }, + "runArgs": ["--privileged"] +} \ No newline at end of file diff --git a/ESP32-Rainmaker-Switch/.vscode/c_cpp_properties.json b/ESP32-Rainmaker-Switch/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..898498ca --- /dev/null +++ b/ESP32-Rainmaker-Switch/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPathWin}\\tools\\xtensa-esp-elf\\esp-13.2.0_20230928\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe", + "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/ESP32-Rainmaker-Switch/.vscode/launch.json b/ESP32-Rainmaker-Switch/.vscode/launch.json new file mode 100644 index 00000000..2511a38a --- /dev/null +++ b/ESP32-Rainmaker-Switch/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + }, + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file diff --git a/ESP32-Rainmaker-Switch/.vscode/settings.json b/ESP32-Rainmaker-Switch/.vscode/settings.json new file mode 100644 index 00000000..e9ac762b --- /dev/null +++ b/ESP32-Rainmaker-Switch/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "idf.adapterTargetName": "esp32", + "idf.customExtraPaths": "c:\\Users\\alex\\.espressif\\tools\\tools\\xtensa-esp-elf-gdb\\14.2_20240403\\xtensa-esp-elf-gdb\\bin;c:\\Users\\alex\\.espressif\\tools\\tools\\riscv32-esp-elf-gdb\\14.2_20240403\\riscv32-esp-elf-gdb\\bin;c:\\Users\\alex\\.espressif\\tools\\tools\\xtensa-esp-elf\\esp-13.2.0_20230928\\xtensa-esp-elf\\bin;c:\\Users\\alex\\.espressif\\tools\\tools\\riscv32-esp-elf\\esp-13.2.0_20230928\\riscv32-esp-elf\\bin;c:\\Users\\alex\\.espressif\\tools\\tools\\esp32ulp-elf\\2.35_20220830\\esp32ulp-elf\\bin;c:\\Users\\alex\\.espressif\\tools\\tools\\cmake\\3.24.0\\bin;c:\\Users\\alex\\.espressif\\tools\\tools\\openocd-esp32\\v0.12.0-esp32-20240318\\openocd-esp32\\bin;c:\\Users\\alex\\.espressif\\tools\\tools\\ninja\\1.11.1;c:\\Users\\alex\\.espressif\\tools\\tools\\idf-exe\\1.0.3;c:\\Users\\alex\\.espressif\\tools\\tools\\ccache\\4.8\\ccache-4.8-windows-x86_64;c:\\Users\\alex\\.espressif\\tools\\tools\\dfu-util\\0.11\\dfu-util-0.11-win64;c:\\Users\\alex\\.espressif\\tools\\tools\\esp-rom-elfs\\20230320", + "idf.customExtraVars": { + "OPENOCD_SCRIPTS": "c:\\Users\\alex\\.espressif\\tools\\tools\\openocd-esp32\\v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts", + "IDF_CCACHE_ENABLE": "1", + "ESP_ROM_ELF_DIR": "c:\\Users\\alex\\.espressif\\tools\\tools\\esp-rom-elfs\\20230320/" + }, + "idf.espIdfPathWin": "C:\\Users\\alex\\esp\\v5.2.2\\esp-idf", + "idf.espAdfPathWin": "C:\\Users\\alex\\.espressif\\esp-adf", + "idf.openOcdConfigs": [ + "board/esp32-wrover-kit-3.3v.cfg" + ], + "idf.portWin": "COM29", + "idf.pythonBinPathWin": "c:\\Users\\alex\\.espressif\\tools\\python_env\\idf5.2_py3.11_env\\Scripts\\python.exe", + "idf.toolsPathWin": "c:\\Users\\alex\\.espressif\\tools" +} diff --git a/ESP32-Rainmaker-Switch/.vscode/tasks.json b/ESP32-Rainmaker-Switch/.vscode/tasks.json new file mode 100644 index 00000000..1dc79158 --- /dev/null +++ b/ESP32-Rainmaker-Switch/.vscode/tasks.json @@ -0,0 +1,259 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build - Build project", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py build", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py build", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Set ESP-IDF Target", + "type": "shell", + "command": "${command:espIdf.setTarget}", + "problemMatcher": { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + }, + { + "label": "Clean - Clean the project", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py fullclean", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py fullclean", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ] + }, + { + "label": "Flash - Flash the device", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py -p ${config:idf.port} -b ${config:idf.flashBaudRate} flash", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py flash -p ${config:idf.portWin} -b ${config:idf.flashBaudRate}", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ] + }, + { + "label": "Monitor: Start the monitor", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py -p ${config:idf.port} monitor", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py -p ${config:idf.portWin} monitor", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ], + "dependsOn": "Flash - Flash the device" + }, + { + "label": "OpenOCD: Start openOCD", + "type": "shell", + "presentation": { + "echo": true, + "reveal": "never", + "focus": false, + "panel": "new" + }, + "command": "openocd -s ${command:espIdf.getOpenOcdScriptValue} ${command:espIdf.getOpenOcdConfigs}", + "windows": { + "command": "openocd.exe -s ${command:espIdf.getOpenOcdScriptValue} ${command:espIdf.getOpenOcdConfigs}", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + }, + { + "label": "adapter", + "type": "shell", + "command": "${config:idf.pythonBinPath}", + "isBackground": true, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}", + "PYTHONPATH": "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter" + } + }, + "problemMatcher": { + "background": { + "beginsPattern": "\bDEBUG_ADAPTER_STARTED\b", + "endsPattern": "DEBUG_ADAPTER_READY2CONNECT", + "activeOnStart": true + }, + "pattern": { + "regexp": "(\\d+)-(\\d+)-(\\d+)\\s(\\d+):(\\d+):(\\d+),(\\d+)\\s-(.+)\\s(ERROR)", + "file": 8, + "line": 2, + "column": 3, + "severity": 4, + "message": 9 + } + }, + "args": [ + "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter_main.py", + "-e", + "${workspaceFolder}/build/${command:espIdf.getProjectName}.elf", + "-s", + "$OPENOCD_SCRIPTS", + "-dn", + "esp32", + "-om", + "connect_to_instance", + "-t", + "xtensa-esp32-elf-" + + ], + "windows": { + "command": "${config:idf.pythonBinPathWin}", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}", + "PYTHONPATH": "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter" + } + } + } + } + ] +} \ No newline at end of file diff --git a/ESP32-Rainmaker-Switch/CMakeLists.txt b/ESP32-Rainmaker-Switch/CMakeLists.txt new file mode 100644 index 00000000..f99abe02 --- /dev/null +++ b/ESP32-Rainmaker-Switch/CMakeLists.txt @@ -0,0 +1,62 @@ +# 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(DEFINED ENV{RMAKER_PATH}) + set(RMAKER_PATH $ENV{RMAKER_PATH}) +else() + set(RMAKER_PATH ${CMAKE_CURRENT_LIST_DIR}/../../..) +endif(DEFINED ENV{RMAKER_PATH}) + +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}) + +if(NOT DEFINED ENV{ESP_MATTER_DEVICE_PATH}) + if("${IDF_TARGET}" STREQUAL "esp32" OR "${IDF_TARGET}" STREQUAL "") + set(ENV{ESP_MATTER_DEVICE_PATH} $ENV{ESP_MATTER_PATH}/device_hal/device/esp32_devkit_c) + elseif("${IDF_TARGET}" STREQUAL "esp32c3") + set(ENV{ESP_MATTER_DEVICE_PATH} $ENV{ESP_MATTER_PATH}/device_hal/device/esp32c3_devkit_m) + elseif("${IDF_TARGET}" STREQUAL "esp32s3") + set(ENV{ESP_MATTER_DEVICE_PATH} $ENV{ESP_MATTER_PATH}/device_hal/device/esp32s3_devkit_c) + elseif("${IDF_TARGET}" STREQUAL "esp32c6") + set(ENV{ESP_MATTER_DEVICE_PATH} $ENV{ESP_MATTER_PATH}/device_hal/device/esp32c6_devkit_c) + else() + message(FATAL_ERROR "Unsupported IDF_TARGET") + endif() +endif(NOT DEFINED ENV{ESP_MATTER_DEVICE_PATH}) + +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) +include($ENV{ESP_MATTER_DEVICE_PATH}/esp_matter_device.cmake) + +idf_build_set_property(RAINMAKER_ENABLED 1) + +set(EXTRA_COMPONENT_DIRS + "${MATTER_SDK_PATH}/config/esp32/components" + "${ESP_MATTER_PATH}/components" + "${ESP_MATTER_PATH}/device_hal/device" + "${ESP_MATTER_PATH}/examples/common" + "${RMAKER_PATH}/examples/common/app_insights" + "${RMAKER_PATH}/components/esp_rainmaker" + "${RMAKER_PATH}/components/esp_schedule" + "${RMAKER_PATH}/components/json_generator" + "${RMAKER_PATH}/components/json_parser" + "${RMAKER_PATH}/components/rmaker_common" + ${extra_components_dirs_append}) + +# Include insights only for IDF <= v4.4 as it is already included by esp-matter for >= v5.0 +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS_EQUAL "4.4") + list(APPEND EXTRA_COMPONENT_DIRS "${RMAKER_PATH}/components/esp-insights/components") +endif() + +project(ESP32-Rainmaker-Switch) + +idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++17;-Os;-DCHIP_HAVE_CONFIG_H" 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" APPEND) diff --git a/ESP32-Rainmaker-Switch/README.md b/ESP32-Rainmaker-Switch/README.md new file mode 100644 index 00000000..a108c5c3 --- /dev/null +++ b/ESP32-Rainmaker-Switch/README.md @@ -0,0 +1,12 @@ +# Matter + Rainmaker Switch Example + +## What to expect in this example? + +- This demonstrates a Matter + RainMaker Switch. Matter is used for commissioning (also known as Wi-Fi provisioning) and local control, whereas RainMaker is used for remote control and OTA upgrades. +- This example uses the BOOT button and RGB LED on the ESP32-C3-DevKitC board to demonstrate a switch. +- To commission the device, scan the QR Code generated by the mfg_tool script using ESP RainMaker app. +- Pressing the BOOT button will send a toggle the power state of switch and send an on/off command to the remote device. This will also reflect on the phone app. +- Toggling the button on the phone app should toggle the LED on your board. +- To test remote control, change the network connection of the mobile. + +> Please refer to the [README in the parent folder](../README.md) for instructions. diff --git a/ESP32-Rainmaker-Switch/main/CMakeLists.txt b/ESP32-Rainmaker-Switch/main/CMakeLists.txt new file mode 100644 index 00000000..b25066b2 --- /dev/null +++ b/ESP32-Rainmaker-Switch/main/CMakeLists.txt @@ -0,0 +1,9 @@ +set(PRIV_REQUIRES_LIST device esp_matter esp_matter_console esp_matter_rainmaker app_reset + esp_rainmaker app_insights) + +idf_component_register(SRCS ./app_main.cpp ./app_matter.cpp ./app_driver.cpp + PRIV_INCLUDE_DIRS "." + PRIV_REQUIRES ${PRIV_REQUIRES_LIST}) + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) +target_compile_options(${COMPONENT_LIB} PRIVATE "-DCHIP_HAVE_CONFIG_H") diff --git a/ESP32-Rainmaker-Switch/main/app_driver.cpp b/ESP32-Rainmaker-Switch/main/app_driver.cpp new file mode 100644 index 00000000..47ecb0d1 --- /dev/null +++ b/ESP32-Rainmaker-Switch/main/app_driver.cpp @@ -0,0 +1,59 @@ +/* + 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static const char *TAG = "app_driver"; +extern uint16_t switch_endpoint_id; +static bool g_power = DEFAULT_POWER; + +/* Do any conversions/remapping for the actual value here */ +esp_err_t app_driver_switch_set_power(led_driver_handle_t handle, bool val) +{ + g_power = val; + return led_driver_set_power(handle, val); +} + +static void app_driver_button_toggle_cb(void *handle, void *usr_data) +{ + ESP_LOGI(TAG, "Toggle button pressed"); + app_matter_send_command_binding(!g_power); +} + +esp_err_t app_driver_light_set_defaults() +{ + return app_driver_switch_set_power((led_driver_handle_t)esp_matter::endpoint::get_priv_data(switch_endpoint_id), + DEFAULT_POWER); +} + +app_driver_handle_t app_driver_light_init() +{ + /* Initialize led */ + led_driver_config_t config = led_driver_get_config(); + led_driver_handle_t handle = led_driver_init(&config); + return (app_driver_handle_t)handle; +} + +app_driver_handle_t app_driver_button_init(void *user_data) +{ + /* Initialize button */ + button_config_t config = button_driver_get_config(); + button_handle_t handle = iot_button_create(&config); + iot_button_register_cb(handle, BUTTON_PRESS_DOWN, app_driver_button_toggle_cb, user_data); + return (app_driver_handle_t)handle; +} diff --git a/ESP32-Rainmaker-Switch/main/app_main.cpp b/ESP32-Rainmaker-Switch/main/app_main.cpp new file mode 100644 index 00000000..17eeeb16 --- /dev/null +++ b/ESP32-Rainmaker-Switch/main/app_main.cpp @@ -0,0 +1,114 @@ +/* + 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static const char *TAG = "app_main"; + +static app_driver_handle_t switch_handle; + +/* Callback to handle commands received from the RainMaker cloud */ +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 (ctx) { + ESP_LOGI(TAG, "Received write request via : %s", esp_rmaker_device_cb_src_to_str(ctx->src)); + } + + const char *param_name = esp_rmaker_param_get_name(param); + if (strcmp(param_name, ESP_RMAKER_DEF_POWER_NAME) == 0) { + app_matter_send_command_binding(val.val.b); + } + return ESP_OK; +} + +extern "C" void app_main() +{ + /* Initialize NVS. */ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + /* Initialize drivers for light and button */ + switch_handle = app_driver_light_init(); + app_driver_switch_set_power(switch_handle, DEFAULT_POWER); + app_driver_handle_t button_handle = app_driver_button_init(switch_handle); + app_reset_button_register(button_handle); + + /* Initialize matter */ + app_matter_init(); + app_matter_switch_create(switch_handle); + + /* Matter start */ + app_matter_start(); + + /* Initialize the ESP RainMaker Agent. + * Create Lightbulb device and its parameters. + * */ + esp_rmaker_config_t rainmaker_cfg = { + .enable_time_sync = false, + }; + esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "ESP RainMaker Device", "Switch"); + if (!node) { + ESP_LOGE(TAG, "Could not initialise node."); + vTaskDelay(5000 / portTICK_PERIOD_MS); + abort(); + } + esp_rmaker_device_t *switch_device = esp_rmaker_switch_device_create(SWITCH_DEVICE_NAME, NULL, DEFAULT_POWER); + esp_rmaker_device_add_cb(switch_device, write_cb, NULL); + + + esp_rmaker_node_add_device(node, switch_device); + + /* Enable OTA */ + esp_rmaker_ota_config_t ota_config = { + .server_cert = ESP_RMAKER_OTA_DEFAULT_SERVER_CERT, + }; + esp_rmaker_ota_enable(&ota_config, OTA_USING_PARAMS); + + /* Enable timezone service which will be require for setting appropriate timezone + * from the phone apps for scheduling to work correctly. + * For more information on the various ways of setting timezone, please check + * https://rainmaker.espressif.com/docs/time-service.html. + */ + esp_rmaker_timezone_service_enable(); + + /* Enable scheduling. */ + esp_rmaker_schedule_enable(); + + /* Enable Scenes */ + esp_rmaker_scenes_enable(); + + /* Enable Insights. Requires CONFIG_ESP_INSIGHTS_ENABLED=y */ + app_insights_enable(); + + /* Pre start */ + ESP_ERROR_CHECK(app_matter_pre_rainmaker_start()); + + /* Start the ESP RainMaker Agent */ + esp_rmaker_start(); + + /* Enable Matter diagnostics console*/ + app_matter_enable_matter_console(); +} diff --git a/ESP32-Rainmaker-Switch/main/app_matter.cpp b/ESP32-Rainmaker-Switch/main/app_matter.cpp new file mode 100644 index 00000000..8674d2d6 --- /dev/null +++ b/ESP32-Rainmaker-Switch/main/app_matter.cpp @@ -0,0 +1,171 @@ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace esp_matter; +using namespace esp_matter::attribute; +using namespace esp_matter::cluster; +using namespace esp_matter::endpoint; +using namespace chip::app::Clusters; + +static const char *TAG = "app_matter"; +uint16_t switch_endpoint_id; + +esp_err_t app_matter_send_command_binding(bool power) +{ + client::command_handle_t command; + command.cluster_id = OnOff::Id; + if (power == true) { + command.command_id = OnOff::Commands::On::Id; + } else { + command.command_id = OnOff::Commands::Off::Id; + } + + lock::chip_stack_lock(portMAX_DELAY); + esp_err_t err = client::cluster_update(switch_endpoint_id, &command); + lock::chip_stack_unlock(); + return err; +} + +static esp_err_t app_identification_cb(identification::callback_type_t type, uint16_t endpoint_id, uint8_t effect_id, + uint8_t effect_variant, void *priv_data) +{ + ESP_LOGI(TAG, "Identification callback: type: %d, effect: %d", type, effect_id); + return ESP_OK; +} + +static esp_err_t app_attribute_update_cb(attribute::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) +{ + return ESP_OK; +} + +static void app_event_cb(const ChipDeviceEvent *event, intptr_t arg) +{ + switch (event->Type) { + case chip::DeviceLayer::DeviceEventType::PublicEventTypes::kCommissioningComplete: + ESP_LOGI(TAG, "Commissioning complete"); + break; + + default: + break; + } +} + + +esp_err_t app_matter_init() +{ + /* Create a Matter node */ + node::config_t node_config; + node_t *node = node::create(&node_config, app_attribute_update_cb, app_identification_cb); + + /* The node and endpoint handles can be used to create/add other endpoints and clusters. */ + if (!node) { + ESP_LOGE(TAG, "Matter node creation failed"); + return ESP_FAIL; + } + + /* Add custom rainmaker cluster */ + return rainmaker::init(); +} + +static void app_matter_client_command_callback(client::peer_device_t *peer_device, client::command_handle_t *command_handle, + void *priv_data) +{ + if (command_handle->cluster_id == OnOff::Id) { + /* RainMaker update */ + + const esp_rmaker_node_t *node = esp_rmaker_get_node(); + esp_rmaker_device_t *device = esp_rmaker_node_get_device_by_name(node, SWITCH_DEVICE_NAME); + esp_rmaker_param_t *param = esp_rmaker_device_get_param_by_name(device, ESP_RMAKER_DEF_POWER_NAME); + + if (command_handle->command_id == OnOff::Commands::Off::Id) { + on_off::command::send_off(peer_device, command_handle->endpoint_id); + app_driver_switch_set_power((app_driver_handle_t)priv_data, false); + esp_rmaker_param_update_and_report(param, esp_rmaker_bool(false)); + } else if (command_handle->command_id == OnOff::Commands::On::Id) { + on_off::command::send_on(peer_device, command_handle->endpoint_id); + app_driver_switch_set_power((app_driver_handle_t)priv_data, true); + esp_rmaker_param_update_and_report(param, esp_rmaker_bool(true)); + } else if (command_handle->command_id == OnOff::Commands::Toggle::Id) { + on_off::command::send_toggle(peer_device, command_handle->endpoint_id); + esp_rmaker_param_val_t *param_val = esp_rmaker_param_get_val(param); + app_driver_switch_set_power((app_driver_handle_t)priv_data, !param_val->val.b); + esp_rmaker_param_update_and_report(param, esp_rmaker_bool(!param_val->val.b)); + } + } else if (command_handle->cluster_id == Identify::Id) { + if (((char *)command_handle->command_data)[0] != 1) { + ESP_LOGE(TAG, "Number of parameters error"); + return; + } + identify::command::send_identify(peer_device, command_handle->endpoint_id, + strtoul((const char *)(command_handle->command_data) + 1, NULL, 16)); + } +} + +esp_err_t app_matter_switch_create(app_driver_handle_t driver_handle) +{ + node_t *node = node::get(); + if (!node) { + ESP_LOGE(TAG, "Matter node not found"); + return ESP_FAIL; + } + + on_off_switch::config_t switch_config; + endpoint_t *endpoint = on_off_switch::create(node, &switch_config, ENDPOINT_FLAG_NONE, driver_handle); + if (!endpoint) { + ESP_LOGE(TAG, "Matter endpoint creation failed"); + return ESP_FAIL; + } + cluster::groups::config_t groups_config; + cluster::groups::create(endpoint, &groups_config, CLUSTER_FLAG_SERVER | CLUSTER_FLAG_CLIENT); + + switch_endpoint_id = endpoint::get_id(endpoint); + ESP_LOGI(TAG, "Switch created with endpoint_id %d", switch_endpoint_id); + return ESP_OK; +} + +esp_err_t app_matter_pre_rainmaker_start() +{ + /* Other initializations for custom rainmaker cluster */ + return rainmaker::start(); +} + +esp_err_t app_matter_start() +{ + client::set_command_callback(app_matter_client_command_callback, NULL, NULL); + esp_err_t err = esp_matter::start(app_event_cb); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Matter start failed: %d", err); + } + app_matter_send_command_binding(DEFAULT_POWER); + return err; +} + +void app_matter_enable_matter_console() +{ +#if CONFIG_ENABLE_CHIP_SHELL + esp_matter::console::diagnostics_register_commands(); + esp_matter::console::init(); +#else + ESP_LOGI(TAG, "Set CONFIG_ENABLE_CHIP_SHELL to enable Matter Console"); +#endif +} diff --git a/ESP32-Rainmaker-Switch/main/app_matter.h b/ESP32-Rainmaker-Switch/main/app_matter.h new file mode 100644 index 00000000..c7a5da35 --- /dev/null +++ b/ESP32-Rainmaker-Switch/main/app_matter.h @@ -0,0 +1,19 @@ +/* + 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 +#include + +esp_err_t app_matter_init(); +esp_err_t app_matter_switch_create(app_driver_handle_t driver_handle); +esp_err_t app_matter_start(); +esp_err_t app_matter_pre_rainmaker_start(); +esp_err_t app_matter_send_command_binding(bool power); +void app_matter_enable_matter_console(); diff --git a/ESP32-Rainmaker-Switch/main/app_priv.h b/ESP32-Rainmaker-Switch/main/app_priv.h new file mode 100644 index 00000000..59e48c85 --- /dev/null +++ b/ESP32-Rainmaker-Switch/main/app_priv.h @@ -0,0 +1,49 @@ +/* + 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 +#include + + +/** Default attribute values used by Rainmaker during initialization */ +#define SWITCH_DEVICE_NAME "Matter Switch" +#define DEFAULT_POWER true + +typedef void *app_driver_handle_t; + +/** Initialize the light driver + * + * This initializes the light driver associated with the selected board. + * + * @return Handle on success. + * @return NULL in case of failure. + */ +app_driver_handle_t app_driver_light_init(); + +/** Initialize the button driver + * + * This initializes the button driver associated with the selected board. + * + * @param[in] user_data Custom user data that will be used in button toggle callback. + * + * @return Handle on success. + * @return NULL in case of failure. + */ +app_driver_handle_t app_driver_button_init(void *user_data); + +/** Set LED Power + * + * @param[in] handle Pointer to switch driver handle. + * @param[in] power LED power state. + * + * @return ESP_OK on success. + * @return error in case of failure. + */ +esp_err_t app_driver_switch_set_power(app_driver_handle_t handle, bool power); diff --git a/ESP32-Rainmaker-Switch/partitions.csv b/ESP32-Rainmaker-Switch/partitions.csv new file mode 100644 index 00000000..b10b1d0e --- /dev/null +++ b/ESP32-Rainmaker-Switch/partitions.csv @@ -0,0 +1,10 @@ +# 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, 0x6000, +nvs_keys, data, nvs_keys,, 0x1000, encrypted +otadata, data, ota, , 0x2000 +phy_init, data, phy, , 0x1000, +ota_0, app, ota_0, 0x20000, 0x1E0000, +ota_1, app, ota_1, 0x200000, 0x1E0000, +fctry, data, nvs, , 0x6000, diff --git a/ESP32-Rainmaker-Switch/partitions_4mb_optimised.csv b/ESP32-Rainmaker-Switch/partitions_4mb_optimised.csv new file mode 100644 index 00000000..96a9764e --- /dev/null +++ b/ESP32-Rainmaker-Switch/partitions_4mb_optimised.csv @@ -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, 0x6000, +nvs_keys, data, nvs_keys,, 0x1000, encrypted +otadata, data, ota, , 0x2000 +phy_init, data, phy, , 0x1000, +ota_0, app, ota_0, 0x20000, 0x1E0000, +ota_1, app, ota_1, 0x200000, 0x1E0000, +reserved, 0x06, , 0x3E0000, 0x1A000, +fctry, data, nvs, 0x3FA000, 0x6000 diff --git a/ESP32-Rainmaker-Switch/sdkconfig.defaults b/ESP32-Rainmaker-Switch/sdkconfig.defaults new file mode 100644 index 00000000..7269cb62 --- /dev/null +++ b/ESP32-Rainmaker-Switch/sdkconfig.defaults @@ -0,0 +1,74 @@ +# Default to 921600 baud when flashing and monitoring device +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_ESPTOOLPY_BAUD=921600 +CONFIG_ESPTOOLPY_COMPRESSED=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y + +# Enable BT +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y + +# Disable BT connection re-attempts +CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=n +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y + +# Enable lwip ipv6 autoconfig +CONFIG_LWIP_IPV6_AUTOCONFIG=y + +# Use a custom partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_MD5=y + +# Enable chip shell +CONFIG_ENABLE_CHIP_SHELL=y + +# mbedtls +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y +CONFIG_MBEDTLS_DYNAMIC_FREE_PEER_CERT=y +CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y + +# Temporary Fix for Timer Overflows +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 + +# Enable lwIP route hooks +CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y +CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y + +# Button +CONFIG_BUTTON_PERIOD_TIME_MS=20 +CONFIG_BUTTON_LONG_PRESS_TIME_MS=5000 + +# Disable softap by default +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n + +# ESP RainMaker +CONFIG_ESP_RMAKER_USER_ID_CHECK=y +CONFIG_ESP_RMAKER_NO_CLAIM=y +CONFIG_ESP_RMAKER_USE_ESP_SECURE_CERT_MGR=y +CONFIG_ESP_RMAKER_READ_NODE_ID_FROM_CERT_CN=y +CONFIG_ESP_RMAKER_DISABLE_USER_MAPPING_PROV=y + +# ESP Matter +CONFIG_CHIP_FACTORY_NAMESPACE_PARTITION_LABEL="fctry" +CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER=y +CONFIG_ENABLE_ESP32_DEVICE_INSTANCE_INFO_PROVIDER=y +CONFIG_ENABLE_ESP32_DEVICE_INFO_PROVIDER=y +CONFIG_SEC_CERT_DAC_PROVIDER=y +CONFIG_DEVICE_VENDOR_ID=0x131B +CONFIG_DEVICE_PRODUCT_ID=0x2 + +CONFIG_ESP_SECURE_CERT_DS_PERIPHERAL=n + +# Enable HKDF in mbedtls +CONFIG_MBEDTLS_HKDF_C=y + +# Use compact attribute storage mode +CONFIG_ESP_MATTER_NVS_USE_COMPACT_ATTR_STORAGE=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 diff --git a/ESP32-Rainmaker-Switch/sdkconfig.defaults.esp32c6 b/ESP32-Rainmaker-Switch/sdkconfig.defaults.esp32c6 new file mode 100644 index 00000000..0caf64ce --- /dev/null +++ b/ESP32-Rainmaker-Switch/sdkconfig.defaults.esp32c6 @@ -0,0 +1,10 @@ +# +# Use partition table which makes use of flash to the fullest +# Can be used for other platforms as well. But please keep in mind that fctry partition address is +# different than default, and the new address needs to be specified to `rainmaker.py claim` +# +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_4mb_optimised.csv" + +# To accomodate security features +CONFIG_PARTITION_TABLE_OFFSET=0xc000 diff --git a/assets/Espressif-Rainmaker_001.png b/assets/Espressif-Rainmaker_001.png new file mode 100644 index 00000000..4edd3b33 Binary files /dev/null and b/assets/Espressif-Rainmaker_001.png differ diff --git a/assets/Espressif-Rainmaker_002.png b/assets/Espressif-Rainmaker_002.png new file mode 100644 index 00000000..94ce38a7 Binary files /dev/null and b/assets/Espressif-Rainmaker_002.png differ diff --git a/assets/Espressif-Rainmaker_003.png b/assets/Espressif-Rainmaker_003.png new file mode 100644 index 00000000..7a94c8df Binary files /dev/null and b/assets/Espressif-Rainmaker_003.png differ