1306 lines
47 KiB
C++
1306 lines
47 KiB
C++
/*
|
|
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 <esp_event.h>
|
|
#include <esp_now.h>
|
|
#include <nvs_flash.h>
|
|
#include <esp_netif.h>
|
|
#include <esp_wifi.h>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <esp_timer.h>
|
|
#include <nvs.h>
|
|
|
|
#include <esp_matter.h>
|
|
#include <esp_matter_console.h>
|
|
#include <esp_matter_ota.h>
|
|
|
|
#include <common_macros.h>
|
|
#include <log_heap_numbers.h>
|
|
|
|
#include <OtaManager.h>
|
|
|
|
#include <app_priv.h>
|
|
#include <app_reset.h>
|
|
#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
|
|
#include <platform/ESP32/OpenthreadLauncher.h>
|
|
#endif
|
|
|
|
#include <setup_payload/OnboardingCodesUtil.h>
|
|
|
|
#include <app/server/CommissioningWindowManager.h>
|
|
#include <app/server/Server.h>
|
|
|
|
#include <platform/PlatformManager.h>
|
|
|
|
#include <app/clusters/scenes-server/scenes-server.h>
|
|
#include <lib/core/CHIPConfig.h>
|
|
|
|
#ifdef CONFIG_ENABLE_SET_CERT_DECLARATION_API
|
|
#include <esp_matter_providers.h>
|
|
#include <lib/support/Span.h>
|
|
#ifdef CONFIG_SEC_CERT_DAC_PROVIDER
|
|
#include <platform/ESP32/ESP32SecureCertDACProvider.h>
|
|
#elif defined(CONFIG_FACTORY_PARTITION_DAC_PROVIDER)
|
|
#include <platform/ESP32/ESP32FactoryDataProvider.h>
|
|
#endif
|
|
using namespace chip::DeviceLayer;
|
|
#endif
|
|
|
|
static const char *TAG = "app_main";
|
|
uint16_t rgb_light_endpoint_id = 0;
|
|
uint16_t cct_light_endpoint_id = 0;
|
|
|
|
using namespace esp_matter;
|
|
using namespace esp_matter::attribute;
|
|
using namespace esp_matter::endpoint;
|
|
using namespace chip::app::Clusters;
|
|
|
|
static void matter_set_onoff(uint16_t endpoint_id, bool on)
|
|
{
|
|
attribute_t *attribute = attribute::get(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id);
|
|
if (!attribute) {
|
|
return;
|
|
}
|
|
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
|
|
attribute::get_val(attribute, &val);
|
|
val.val.b = on;
|
|
attribute::update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
|
}
|
|
|
|
static void matter_toggle_onoff(uint16_t endpoint_id)
|
|
{
|
|
attribute_t *attribute = attribute::get(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id);
|
|
if (!attribute) {
|
|
return;
|
|
}
|
|
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
|
|
attribute::get_val(attribute, &val);
|
|
val.val.b = !val.val.b;
|
|
attribute::update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
|
}
|
|
|
|
static void matter_adjust_brightness(uint16_t endpoint_id, int delta)
|
|
{
|
|
attribute_t *attribute = attribute::get(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id);
|
|
if (!attribute) {
|
|
return;
|
|
}
|
|
|
|
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
|
|
attribute::get_val(attribute, &val);
|
|
|
|
int level = static_cast<int>(val.val.u8);
|
|
level += delta;
|
|
if (level < 0) {
|
|
level = 0;
|
|
}
|
|
if (level > MATTER_BRIGHTNESS) {
|
|
level = MATTER_BRIGHTNESS;
|
|
}
|
|
|
|
val.val.u8 = static_cast<uint8_t>(level);
|
|
attribute::update(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val);
|
|
}
|
|
|
|
static void matter_adjust_color_temperature_mireds(uint16_t endpoint_id, int delta)
|
|
{
|
|
attribute_t *attribute =
|
|
attribute::get(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id);
|
|
if (!attribute) {
|
|
return;
|
|
}
|
|
|
|
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
|
|
attribute::get_val(attribute, &val);
|
|
|
|
int mireds = static_cast<int>(val.val.u16);
|
|
mireds += delta;
|
|
|
|
// Try to clamp using physical min/max if present; fall back to common CCT range.
|
|
int min_mireds = 153; // ~6500K
|
|
int max_mireds = 500; // ~2000K
|
|
attribute_t *min_attr =
|
|
attribute::get(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTempPhysicalMinMireds::Id);
|
|
attribute_t *max_attr =
|
|
attribute::get(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTempPhysicalMaxMireds::Id);
|
|
if (min_attr && max_attr) {
|
|
esp_matter_attr_val_t vmin = esp_matter_invalid(NULL);
|
|
esp_matter_attr_val_t vmax = esp_matter_invalid(NULL);
|
|
attribute::get_val(min_attr, &vmin);
|
|
attribute::get_val(max_attr, &vmax);
|
|
if (vmin.type == ESP_MATTER_VAL_TYPE_UINT16 && vmax.type == ESP_MATTER_VAL_TYPE_UINT16 &&
|
|
vmin.val.u16 > 0 && vmax.val.u16 > 0 && vmin.val.u16 < vmax.val.u16) {
|
|
min_mireds = static_cast<int>(vmin.val.u16);
|
|
max_mireds = static_cast<int>(vmax.val.u16);
|
|
}
|
|
}
|
|
|
|
if (mireds < min_mireds) {
|
|
mireds = min_mireds;
|
|
}
|
|
if (mireds > max_mireds) {
|
|
mireds = max_mireds;
|
|
}
|
|
|
|
val.val.u16 = static_cast<uint16_t>(mireds);
|
|
attribute::update(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id, &val);
|
|
}
|
|
|
|
constexpr auto k_timeout_seconds = 300;
|
|
|
|
namespace {
|
|
|
|
enum EspNowCmd : uint8_t {
|
|
kEspNowCmdToggle = 0x01,
|
|
kEspNowCmdOn = 0x02,
|
|
kEspNowCmdBrightnessUp = 0x03,
|
|
kEspNowCmdBrightnessDown = 0x04,
|
|
kEspNowCmdOff = 0x05,
|
|
kEspNowCmdPairingConfirm = 0xA5,
|
|
};
|
|
constexpr uint8_t kBrightnessStep = 20;
|
|
|
|
static constexpr uint8_t kCctWarmCmd = kEspNowCmdBrightnessDown;
|
|
static constexpr uint8_t kCctCoolCmd = kEspNowCmdBrightnessUp;
|
|
|
|
// Short/long inference for CCT endpoint brightness keys.
|
|
// Requires the remote to send repeated brightness commands while holding the key.
|
|
static constexpr uint32_t kCctLongPressRepeatGapMs = 900;
|
|
static constexpr uint8_t kCctLongPressMinRepeats = 3;
|
|
static constexpr uint32_t kCctShortPressGapMs = 350;
|
|
static constexpr uint32_t kCctPressReleaseGapMs = 1500;
|
|
static constexpr uint32_t kCctColorModeGapMs = 800;
|
|
static constexpr int kCctTempStepMireds = 10;
|
|
|
|
static constexpr chip::GroupId kReadingSceneGroupId = 0x0000;
|
|
static constexpr chip::SceneId kReadingSceneId = 0x01;
|
|
static constexpr uint8_t kReadingRgbLevel = 180;
|
|
static constexpr uint8_t kReadingCctLevel = 180;
|
|
static constexpr uint16_t kReadingCctTempMireds = 370;
|
|
|
|
static uint8_t s_cct_last_cmd = 0;
|
|
static uint8_t s_cct_repeat_count = 0;
|
|
static uint32_t s_cct_last_cmd_ms = 0;
|
|
static bool s_cct_brightness_applied = false;
|
|
static int s_cct_brightness_applied_delta = 0;
|
|
|
|
static bool s_cct_color_mode_active = false;
|
|
static uint32_t s_cct_last_repeat_ms = 0;
|
|
|
|
static bool s_suppress_driver_updates = false;
|
|
static bool s_reading_scene_stored_for_fabric[CHIP_CONFIG_MAX_FABRICS + 1] = { 0 };
|
|
|
|
static void store_reading_scene_for_fabric(intptr_t arg);
|
|
|
|
static void store_reading_scenes_for_all_fabrics(intptr_t arg)
|
|
{
|
|
(void)arg;
|
|
const auto & fabric_table = chip::Server::GetInstance().GetFabricTable();
|
|
for (auto it = fabric_table.begin(); it != fabric_table.end(); ++it) {
|
|
const chip::FabricIndex fabric = it->GetFabricIndex();
|
|
store_reading_scene_for_fabric((intptr_t) fabric);
|
|
}
|
|
}
|
|
|
|
static void store_reading_scene_for_fabric(intptr_t arg)
|
|
{
|
|
const chip::FabricIndex fabric = static_cast<chip::FabricIndex>(arg);
|
|
if (fabric == chip::kUndefinedFabricIndex || fabric > CHIP_CONFIG_MAX_FABRICS) {
|
|
return;
|
|
}
|
|
if (s_reading_scene_stored_for_fabric[fabric]) {
|
|
return;
|
|
}
|
|
|
|
auto store_for_endpoint = [&](uint16_t endpoint_id, bool with_color_temp) {
|
|
if (!endpoint_id) {
|
|
return;
|
|
}
|
|
// Require ScenesManagement cluster present.
|
|
if (!cluster::get(endpoint_id, ScenesManagement::Id)) {
|
|
ESP_LOGW(TAG, "ScenesManagement cluster missing on endpoint %u, skipping reading scene", (unsigned)endpoint_id);
|
|
return;
|
|
}
|
|
|
|
attribute_t *onoff_attr = attribute::get(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id);
|
|
attribute_t *level_attr = attribute::get(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id);
|
|
if (!onoff_attr || !level_attr) {
|
|
ESP_LOGW(TAG, "Missing OnOff/LevelControl attributes on endpoint %u, skipping reading scene", (unsigned)endpoint_id);
|
|
return;
|
|
}
|
|
|
|
esp_matter_attr_val_t prev_onoff = esp_matter_invalid(NULL);
|
|
esp_matter_attr_val_t prev_level = esp_matter_invalid(NULL);
|
|
attribute::get_val(onoff_attr, &prev_onoff);
|
|
attribute::get_val(level_attr, &prev_level);
|
|
|
|
esp_matter_attr_val_t prev_temp = esp_matter_invalid(NULL);
|
|
attribute_t *temp_attr = nullptr;
|
|
if (with_color_temp) {
|
|
temp_attr = attribute::get(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id);
|
|
if (temp_attr) {
|
|
attribute::get_val(temp_attr, &prev_temp);
|
|
}
|
|
}
|
|
|
|
s_suppress_driver_updates = true;
|
|
// Apply desired scene state to attributes (suppressed from affecting the physical driver).
|
|
esp_matter_attr_val_t v_onoff = prev_onoff;
|
|
v_onoff.val.b = true;
|
|
attribute::update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &v_onoff);
|
|
|
|
esp_matter_attr_val_t v_level = prev_level;
|
|
v_level.val.u8 = with_color_temp ? kReadingCctLevel : kReadingRgbLevel;
|
|
attribute::update(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &v_level);
|
|
|
|
if (with_color_temp && temp_attr) {
|
|
esp_matter_attr_val_t v_temp = prev_temp;
|
|
v_temp.val.u16 = kReadingCctTempMireds;
|
|
attribute::update(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id, &v_temp);
|
|
}
|
|
|
|
chip::app::Clusters::ScenesManagement::ScenesServer::Instance().StoreCurrentScene(fabric, endpoint_id, kReadingSceneGroupId,
|
|
kReadingSceneId);
|
|
|
|
// Restore previous attribute values.
|
|
attribute::update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &prev_onoff);
|
|
attribute::update(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &prev_level);
|
|
if (with_color_temp && temp_attr) {
|
|
attribute::update(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id, &prev_temp);
|
|
}
|
|
s_suppress_driver_updates = false;
|
|
};
|
|
|
|
store_for_endpoint(rgb_light_endpoint_id, false);
|
|
store_for_endpoint(cct_light_endpoint_id, true);
|
|
|
|
s_reading_scene_stored_for_fabric[fabric] = true;
|
|
ESP_LOGI(TAG, "Reading scene stored: group=0 scene=1 for fabric %u", (unsigned)fabric);
|
|
}
|
|
|
|
static void handle_brightness_key(uint8_t cmd)
|
|
{
|
|
const uint32_t now_ms = esp_log_timestamp();
|
|
|
|
// If we are in color temperature mode, keep it active until we observe a real "release" gap.
|
|
// While in this mode, any incoming brightness key packets (up/down) are treated as temperature steps.
|
|
if (s_cct_color_mode_active) {
|
|
if ((now_ms - s_cct_last_repeat_ms) > kCctPressReleaseGapMs) {
|
|
s_cct_color_mode_active = false;
|
|
s_cct_repeat_count = 0;
|
|
s_cct_brightness_applied = false;
|
|
s_cct_brightness_applied_delta = 0;
|
|
} else {
|
|
s_cct_last_repeat_ms = now_ms;
|
|
if (cct_light_endpoint_id) {
|
|
if (cmd == kCctCoolCmd) {
|
|
matter_adjust_color_temperature_mireds(cct_light_endpoint_id, -kCctTempStepMireds);
|
|
} else if (cmd == kCctWarmCmd) {
|
|
matter_adjust_color_temperature_mireds(cct_light_endpoint_id, +kCctTempStepMireds);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// For short presses we want fast response: allow a new short press after a small gap.
|
|
const bool new_sequence = (cmd != s_cct_last_cmd) || ((now_ms - s_cct_last_cmd_ms) > kCctShortPressGapMs);
|
|
if (new_sequence) {
|
|
// New press: allow one brightness step again.
|
|
s_cct_last_cmd = cmd;
|
|
s_cct_repeat_count = 1;
|
|
s_cct_brightness_applied = false;
|
|
s_cct_brightness_applied_delta = 0;
|
|
} else {
|
|
// Same press: only count repeat if it is within the repeat gap.
|
|
if ((now_ms - s_cct_last_cmd_ms) <= kCctLongPressRepeatGapMs) {
|
|
if (s_cct_repeat_count < 255) {
|
|
s_cct_repeat_count++;
|
|
}
|
|
}
|
|
}
|
|
s_cct_last_cmd_ms = now_ms;
|
|
|
|
if (!s_cct_color_mode_active && s_cct_repeat_count >= kCctLongPressMinRepeats) {
|
|
s_cct_color_mode_active = true;
|
|
s_cct_last_repeat_ms = now_ms;
|
|
if (s_cct_brightness_applied && s_cct_brightness_applied_delta != 0) {
|
|
if (rgb_light_endpoint_id) {
|
|
matter_adjust_brightness(rgb_light_endpoint_id, -s_cct_brightness_applied_delta);
|
|
}
|
|
if (cct_light_endpoint_id) {
|
|
matter_adjust_brightness(cct_light_endpoint_id, -s_cct_brightness_applied_delta);
|
|
}
|
|
s_cct_brightness_applied = false;
|
|
s_cct_brightness_applied_delta = 0;
|
|
}
|
|
ESP_LOGI(TAG, "CCT long press detected -> color temperature mode");
|
|
}
|
|
|
|
if (s_cct_color_mode_active) {
|
|
// Apply one temperature step immediately on the packet that triggers long-press.
|
|
s_cct_last_repeat_ms = now_ms;
|
|
if (cct_light_endpoint_id) {
|
|
if (cmd == kCctCoolCmd) {
|
|
matter_adjust_color_temperature_mireds(cct_light_endpoint_id, -kCctTempStepMireds);
|
|
} else if (cmd == kCctWarmCmd) {
|
|
matter_adjust_color_temperature_mireds(cct_light_endpoint_id, +kCctTempStepMireds);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Short press: apply only one brightness step for both endpoints.
|
|
if (!s_cct_brightness_applied) {
|
|
if (cmd == kEspNowCmdBrightnessUp) {
|
|
s_cct_brightness_applied_delta = kBrightnessStep;
|
|
s_cct_brightness_applied = true;
|
|
if (rgb_light_endpoint_id) {
|
|
matter_adjust_brightness(rgb_light_endpoint_id, kBrightnessStep);
|
|
}
|
|
if (cct_light_endpoint_id) {
|
|
matter_adjust_brightness(cct_light_endpoint_id, kBrightnessStep);
|
|
}
|
|
} else if (cmd == kEspNowCmdBrightnessDown) {
|
|
s_cct_brightness_applied_delta = -static_cast<int>(kBrightnessStep);
|
|
s_cct_brightness_applied = true;
|
|
if (rgb_light_endpoint_id) {
|
|
matter_adjust_brightness(rgb_light_endpoint_id, -static_cast<int>(kBrightnessStep));
|
|
}
|
|
if (cct_light_endpoint_id) {
|
|
matter_adjust_brightness(cct_light_endpoint_id, -static_cast<int>(kBrightnessStep));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static constexpr uint8_t kBootsToPair = 5;
|
|
static constexpr uint8_t kBootsToFactoryReset = 3;
|
|
static constexpr uint32_t kBootWindowMs = 60000; // 60 seconds window for 3 reboots
|
|
static constexpr uint32_t kPairingConfirmDelayMs = 5000;
|
|
static constexpr char kBootNvsNamespace[] = "boot_config";
|
|
static constexpr char kBootNvsKeyPair[] = "bootCount";
|
|
static constexpr char kBootNvsKeyFactory[] = "bootCntFactory";
|
|
|
|
static constexpr uint32_t kPairingBeaconMagic = 0xABCDEF01;
|
|
|
|
static bool s_espnow_inited = false;
|
|
static bool s_pairing_mode_active = false;
|
|
static bool s_boot_counter_reset_pending = false;
|
|
static bool s_factory_reset_pending = false;
|
|
static bool s_wifi_inited_for_espnow = false;
|
|
static bool s_matter_started = false;
|
|
static bool s_wifi_sta_connected = false;
|
|
static bool s_has_ipv4 = false;
|
|
static bool s_has_ipv6 = false;
|
|
static bool s_commissioned = false;
|
|
static bool s_commissioning_window_open = false;
|
|
static uint8_t g_espnow_channel = 1;
|
|
static esp_timer_handle_t s_boot_counter_reset_timer = nullptr;
|
|
static esp_timer_handle_t s_pairing_confirm_timer = nullptr;
|
|
static esp_timer_handle_t s_factory_reset_confirm_timer = nullptr;
|
|
static esp_timer_handle_t s_pairing_beacon_timer = nullptr;
|
|
static esp_timer_handle_t s_espnow_retry_timer = nullptr;
|
|
|
|
static void espnow_try_init();
|
|
static void factory_reset_work(intptr_t arg);
|
|
|
|
struct EspNowCmdPacket {
|
|
uint8_t cmd;
|
|
uint8_t src_addr[6];
|
|
};
|
|
|
|
static TaskHandle_t s_http_ota_task_handle = nullptr;
|
|
|
|
static void http_ota_progress_cb(int progress)
|
|
{
|
|
ESP_LOGI(TAG, "HTTP OTA progress: %d%%", progress);
|
|
}
|
|
|
|
static void http_ota_task(void *arg)
|
|
{
|
|
char *url = static_cast<char *>(arg);
|
|
OtaManager *ota = OtaManager::GetInstance();
|
|
ota->init();
|
|
ota->setProgressCallback(http_ota_progress_cb);
|
|
|
|
if (!url) {
|
|
ESP_LOGE(TAG, "HTTP OTA: url is null");
|
|
} else {
|
|
ESP_LOGW(TAG, "HTTP OTA starting: %s", url);
|
|
bool ok = ota->performOta(url, true);
|
|
if (!ok) {
|
|
ESP_LOGE(TAG, "HTTP OTA failed: %s", ota->getLastError().c_str());
|
|
}
|
|
}
|
|
|
|
if (url) {
|
|
free(url);
|
|
}
|
|
s_http_ota_task_handle = nullptr;
|
|
vTaskDelete(nullptr);
|
|
}
|
|
|
|
static esp_err_t http_ota_dispatch(int argc, char **argv)
|
|
{
|
|
if (argc < 1) {
|
|
ESP_LOGI(TAG, "Usage: matter esp ota <http://host/path/firmware.bin>");
|
|
return ESP_OK;
|
|
}
|
|
if (s_http_ota_task_handle) {
|
|
ESP_LOGE(TAG, "HTTP OTA already running");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
char *url_copy = strdup(argv[0]);
|
|
if (!url_copy) {
|
|
ESP_LOGE(TAG, "HTTP OTA: strdup failed");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
BaseType_t ret = xTaskCreate(&http_ota_task, "http_ota", 16384, url_copy, 2, &s_http_ota_task_handle);
|
|
if (ret != pdPASS) {
|
|
ESP_LOGE(TAG, "HTTP OTA: failed to create task");
|
|
free(url_copy);
|
|
s_http_ota_task_handle = nullptr;
|
|
return ESP_FAIL;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void log_app_state(const char *reason)
|
|
{
|
|
ESP_LOGW(TAG,
|
|
"[APP_STATE] %s matter=%d wifi_conn=%d ip4=%d ip6=%d commissioned=%d cw_open=%d espnow=%d ch=%u pairing=%d",
|
|
reason ? reason : "(null)", (int)s_matter_started, (int)s_wifi_sta_connected, (int)s_has_ipv4,
|
|
(int)s_has_ipv6, (int)s_commissioned, (int)s_commissioning_window_open, (int)s_espnow_inited,
|
|
(unsigned)g_espnow_channel, (int)s_pairing_mode_active);
|
|
}
|
|
|
|
static void handle_espnow_cmd(intptr_t arg)
|
|
{
|
|
EspNowCmdPacket *pkt = reinterpret_cast<EspNowCmdPacket *>(arg);
|
|
if (!pkt) {
|
|
return;
|
|
}
|
|
|
|
if (s_pairing_mode_active) {
|
|
if (pkt->cmd == kEspNowCmdPairingConfirm) {
|
|
ESP_LOGI(TAG, "ESP-NOW pairing confirmed (0xA5). Exiting pairing mode.");
|
|
s_pairing_mode_active = false;
|
|
if (s_pairing_beacon_timer) {
|
|
esp_timer_stop(s_pairing_beacon_timer);
|
|
}
|
|
}
|
|
free(pkt);
|
|
return;
|
|
}
|
|
|
|
switch (pkt->cmd) {
|
|
case kEspNowCmdToggle:
|
|
if (rgb_light_endpoint_id) {
|
|
matter_toggle_onoff(rgb_light_endpoint_id);
|
|
}
|
|
if (cct_light_endpoint_id) {
|
|
matter_toggle_onoff(cct_light_endpoint_id);
|
|
}
|
|
break;
|
|
case kEspNowCmdOn:
|
|
if (rgb_light_endpoint_id) {
|
|
matter_set_onoff(rgb_light_endpoint_id, true);
|
|
}
|
|
if (cct_light_endpoint_id) {
|
|
matter_set_onoff(cct_light_endpoint_id, true);
|
|
}
|
|
break;
|
|
case kEspNowCmdOff:
|
|
if (rgb_light_endpoint_id) {
|
|
matter_set_onoff(rgb_light_endpoint_id, false);
|
|
}
|
|
if (cct_light_endpoint_id) {
|
|
matter_set_onoff(cct_light_endpoint_id, false);
|
|
}
|
|
break;
|
|
case kEspNowCmdBrightnessUp:
|
|
handle_brightness_key(pkt->cmd);
|
|
break;
|
|
case kEspNowCmdBrightnessDown:
|
|
handle_brightness_key(pkt->cmd);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
free(pkt);
|
|
}
|
|
|
|
static void espnow_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len)
|
|
{
|
|
if (!recv_info || !data || len < 1) {
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "ESP-NOW RX: cmd=0x%02X len=%d from %02X:%02X:%02X:%02X:%02X:%02X", data[0], len, recv_info->src_addr[0],
|
|
recv_info->src_addr[1], recv_info->src_addr[2], recv_info->src_addr[3], recv_info->src_addr[4], recv_info->src_addr[5]);
|
|
|
|
EspNowCmdPacket *pkt = reinterpret_cast<EspNowCmdPacket *>(calloc(1, sizeof(EspNowCmdPacket)));
|
|
if (!pkt) {
|
|
return;
|
|
}
|
|
pkt->cmd = data[0];
|
|
memcpy(pkt->src_addr, recv_info->src_addr, sizeof(pkt->src_addr));
|
|
|
|
chip::DeviceLayer::PlatformMgr().ScheduleWork(handle_espnow_cmd, reinterpret_cast<intptr_t>(pkt));
|
|
}
|
|
|
|
static void pairing_beacon_timer_cb(void *arg)
|
|
{
|
|
(void)arg;
|
|
if (!s_pairing_mode_active || !s_espnow_inited) {
|
|
return;
|
|
}
|
|
|
|
uint8_t bcast[6];
|
|
memset(bcast, 0xFF, sizeof(bcast));
|
|
uint32_t beacon = kPairingBeaconMagic;
|
|
esp_now_send(bcast, reinterpret_cast<uint8_t *>(&beacon), sizeof(beacon));
|
|
}
|
|
|
|
static void start_pairing_beacon()
|
|
{
|
|
if (!s_espnow_inited) {
|
|
ESP_LOGW(TAG, "ESP-NOW not initialized yet, trying to init...");
|
|
espnow_try_init();
|
|
if (!s_espnow_inited) {
|
|
ESP_LOGE(TAG, "Failed to initialize ESP-NOW for pairing beacon");
|
|
return;
|
|
}
|
|
}
|
|
|
|
esp_now_peer_info_t broadcast_peer = {};
|
|
memset(broadcast_peer.peer_addr, 0xFF, sizeof(broadcast_peer.peer_addr));
|
|
broadcast_peer.channel = g_espnow_channel;
|
|
broadcast_peer.encrypt = false;
|
|
esp_now_add_peer(&broadcast_peer);
|
|
|
|
esp_timer_create_args_t targs = {
|
|
.callback = &pairing_beacon_timer_cb,
|
|
.arg = nullptr,
|
|
.dispatch_method = ESP_TIMER_TASK,
|
|
.name = "espnow_beacon",
|
|
.skip_unhandled_events = true,
|
|
};
|
|
if (!s_pairing_beacon_timer && esp_timer_create(&targs, &s_pairing_beacon_timer) == ESP_OK) {
|
|
esp_timer_start_periodic(s_pairing_beacon_timer, 200 * 1000);
|
|
} else if (s_pairing_beacon_timer) {
|
|
esp_timer_start_periodic(s_pairing_beacon_timer, 200 * 1000);
|
|
}
|
|
}
|
|
|
|
extern "C" void app_enter_espnow_pairing_mode()
|
|
{
|
|
if (s_pairing_mode_active) {
|
|
ESP_LOGW(TAG, "Already in ESP-NOW pairing mode");
|
|
return;
|
|
}
|
|
s_pairing_mode_active = true;
|
|
ESP_LOGW(TAG, "\n\n>>> ESP-NOW PAIRING MODE ACTIVE (button long press) <<<\n");
|
|
ESP_LOGW(TAG, "Broadcasting beacon 0x%08lX on channel %u", (unsigned long)kPairingBeaconMagic, (unsigned)g_espnow_channel);
|
|
start_pairing_beacon();
|
|
}
|
|
|
|
static void boot_counter_reset_timer_cb(void *arg)
|
|
{
|
|
(void)arg;
|
|
if (!s_boot_counter_reset_pending) {
|
|
return;
|
|
}
|
|
|
|
nvs_handle_t h;
|
|
if (nvs_open(kBootNvsNamespace, NVS_READWRITE, &h) == ESP_OK) {
|
|
nvs_set_u8(h, kBootNvsKeyPair, 0);
|
|
nvs_set_u8(h, kBootNvsKeyFactory, 0);
|
|
nvs_commit(h);
|
|
nvs_close(h);
|
|
}
|
|
s_boot_counter_reset_pending = false;
|
|
ESP_LOGW(TAG, ">>> bootCount RESET to 0 (30s window expired) <<<");
|
|
}
|
|
|
|
static void pairing_confirm_timer_cb(void *arg)
|
|
{
|
|
(void)arg;
|
|
if (s_factory_reset_pending) {
|
|
return;
|
|
}
|
|
if (s_pairing_mode_active) {
|
|
return;
|
|
}
|
|
s_pairing_mode_active = true;
|
|
ESP_LOGW(TAG, "ESP-NOW pairing mode ACTIVE (power-cycle trigger)");
|
|
|
|
ESP_LOGW(TAG, "Broadcasting beacon 0x%08lX on channel %u", (unsigned long)kPairingBeaconMagic, (unsigned)g_espnow_channel);
|
|
start_pairing_beacon();
|
|
}
|
|
|
|
static void factory_reset_confirm_timer_cb(void *arg)
|
|
{
|
|
(void)arg;
|
|
if (s_pairing_mode_active) {
|
|
return;
|
|
}
|
|
s_factory_reset_pending = true;
|
|
ESP_LOGW(TAG, "Factory reset CONFIRMED (power-cycle trigger)");
|
|
|
|
nvs_handle_t h;
|
|
if (nvs_open(kBootNvsNamespace, NVS_READWRITE, &h) == ESP_OK) {
|
|
nvs_set_u8(h, kBootNvsKeyPair, 0);
|
|
nvs_set_u8(h, kBootNvsKeyFactory, 0);
|
|
nvs_commit(h);
|
|
nvs_close(h);
|
|
}
|
|
|
|
if (s_matter_started) {
|
|
chip::DeviceLayer::PlatformMgr().ScheduleWork(factory_reset_work, 0);
|
|
}
|
|
}
|
|
|
|
static void pairing_boot_counter_init()
|
|
{
|
|
bool schedule_factory_reset = false;
|
|
bool schedule_pairing = false;
|
|
uint8_t boot_count = 0;
|
|
|
|
nvs_handle_t h;
|
|
const esp_err_t open_ret = nvs_open(kBootNvsNamespace, NVS_READWRITE, &h);
|
|
if (open_ret == ESP_OK) {
|
|
// Use a single boot counter to avoid pairing/factory reset conflicts.
|
|
esp_err_t get_ret = nvs_get_u8(h, kBootNvsKeyFactory, &boot_count);
|
|
esp_err_t get_pair_ret = ESP_OK;
|
|
if (get_ret != ESP_OK) {
|
|
get_pair_ret = nvs_get_u8(h, kBootNvsKeyPair, &boot_count);
|
|
if (get_pair_ret == ESP_OK) {
|
|
get_ret = get_pair_ret;
|
|
}
|
|
}
|
|
boot_count++;
|
|
|
|
if (boot_count == kBootsToFactoryReset) {
|
|
schedule_factory_reset = true;
|
|
} else if (boot_count == kBootsToPair) {
|
|
schedule_pairing = true;
|
|
boot_count = 0;
|
|
} else if (boot_count > kBootsToPair) {
|
|
boot_count = 0;
|
|
}
|
|
|
|
// Keep the legacy key in sync for compatibility with existing NVS content.
|
|
const esp_err_t set_pair_ret = nvs_set_u8(h, kBootNvsKeyPair, boot_count);
|
|
const esp_err_t set_factory_ret = nvs_set_u8(h, kBootNvsKeyFactory, boot_count);
|
|
const esp_err_t commit_ret = nvs_commit(h);
|
|
nvs_close(h);
|
|
|
|
ESP_LOGW(TAG,
|
|
"\n\n>>> bootCount: %u (pair=%u, factory=%u, within %lu ms, nvs_get=%d, nvs_get_pair=%d, nvs_set_pair=%d, nvs_set_factory=%d, nvs_commit=%d) <<<\n",
|
|
(unsigned)boot_count, (unsigned)kBootsToPair, (unsigned)kBootsToFactoryReset, (unsigned long)kBootWindowMs,
|
|
(int)get_ret, (int)get_pair_ret, (int)set_pair_ret, (int)set_factory_ret, (int)commit_ret);
|
|
} else {
|
|
ESP_LOGE(TAG, "bootCount: nvs_open(%s) failed: %d", kBootNvsNamespace, (int)open_ret);
|
|
}
|
|
|
|
s_boot_counter_reset_pending = (!schedule_factory_reset) && (boot_count > 0);
|
|
|
|
if (schedule_factory_reset) {
|
|
esp_timer_create_args_t targs = {
|
|
.callback = &factory_reset_confirm_timer_cb,
|
|
.arg = nullptr,
|
|
.dispatch_method = ESP_TIMER_TASK,
|
|
.name = "fac_confirm",
|
|
.skip_unhandled_events = true,
|
|
};
|
|
if (!s_factory_reset_confirm_timer && esp_timer_create(&targs, &s_factory_reset_confirm_timer) == ESP_OK) {
|
|
esp_timer_start_once(s_factory_reset_confirm_timer, (int64_t)kPairingConfirmDelayMs * 1000);
|
|
} else if (s_factory_reset_confirm_timer) {
|
|
esp_timer_stop(s_factory_reset_confirm_timer);
|
|
esp_timer_start_once(s_factory_reset_confirm_timer, (int64_t)kPairingConfirmDelayMs * 1000);
|
|
}
|
|
ESP_LOGW(TAG, "Factory reset will start in %lu ms if no further power-cycles occur", (unsigned long)kPairingConfirmDelayMs);
|
|
} else if (schedule_pairing) {
|
|
// Defer entering pairing mode to avoid conflict with an in-progress factory-reset power-cycle sequence.
|
|
esp_timer_create_args_t targs = {
|
|
.callback = &pairing_confirm_timer_cb,
|
|
.arg = nullptr,
|
|
.dispatch_method = ESP_TIMER_TASK,
|
|
.name = "pair_confirm",
|
|
.skip_unhandled_events = true,
|
|
};
|
|
if (!s_pairing_confirm_timer && esp_timer_create(&targs, &s_pairing_confirm_timer) == ESP_OK) {
|
|
esp_timer_start_once(s_pairing_confirm_timer, (int64_t)kPairingConfirmDelayMs * 1000);
|
|
} else if (s_pairing_confirm_timer) {
|
|
esp_timer_stop(s_pairing_confirm_timer);
|
|
esp_timer_start_once(s_pairing_confirm_timer, (int64_t)kPairingConfirmDelayMs * 1000);
|
|
}
|
|
ESP_LOGW(TAG, "Pairing will start in %lu ms if no further power-cycles occur", (unsigned long)kPairingConfirmDelayMs);
|
|
} else if (s_boot_counter_reset_pending) {
|
|
esp_timer_create_args_t targs = {
|
|
.callback = &boot_counter_reset_timer_cb,
|
|
.arg = nullptr,
|
|
.dispatch_method = ESP_TIMER_TASK,
|
|
.name = "bootcnt_reset",
|
|
.skip_unhandled_events = true,
|
|
};
|
|
if (!s_boot_counter_reset_timer && esp_timer_create(&targs, &s_boot_counter_reset_timer) == ESP_OK) {
|
|
esp_timer_start_once(s_boot_counter_reset_timer, (int64_t)kBootWindowMs * 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void factory_reset_work(intptr_t arg)
|
|
{
|
|
(void)arg;
|
|
ESP_LOGW(TAG, ">>> Performing factory reset (power-cycle trigger) <<<");
|
|
esp_matter::factory_reset();
|
|
}
|
|
|
|
static void wifi_init_for_espnow()
|
|
{
|
|
if (s_wifi_inited_for_espnow) {
|
|
return;
|
|
}
|
|
|
|
wifi_mode_t mode = WIFI_MODE_NULL;
|
|
esp_err_t err = esp_wifi_get_mode(&mode);
|
|
|
|
if (err == ESP_OK && mode != WIFI_MODE_NULL) {
|
|
uint8_t primary = 0;
|
|
wifi_second_chan_t second = WIFI_SECOND_CHAN_NONE;
|
|
esp_wifi_get_channel(&primary, &second);
|
|
|
|
if (primary > 0) {
|
|
g_espnow_channel = primary; // Use Wi-Fi's current channel for ESP-NOW
|
|
ESP_LOGI(TAG, "Wi-Fi managed by Matter (mode=%d, channel=%u), ESP-NOW will use channel %u", (int)mode, primary, g_espnow_channel);
|
|
if (primary != 1) {
|
|
ESP_LOGW(TAG, ">>> Router channel is %u, remote must also use channel %u <<<", primary, primary);
|
|
}
|
|
}
|
|
|
|
s_wifi_inited_for_espnow = true;
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Wi-Fi not started by Matter yet, initializing for ESP-NOW (channel %u)", (unsigned)g_espnow_channel);
|
|
|
|
err = esp_netif_init();
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGW(TAG, "esp_netif_init: %d", err);
|
|
}
|
|
|
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
|
err = esp_wifi_init(&cfg);
|
|
if (err != ESP_OK && err != ESP_ERR_WIFI_INIT_STATE) {
|
|
ESP_LOGE(TAG, "esp_wifi_init failed: %d", err);
|
|
return;
|
|
}
|
|
|
|
err = esp_wifi_set_mode(WIFI_MODE_STA);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_wifi_set_mode failed: %d", err);
|
|
return;
|
|
}
|
|
|
|
err = esp_wifi_start();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_wifi_start failed: %d", err);
|
|
return;
|
|
}
|
|
|
|
err = esp_wifi_set_channel(g_espnow_channel, WIFI_SECOND_CHAN_NONE);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(TAG, "esp_wifi_set_channel failed: %d", err);
|
|
} else {
|
|
ESP_LOGI(TAG, "Wi-Fi channel fixed to %u for ESP-NOW", (unsigned)g_espnow_channel);
|
|
}
|
|
|
|
s_wifi_inited_for_espnow = true;
|
|
}
|
|
|
|
static void espnow_try_init()
|
|
{
|
|
if (s_espnow_inited) {
|
|
return;
|
|
}
|
|
|
|
if (!s_wifi_inited_for_espnow) {
|
|
wifi_init_for_espnow();
|
|
}
|
|
|
|
wifi_mode_t mode = WIFI_MODE_NULL;
|
|
if (esp_wifi_get_mode(&mode) != ESP_OK || mode == WIFI_MODE_NULL) {
|
|
ESP_LOGW(TAG, "WiFi not ready yet (mode=%d), skip ESP-NOW init", (int)mode);
|
|
return;
|
|
}
|
|
|
|
uint8_t primary = 0;
|
|
wifi_second_chan_t second = WIFI_SECOND_CHAN_NONE;
|
|
esp_err_t ch_err = esp_wifi_get_channel(&primary, &second);
|
|
if (ch_err == ESP_OK && primary > 0) {
|
|
g_espnow_channel = primary; // Always use current Wi-Fi channel
|
|
ESP_LOGI(TAG, "WiFi channel=%u, ESP-NOW will use this channel", primary);
|
|
} else {
|
|
ESP_LOGW(TAG, "esp_wifi_get_channel failed (err=%d), using default channel %u", (int)ch_err, g_espnow_channel);
|
|
}
|
|
|
|
esp_err_t err = esp_now_init();
|
|
if (err != ESP_OK && err != ESP_ERR_ESPNOW_EXIST) {
|
|
ESP_LOGE(TAG, "esp_now_init failed, err:%d", err);
|
|
return;
|
|
}
|
|
esp_now_register_recv_cb(espnow_recv_cb);
|
|
|
|
if (s_pairing_mode_active) {
|
|
esp_now_peer_info_t broadcast_peer = {};
|
|
memset(broadcast_peer.peer_addr, 0xFF, sizeof(broadcast_peer.peer_addr));
|
|
broadcast_peer.channel = g_espnow_channel;
|
|
broadcast_peer.encrypt = false;
|
|
esp_now_add_peer(&broadcast_peer);
|
|
|
|
esp_timer_create_args_t targs = {
|
|
.callback = &pairing_beacon_timer_cb,
|
|
.arg = nullptr,
|
|
.dispatch_method = ESP_TIMER_TASK,
|
|
.name = "espnow_beacon",
|
|
.skip_unhandled_events = true,
|
|
};
|
|
if (!s_pairing_beacon_timer && esp_timer_create(&targs, &s_pairing_beacon_timer) == ESP_OK) {
|
|
esp_timer_start_periodic(s_pairing_beacon_timer, 200 * 1000);
|
|
}
|
|
}
|
|
|
|
s_espnow_inited = true;
|
|
ESP_LOGI(TAG, "ESP-NOW RX initialized");
|
|
}
|
|
|
|
static void espnow_retry_timer_cb(void *arg)
|
|
{
|
|
(void)arg;
|
|
if (!s_espnow_inited) {
|
|
espnow_try_init();
|
|
}
|
|
if (s_espnow_inited && s_espnow_retry_timer) {
|
|
esp_timer_stop(s_espnow_retry_timer);
|
|
}
|
|
}
|
|
|
|
static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
|
{
|
|
(void)arg;
|
|
(void)event_data;
|
|
|
|
if (event_base != WIFI_EVENT) {
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "WIFI_EVENT id=%ld", (long)event_id);
|
|
|
|
// For Matter over Wi-Fi: wait until connected to init ESP-NOW with correct channel
|
|
if (event_id == WIFI_EVENT_STA_CONNECTED) {
|
|
s_wifi_sta_connected = true;
|
|
log_app_state("wifi_sta_connected");
|
|
uint8_t primary = 0;
|
|
wifi_second_chan_t second = WIFI_SECOND_CHAN_NONE;
|
|
if (esp_wifi_get_channel(&primary, &second) == ESP_OK && primary > 0) {
|
|
g_espnow_channel = primary;
|
|
ESP_LOGI(TAG, "WiFi connected on channel %u, initializing ESP-NOW", primary);
|
|
if (primary != 1) {
|
|
ESP_LOGW(TAG, ">>> Remote must use channel %u (not 1) to work! <<<", primary);
|
|
}
|
|
}
|
|
// Initialize ESP-NOW after Wi-Fi is connected with correct channel
|
|
espnow_try_init();
|
|
|
|
// Start pairing beacon if in pairing mode
|
|
if (s_pairing_mode_active && s_espnow_inited) {
|
|
ESP_LOGW(TAG, "Starting ESP-NOW pairing beacon on channel %u", g_espnow_channel);
|
|
start_pairing_beacon();
|
|
}
|
|
}
|
|
|
|
if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
s_wifi_sta_connected = false;
|
|
s_has_ipv4 = false;
|
|
s_has_ipv6 = false;
|
|
log_app_state("wifi_sta_disconnected");
|
|
}
|
|
|
|
// For AP mode (fallback)
|
|
if (event_id == WIFI_EVENT_AP_START) {
|
|
espnow_try_init();
|
|
}
|
|
}
|
|
|
|
static void ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
|
{
|
|
(void)arg;
|
|
if (event_base != IP_EVENT) {
|
|
return;
|
|
}
|
|
|
|
if (event_id == IP_EVENT_STA_GOT_IP) {
|
|
s_has_ipv4 = true;
|
|
log_app_state("ip_sta_got_ipv4");
|
|
} else if (event_id == IP_EVENT_STA_LOST_IP) {
|
|
s_has_ipv4 = false;
|
|
log_app_state("ip_sta_lost_ipv4");
|
|
} else if (event_id == IP_EVENT_GOT_IP6) {
|
|
s_has_ipv6 = true;
|
|
log_app_state("ip_got_ipv6");
|
|
}
|
|
#ifdef IP_EVENT_LOST_IP6
|
|
else if (event_id == IP_EVENT_LOST_IP6) {
|
|
s_has_ipv6 = false;
|
|
log_app_state("ip_lost_ipv6");
|
|
} else {
|
|
(void)event_data;
|
|
}
|
|
#else
|
|
else {
|
|
(void)event_data;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#ifdef CONFIG_ENABLE_SET_CERT_DECLARATION_API
|
|
extern const uint8_t cd_start[] asm("_binary_certification_declaration_der_start");
|
|
extern const uint8_t cd_end[] asm("_binary_certification_declaration_der_end");
|
|
|
|
const chip::ByteSpan cdSpan(cd_start, static_cast<size_t>(cd_end - cd_start));
|
|
#endif // CONFIG_ENABLE_SET_CERT_DECLARATION_API
|
|
|
|
#if CONFIG_ENABLE_ENCRYPTED_OTA
|
|
extern const char decryption_key_start[] asm("_binary_esp_image_encryption_key_pem_start");
|
|
extern const char decryption_key_end[] asm("_binary_esp_image_encryption_key_pem_end");
|
|
|
|
static const char *s_decryption_key = decryption_key_start;
|
|
static const uint16_t s_decryption_key_len = decryption_key_end - decryption_key_start;
|
|
#endif // CONFIG_ENABLE_ENCRYPTED_OTA
|
|
|
|
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");
|
|
log_app_state("chip_if_ip_changed");
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kServerReady:
|
|
ESP_LOGI(TAG, "Server initialization complete");
|
|
chip::DeviceLayer::PlatformMgr().ScheduleWork(store_reading_scenes_for_all_fabrics, 0);
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kCommissioningComplete:
|
|
ESP_LOGI(TAG, "Commissioning complete");
|
|
s_commissioned = true;
|
|
log_app_state("commissioning_complete");
|
|
MEMORY_PROFILER_DUMP_HEAP_STAT("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");
|
|
log_app_state("commissioning_session_started");
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kCommissioningSessionStopped:
|
|
ESP_LOGI(TAG, "Commissioning session stopped");
|
|
log_app_state("commissioning_session_stopped");
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kCommissioningWindowOpened:
|
|
ESP_LOGI(TAG, "Commissioning window opened");
|
|
s_commissioning_window_open = true;
|
|
s_commissioned = false;
|
|
log_app_state("commissioning_window_opened");
|
|
MEMORY_PROFILER_DUMP_HEAP_STAT("commissioning window opened");
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kCommissioningWindowClosed:
|
|
ESP_LOGI(TAG, "Commissioning window closed");
|
|
s_commissioning_window_open = false;
|
|
log_app_state("commissioning_window_closed");
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kFabricRemoved:
|
|
{
|
|
ESP_LOGI(TAG, "Fabric removed successfully");
|
|
s_commissioned = false;
|
|
log_app_state("fabric_removed");
|
|
if (chip::Server::GetInstance().GetFabricTable().FabricCount() == 0)
|
|
{
|
|
chip::CommissioningWindowManager & commissionMgr = chip::Server::GetInstance().GetCommissioningWindowManager();
|
|
constexpr auto kTimeoutSeconds = chip::System::Clock::Seconds16(k_timeout_seconds);
|
|
if (!commissionMgr.IsCommissioningWindowOpen())
|
|
{
|
|
/* After removing last fabric, this example does not remove the Wi-Fi credentials
|
|
* and still has IP connectivity so, only advertising on DNS-SD.
|
|
*/
|
|
CHIP_ERROR err = commissionMgr.OpenBasicCommissioningWindow(kTimeoutSeconds,
|
|
chip::CommissioningWindowAdvertisement::kDnssdOnly);
|
|
if (err != CHIP_NO_ERROR)
|
|
{
|
|
ESP_LOGE(TAG, "Failed to open commissioning window, err:%" CHIP_ERROR_FORMAT, err.Format());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kFabricWillBeRemoved:
|
|
ESP_LOGI(TAG, "Fabric will be removed");
|
|
log_app_state("fabric_will_be_removed");
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kFabricUpdated:
|
|
ESP_LOGI(TAG, "Fabric is updated");
|
|
log_app_state("fabric_updated");
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kFabricCommitted:
|
|
ESP_LOGI(TAG, "Fabric is committed");
|
|
s_commissioned = true;
|
|
log_app_state("fabric_committed");
|
|
chip::DeviceLayer::PlatformMgr().ScheduleWork(store_reading_scenes_for_all_fabrics, 0);
|
|
break;
|
|
|
|
case chip::DeviceLayer::DeviceEventType::kBLEDeinitialized:
|
|
ESP_LOGI(TAG, "BLE deinitialized and memory reclaimed");
|
|
log_app_state("ble_deinit");
|
|
MEMORY_PROFILER_DUMP_HEAP_STAT("BLE deinitialized");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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: %u, effect: %u, variant: %u", type, effect_id, effect_variant);
|
|
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)
|
|
{
|
|
esp_err_t err = ESP_OK;
|
|
|
|
if (type == PRE_UPDATE) {
|
|
/* Driver update */
|
|
app_driver_handle_t driver_handle = (app_driver_handle_t)priv_data;
|
|
if (!s_suppress_driver_updates) {
|
|
err = app_driver_attribute_update(driver_handle, endpoint_id, cluster_id, attribute_id, val);
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if CONFIG_ENABLE_OTA_REQUESTOR
|
|
static void ota_requestor_start_work(intptr_t arg)
|
|
{
|
|
(void) arg;
|
|
esp_matter_ota_requestor_start();
|
|
}
|
|
#endif
|
|
|
|
extern "C" void app_main()
|
|
{
|
|
esp_err_t err = ESP_OK;
|
|
|
|
/* Initialize the ESP NVS layer */
|
|
err = nvs_flash_init();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "nvs_flash_init failed: %d", (int)err);
|
|
}
|
|
|
|
pairing_boot_counter_init();
|
|
|
|
MEMORY_PROFILER_DUMP_HEAP_STAT("Bootup");
|
|
|
|
/* Initialize light drivers (button init deferred until after Matter start) */
|
|
app_driver_handle_t rgb_light_handle = app_driver_rgb_light_init();
|
|
app_driver_handle_t cct_light_handle = app_driver_cct_light_init();
|
|
|
|
/* Create a Matter node and add the mandatory Root Node device type on endpoint 0 */
|
|
node::config_t node_config;
|
|
|
|
// node handle can be used to add/modify other endpoints.
|
|
node_t *node = node::create(&node_config, app_attribute_update_cb, app_identification_cb);
|
|
ABORT_APP_ON_FAILURE(node != nullptr, ESP_LOGE(TAG, "Failed to create Matter node"));
|
|
|
|
#if CONFIG_ENABLE_OTA_REQUESTOR
|
|
err = esp_matter_ota_requestor_init();
|
|
ABORT_APP_ON_FAILURE(err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED,
|
|
ESP_LOGE(TAG, "Failed to init OTA Requestor, err:%d", (int)err));
|
|
#endif
|
|
|
|
MEMORY_PROFILER_DUMP_HEAP_STAT("node created");
|
|
|
|
dimmable_light::config_t rgb_light_config;
|
|
rgb_light_config.on_off.on_off = DEFAULT_POWER;
|
|
rgb_light_config.on_off_lighting.start_up_on_off = nullptr;
|
|
rgb_light_config.level_control.current_level = DEFAULT_BRIGHTNESS;
|
|
rgb_light_config.level_control.on_level = DEFAULT_BRIGHTNESS;
|
|
rgb_light_config.level_control_lighting.start_up_current_level = DEFAULT_BRIGHTNESS;
|
|
|
|
// endpoint handles can be used to add/modify clusters.
|
|
endpoint_t *endpoint = dimmable_light::create(node, &rgb_light_config, ENDPOINT_FLAG_NONE, rgb_light_handle);
|
|
ABORT_APP_ON_FAILURE(endpoint != nullptr, ESP_LOGE(TAG, "Failed to create dimmable light endpoint"));
|
|
|
|
rgb_light_endpoint_id = endpoint::get_id(endpoint);
|
|
ESP_LOGI(TAG, "RGB light created with endpoint_id %d", rgb_light_endpoint_id);
|
|
|
|
color_temperature_light::config_t cct_light_config;
|
|
cct_light_config.on_off.on_off = DEFAULT_POWER;
|
|
cct_light_config.on_off_lighting.start_up_on_off = nullptr;
|
|
cct_light_config.level_control.current_level = DEFAULT_BRIGHTNESS;
|
|
cct_light_config.level_control.on_level = DEFAULT_BRIGHTNESS;
|
|
cct_light_config.level_control_lighting.start_up_current_level = DEFAULT_BRIGHTNESS;
|
|
cct_light_config.color_control.color_mode = (uint8_t)ColorControl::ColorMode::kColorTemperature;
|
|
cct_light_config.color_control.enhanced_color_mode = (uint8_t)ColorControl::ColorMode::kColorTemperature;
|
|
cct_light_config.color_control_color_temperature.start_up_color_temperature_mireds = nullptr;
|
|
|
|
endpoint = color_temperature_light::create(node, &cct_light_config, ENDPOINT_FLAG_NONE, cct_light_handle);
|
|
ABORT_APP_ON_FAILURE(endpoint != nullptr, ESP_LOGE(TAG, "Failed to create color temperature light endpoint"));
|
|
|
|
cct_light_endpoint_id = endpoint::get_id(endpoint);
|
|
ESP_LOGI(TAG, "CCT light created with endpoint_id %d", cct_light_endpoint_id);
|
|
|
|
/* Mark deferred persistence for some attributes that might be changed rapidly */
|
|
attribute_t *current_level_attribute = attribute::get(rgb_light_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id);
|
|
attribute::set_deferred_persistence(current_level_attribute);
|
|
|
|
current_level_attribute = attribute::get(cct_light_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id);
|
|
attribute::set_deferred_persistence(current_level_attribute);
|
|
|
|
attribute_t *color_temp_attribute = attribute::get(cct_light_endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id);
|
|
attribute::set_deferred_persistence(color_temp_attribute);
|
|
|
|
#if CHIP_DEVICE_CONFIG_ENABLE_THREAD && CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION
|
|
// Enable secondary network interface
|
|
secondary_network_interface::config_t secondary_network_interface_config;
|
|
endpoint = endpoint::secondary_network_interface::create(node, &secondary_network_interface_config, ENDPOINT_FLAG_NONE, nullptr);
|
|
ABORT_APP_ON_FAILURE(endpoint != nullptr, ESP_LOGE(TAG, "Failed to create secondary network interface endpoint"));
|
|
#endif
|
|
|
|
#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
|
|
/* Set OpenThread platform config */
|
|
esp_openthread_platform_config_t config = {
|
|
.radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(),
|
|
.host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(),
|
|
.port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(),
|
|
};
|
|
set_openthread_platform_config(&config);
|
|
#endif
|
|
|
|
#ifdef CONFIG_ENABLE_SET_CERT_DECLARATION_API
|
|
auto * dac_provider = get_dac_provider();
|
|
#ifdef CONFIG_SEC_CERT_DAC_PROVIDER
|
|
static_cast<ESP32SecureCertDACProvider *>(dac_provider)->SetCertificationDeclaration(cdSpan);
|
|
#elif defined(CONFIG_FACTORY_PARTITION_DAC_PROVIDER)
|
|
static_cast<ESP32FactoryDataProvider *>(dac_provider)->SetCertificationDeclaration(cdSpan);
|
|
#endif
|
|
#endif // CONFIG_ENABLE_SET_CERT_DECLARATION_API
|
|
|
|
/* 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));
|
|
|
|
#if CONFIG_ENABLE_OTA_REQUESTOR
|
|
chip::DeviceLayer::PlatformMgr().ScheduleWork(ota_requestor_start_work, 0);
|
|
#endif
|
|
|
|
s_matter_started = true;
|
|
log_app_state("matter_started");
|
|
|
|
if (s_factory_reset_pending) {
|
|
s_factory_reset_pending = false;
|
|
chip::DeviceLayer::PlatformMgr().ScheduleWork(factory_reset_work, 0);
|
|
while (true) {
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
|
}
|
|
}
|
|
|
|
PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE));
|
|
|
|
/* Initialize Wi-Fi for ESP-NOW (independent of Matter, fixed channel 1) */
|
|
wifi_init_for_espnow();
|
|
espnow_try_init();
|
|
|
|
log_app_state("after_espnow_init");
|
|
|
|
err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, nullptr);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_event_handler_register(WIFI_EVENT) failed, err:%d", (int)err);
|
|
}
|
|
|
|
err = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &ip_event_handler, nullptr);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_event_handler_register(IP_EVENT) failed, err:%d", (int)err);
|
|
}
|
|
|
|
MEMORY_PROFILER_DUMP_HEAP_STAT("matter started");
|
|
|
|
/* Initialize button after Matter is ready to avoid callback crash */
|
|
app_driver_handle_t button_handle = app_driver_button_init();
|
|
app_reset_button_register(button_handle);
|
|
|
|
/* Starting driver with default values */
|
|
app_driver_light_set_defaults(rgb_light_endpoint_id);
|
|
app_driver_light_set_defaults(cct_light_endpoint_id);
|
|
|
|
#if CONFIG_ENABLE_ENCRYPTED_OTA
|
|
err = esp_matter_ota_requestor_encrypted_init(s_decryption_key, s_decryption_key_len);
|
|
ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to initialized the encrypted OTA, err: %d", err));
|
|
#endif // CONFIG_ENABLE_ENCRYPTED_OTA
|
|
|
|
#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::attribute_register_commands();
|
|
#if CONFIG_OPENTHREAD_CLI
|
|
esp_matter::console::otcli_register_commands();
|
|
#endif
|
|
|
|
static const esp_matter::console::command_t ota_cmd = {
|
|
.name = "ota",
|
|
.description = "Start HTTP OTA. Usage: matter esp ota <http://url>",
|
|
.handler = http_ota_dispatch,
|
|
};
|
|
esp_matter::console::add_commands(&ota_cmd, 1);
|
|
|
|
esp_matter::console::init();
|
|
#endif
|
|
|
|
while (true) {
|
|
MEMORY_PROFILER_DUMP_HEAP_STAT("Idle");
|
|
vTaskDelay(10000 / portTICK_PERIOD_MS);
|
|
}
|
|
}
|