/* * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "freertos/timers.h" #include #include #include "ping/ping_sock.h" #include "lwip/inet.h" #include "lwip/netdb.h" #include "lwip/sockets.h" #include #include #include #include #include #include #include #include #include #include #include #include "led_strip.h" #include #include "esp_radar.h" #include "esp_ping.h" #if CONFIG_IDF_TARGET_ESP32C5 #define WS2812_GPIO 27 #elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 #define WS2812_GPIO 8 #elif CONFIG_IDF_TARGET_ESP32S3 #define WS2812_GPIO 38 #elif CONFIG_IDF_TARGET_ESP32S2 #define WS2812_GPIO 18 #elif CONFIG_IDF_TARGET_ESP32C3 #define WS2812_GPIO 8 #endif #ifdef CONFIG_IDF_TARGET_ESP32C3 #define BUTTON_GPIO GPIO_NUM_8 #else #define BUTTON_GPIO GPIO_NUM_0 #endif #define BUTTON_WIFI_RESET_TIMEOUT 3 #define BUTTON_FACTORY_RESET_TIMEOUT 5 #define AUTO_CALIBRATE_RAPORT_INTERVAL 5 #define RADAR_PING_DATA_INTERVAL 10 #define RADAR_DETECT_BUFF_MAX_SIZE 16 #define RADAR_PARAM_SOMEONE_STATUS "someone_status" #define RADAR_PARAM_SOMEONE_TIMEOUT "someone_timeout" #define RADAR_PARAM_MOVE_STATUS "move_status" #define RADAR_PARAM_MOVE_COUNT "move_count" #define RADAR_PARAM_MOVE_THRESHOLD "move_threshold" #define RADAR_PARAM_FILTER_WINDOW "filter_window" #define RADAR_PARAM_FILTER_COUNT "filter_count" #define RADAR_PARAM_THRESHOLD_CALIBRATE "threshold_calibrate" #define RADAR_PARAM_THRESHOLD_CALIBRATE_TIMEOUT "threshold_calibrate_timeout" static led_strip_handle_t led_strip; typedef struct { bool threshold_calibrate; /**< Self-calibration acquisition, the most suitable threshold, calibration is to ensure that no one is in the room */ uint8_t threshold_calibrate_timeout; /**< Calibration timeout, the unit is second */ float someone_timeout; /**< The unit is second, how long no one moves in the room is marked as no one */ float move_threshold; /**< Use to determine whether someone is moving */ uint8_t filter_window; /**< window without filtering outliers */ uint8_t filter_count; /**< Detect multiple times within the outlier window greater than move_threshold to mark as someone moving */ } radar_detect_config_t; static const char *TAG = "app_main"; static float g_someone_threshold = 0.002; static nvs_handle_t g_nvs_handle = 0; static int32_t g_auto_calibrate_timerleft = 0; static TimerHandle_t g_auto_calibrate_timerhandle = NULL; static esp_rmaker_device_t *radar_device = NULL; static radar_detect_config_t g_detect_config = { .someone_timeout = 3 * 60, .move_threshold = 0.002, .filter_window = 5, .filter_count = 2, .threshold_calibrate_timeout = 60, }; static esp_err_t ping_router_start(uint32_t interval_ms) { static esp_ping_handle_t ping_handle = NULL; esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG(); config.count = 0; config.timeout_ms = 1000; config.data_size = 1; config.interval_ms = interval_ms; /** * @brief Get the Router IP information from the esp-netif */ esp_netif_ip_info_t local_ip; esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &local_ip); ESP_LOGI(TAG, "Ping: got ip:" IPSTR ", gw: " IPSTR, IP2STR(&local_ip.ip), IP2STR(&local_ip.gw)); config.target_addr.u_addr.ip4.addr = ip4_addr_get_u32(&local_ip.gw); config.target_addr.type = ESP_IPADDR_TYPE_V4; // esp_ping_get_target(PING_TARGET_IP_ADDRESS, &config.target_addr, sizeof(ip_addr_t)); esp_ping_callbacks_t cbs = { 0 }; esp_ping_new_session(&config, &cbs, &ping_handle); esp_ping_start(ping_handle); return ESP_OK; } static void radar_cb(void *ctx, const wifi_radar_info_t *info) { bool someone_status = false; bool move_status = false; /** * @brief Filtering outliers is more accurate using detection, * but it takes more time, lower sensitive to the environment * * @note Current algorithm limitations: * 1. Calibration is required to detect whether there are people in the room; * 2. No calibration is required to detect movement, but the sensitivity is higher after calibration */ static uint32_t s_buff_count = 0; static float s_buff_jitter[RADAR_DETECT_BUFF_MAX_SIZE] = {0}; uint32_t buff_max_size = g_detect_config.filter_window; uint32_t buff_outliers_num = g_detect_config.filter_count; s_buff_jitter[s_buff_count % buff_max_size] = info->waveform_jitter; if (++s_buff_count < buff_max_size) { return; } for (int i = 0, move_count = 0; i < buff_max_size; i++) { if (s_buff_jitter[i] > g_detect_config.move_threshold) { move_count++; } if (move_count >= buff_outliers_num) { move_status = true; } } someone_status = (info->waveform_jitter > g_someone_threshold) ? true : false; static uint32_t s_count = 0; if (!s_count++) { ESP_LOGI(TAG, "================ RADAR RECV ================"); ESP_LOGI(TAG, "type,sequence,timestamp,waveform_wander,someone_threshold,someone_status,waveform_jitter,move_threshold,move_status\n"); } ESP_LOGI("RADAR_DADA", "RADAR_DADA,%d,%u,%.6f,%.6f,%d,%.6f,%.6f,%d", s_count, esp_log_timestamp(), info->waveform_wander, g_someone_threshold, someone_status, info->waveform_jitter, g_detect_config.move_threshold, move_status); /* Calibrate the environment, LED will flash yellow */ if (g_detect_config.threshold_calibrate) { static bool led_status = false; if (led_status) { led_strip_set_pixel(led_strip, 0, 0, 0); } else { led_strip_set_pixel(led_strip, 0, 255, 255, 0); } led_strip_refresh(led_strip); led_status = !led_status; return; } /** * @brief Use LED and LOG to display the detection results * 1. Someone moves in the room, LED will flash green * 2. No one moves in the room, LED will flash white * 3. No one in the room, LED will turn off */ static uint32_t s_last_move_time = 0; static bool s_last_move_status = false; if (esp_log_timestamp() - s_last_move_time < g_detect_config.someone_timeout * 1000) { someone_status = true; } else { someone_status = false; } if (move_status) { if (move_status != s_last_move_status) { led_strip_set_pixel(led_strip, 0, 0, 255, 0); ESP_LOGI(TAG, "someone moves in the room"); } s_last_move_time = esp_log_timestamp(); } else if (move_status != s_last_move_status) { ESP_LOGI(TAG, "No one moves in the room"); } else if (esp_log_timestamp() - s_last_move_time > 3 * 1000) { if (someone_status) { led_strip_set_pixel(led_strip, 0, 255, 255, 255); } else { led_strip_set_pixel(led_strip, 0, 0, 0, 0); } } led_strip_refresh(led_strip); s_last_move_status = move_status; /** * @brief Report the room status to the cloud every 10 seconds or when the room status changes */ if (!esp_rmaker_time_check()) { return; } static bool s_report_someone_status = false; static bool s_report_move_status = false; static uint32_t s_report_move_count = 0; static uint32_t s_report_move_timestamp = 0; s_report_move_count = move_status ? s_report_move_count + 1 : s_report_move_count; if (s_report_someone_status != someone_status && !someone_status) { esp_rmaker_param_t *someone_status_param = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_SOMEONE_STATUS); esp_rmaker_param_update_and_report(someone_status_param, esp_rmaker_bool(someone_status)); s_report_someone_status = someone_status; } if (move_status != s_report_move_status /* Reduce the number of times to report data and reduce server pressure */ || (move_status && esp_log_timestamp() - s_report_move_timestamp > 10 * 1000)) { esp_rmaker_param_t *someone_status_param = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_SOMEONE_STATUS); esp_rmaker_param_update(someone_status_param, esp_rmaker_bool(someone_status)); esp_rmaker_param_t *move_status_param = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_MOVE_STATUS); esp_rmaker_param_update(move_status_param, esp_rmaker_bool(move_status | (s_report_move_count > 0))); esp_rmaker_param_t *move_count_param = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_MOVE_COUNT); esp_rmaker_param_update_and_report(move_count_param, esp_rmaker_int(s_report_move_count)); if (!s_report_move_status || !s_report_move_count) { s_report_move_status = move_status; } s_report_move_count = 0; s_report_move_timestamp = esp_log_timestamp(); } } static esp_err_t radar_start() { esp_radar_config_t radar_config = ESP_RADAR_CONFIG_DEFAULT(); #if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 // radar_config.csi_config.acquire_csi_lltf = true; // radar_config.csi_config.htltf_en = true; #else // radar_config.csi_config.lltf_en = true; // radar_config.csi_config.htltf_en = true; #endif radar_config.dec_config.wifi_radar_cb = radar_cb; wifi_ap_record_t ap_info = {0x0}; esp_wifi_sta_get_ap_info(&ap_info); memcpy(radar_config.csi_config.filter_mac, ap_info.bssid, sizeof(ap_info.bssid)); ESP_ERROR_CHECK(esp_radar_csi_init(&radar_config.csi_config)); ESP_ERROR_CHECK(esp_radar_dec_init(&radar_config.dec_config)); esp_radar_start(); return ESP_OK; } static void auto_calibrate_timercb(TimerHandle_t timer) { g_auto_calibrate_timerleft -= AUTO_CALIBRATE_RAPORT_INTERVAL; if (g_auto_calibrate_timerleft < AUTO_CALIBRATE_RAPORT_INTERVAL) { g_auto_calibrate_timerleft = g_detect_config.threshold_calibrate_timeout; g_detect_config.threshold_calibrate = false; xTimerStop(g_auto_calibrate_timerhandle, portMAX_DELAY); esp_radar_train_stop(&g_someone_threshold, &g_detect_config.move_threshold); esp_rmaker_param_t *move_threshold_param = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_MOVE_THRESHOLD); esp_rmaker_param_update(move_threshold_param, esp_rmaker_float(g_detect_config.move_threshold)); nvs_set_blob(g_nvs_handle, "detect_config", &g_detect_config, sizeof(radar_detect_config_t)); nvs_commit(g_nvs_handle); } esp_rmaker_param_t *param_threshold_calibrate = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_THRESHOLD_CALIBRATE); esp_rmaker_param_update(param_threshold_calibrate, esp_rmaker_bool(g_detect_config.threshold_calibrate)); esp_rmaker_param_t *param_threshold_calibrate_timeout = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_THRESHOLD_CALIBRATE_TIMEOUT); esp_rmaker_param_update_and_report(param_threshold_calibrate_timeout, esp_rmaker_int(g_auto_calibrate_timerleft)); } /* 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) { const char *param_name = esp_rmaker_param_get_name(param); if (strcmp(param_name, RADAR_PARAM_SOMEONE_TIMEOUT) == 0) { g_detect_config.someone_timeout = val.val.i; } else if (strcmp(param_name, RADAR_PARAM_MOVE_THRESHOLD) == 0) { g_detect_config.move_threshold = val.val.f; } else if (strcmp(param_name, RADAR_PARAM_FILTER_WINDOW) == 0) { g_detect_config.filter_window = val.val.i; } else if (strcmp(param_name, RADAR_PARAM_FILTER_COUNT) == 0) { g_detect_config.filter_count = val.val.i; } else if (strcmp(param_name, RADAR_PARAM_THRESHOLD_CALIBRATE) == 0) { g_detect_config.threshold_calibrate = val.val.b; if (!g_auto_calibrate_timerhandle) { g_auto_calibrate_timerhandle = xTimerCreate("auto_calibrate", pdMS_TO_TICKS(AUTO_CALIBRATE_RAPORT_INTERVAL * 1000), true, NULL, auto_calibrate_timercb); } if (g_detect_config.threshold_calibrate) { g_auto_calibrate_timerleft = g_detect_config.threshold_calibrate_timeout; xTimerStart(g_auto_calibrate_timerhandle, portMAX_DELAY); esp_radar_train_start(); } else { g_auto_calibrate_timerleft = 0; auto_calibrate_timercb(NULL); } } else if (strcmp(param_name, RADAR_PARAM_THRESHOLD_CALIBRATE_TIMEOUT) == 0) { g_detect_config.threshold_calibrate_timeout = val.val.i; } else { ESP_LOGE(TAG, "Unhandled parameter '%s'", param_name); /* Silently ignoring invalid params */ return ESP_OK; } esp_rmaker_param_update_and_report(param, val); nvs_set_blob(g_nvs_handle, "detect_config", &g_detect_config, sizeof(radar_detect_config_t)); nvs_commit(g_nvs_handle); return ESP_OK; } esp_err_t ws2812_led_init(void) { led_strip_config_t strip_config = { .strip_gpio_num = WS2812_GPIO, .max_leds = 1, }; led_strip_rmt_config_t rmt_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption .resolution_hz = 10 * 1000 * 1000, // RMT counter clock frequency: 10MHz .flags = { .with_dma = false, // DMA feature is available on chips like ESP32-S3/P4 } }; ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); /* Set all LED off to clear all pixels */ led_strip_clear(led_strip); return ESP_OK; } void app_main() { /* Initialize Application specific hardware drivers and set initial state */ ws2812_led_init(); button_handle_t btn_handle = iot_button_create(BUTTON_GPIO, BUTTON_ACTIVE_LOW); app_reset_button_register(btn_handle, BUTTON_WIFI_RESET_TIMEOUT, BUTTON_FACTORY_RESET_TIMEOUT); /* 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); nvs_open("wifi_radar", NVS_READWRITE, &g_nvs_handle); size_t detect_config_size = sizeof(radar_detect_config_t); nvs_get_blob(g_nvs_handle, "detect_config", &g_detect_config, &detect_config_size); /* Reduced output log */ // esp_log_level_set("esp_radar", ESP_LOG_WARN); esp_log_level_set("RADAR_DADA", ESP_LOG_WARN); /* Initialize Wi-Fi. Note that, this should be called before esp_rmaker_node_init() */ app_wifi_init(); /* Initialize the ESP RainMaker Agent */ esp_rmaker_config_t rainmaker_cfg = { .enable_time_sync = true, }; esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "ESP Wi-Fi CSI Device", "Wi-Fi Radar"); if (!node) { ESP_LOGE(TAG, "Could not initialise node. Aborting!!!"); vTaskDelay(pdMS_TO_TICKS(5000)); abort(); } /* Create a device and add the relevant parameters to it */ radar_device = esp_rmaker_device_create("Sensor Light", ESP_RMAKER_DEVICE_LIGHTBULB, NULL); ESP_ERROR_CHECK(esp_rmaker_device_add_cb(radar_device, write_cb, NULL)); typedef struct { const char *param_name; uint8_t properties; const char *ui_type; esp_rmaker_param_val_t val; esp_rmaker_param_val_t min; esp_rmaker_param_val_t max; esp_rmaker_param_val_t step; } radar_param_t; esp_rmaker_param_val_t invalid_val = {.type = RMAKER_VAL_TYPE_INVALID}; radar_param_t radar_param_list[] = { {RADAR_PARAM_THRESHOLD_CALIBRATE, PROP_FLAG_READ | PROP_FLAG_WRITE, ESP_RMAKER_UI_TOGGLE, esp_rmaker_bool(g_detect_config.threshold_calibrate), invalid_val, invalid_val, invalid_val}, {RADAR_PARAM_THRESHOLD_CALIBRATE_TIMEOUT, PROP_FLAG_READ | PROP_FLAG_WRITE, ESP_RMAKER_UI_TEXT, esp_rmaker_int(g_detect_config.threshold_calibrate_timeout), esp_rmaker_int(10), esp_rmaker_int(3600), esp_rmaker_int(10)}, {RADAR_PARAM_MOVE_THRESHOLD, PROP_FLAG_READ | PROP_FLAG_WRITE, ESP_RMAKER_UI_TEXT, esp_rmaker_float(g_detect_config.move_threshold), esp_rmaker_float(0.01), esp_rmaker_float(0.1), esp_rmaker_float(0.01)}, {RADAR_PARAM_MOVE_STATUS, PROP_FLAG_READ, ESP_RMAKER_UI_TEXT, esp_rmaker_bool(false), invalid_val, invalid_val, invalid_val}, {RADAR_PARAM_MOVE_COUNT, PROP_FLAG_READ | PROP_FLAG_TIME_SERIES, ESP_RMAKER_UI_TEXT, esp_rmaker_int(0), esp_rmaker_int(0), esp_rmaker_int(100), esp_rmaker_int(1)}, {RADAR_PARAM_SOMEONE_STATUS, PROP_FLAG_READ, ESP_RMAKER_UI_TEXT, esp_rmaker_bool(false), invalid_val, invalid_val, invalid_val}, {RADAR_PARAM_SOMEONE_TIMEOUT, PROP_FLAG_READ | PROP_FLAG_WRITE, ESP_RMAKER_UI_TEXT, esp_rmaker_int(g_detect_config.someone_timeout), esp_rmaker_int(10), esp_rmaker_int(3600), esp_rmaker_int(10)}, {RADAR_PARAM_FILTER_WINDOW, PROP_FLAG_READ | PROP_FLAG_WRITE, ESP_RMAKER_UI_TEXT, esp_rmaker_int(g_detect_config.filter_window), esp_rmaker_int(1), esp_rmaker_int(16), esp_rmaker_int(1)}, {RADAR_PARAM_FILTER_COUNT, PROP_FLAG_READ | PROP_FLAG_WRITE, ESP_RMAKER_UI_TEXT, esp_rmaker_int(g_detect_config.filter_count), esp_rmaker_int(1), esp_rmaker_int(16), esp_rmaker_int(1)}, }; for (int i = 0; i < sizeof(radar_param_list) / sizeof(radar_param_list[0]); ++i) { char radar_param_type[64] = "esp.param."; strcat(radar_param_type, radar_param_list[i].param_name); esp_rmaker_param_t *param = esp_rmaker_param_create(radar_param_list[i].param_name, radar_param_type, radar_param_list[i].val, radar_param_list[i].properties); if (radar_param_list[i].ui_type) { esp_rmaker_param_add_ui_type(param, radar_param_list[i].ui_type); } if (radar_param_list[i].min.type != RMAKER_VAL_TYPE_INVALID) { esp_rmaker_param_add_bounds(param, radar_param_list[i].min, radar_param_list[i].max, radar_param_list[i].step); } ESP_ERROR_CHECK(esp_rmaker_device_add_param(radar_device, param)); } esp_rmaker_param_t *move_count_param = esp_rmaker_device_get_param_by_name(radar_device, RADAR_PARAM_MOVE_COUNT); esp_rmaker_device_assign_primary_param(radar_device, move_count_param); ESP_ERROR_CHECK(esp_rmaker_node_add_device(node, radar_device)); /* Enable OTA */ ESP_ERROR_CHECK(esp_rmaker_ota_enable_default()); /* 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(); /* Start the ESP RainMaker Agent */ esp_rmaker_start(); /* Start the Wi-Fi./ * If the node is provisioned, it will start connection attempts, * else, it will start Wi-Fi provisioning. The function will return * after a connection has been successfully established */ err = app_wifi_start(POP_TYPE_NONE); if (err != ESP_OK) { ESP_LOGE(TAG, "Could not start Wifi. Aborting!!!"); vTaskDelay(pdMS_TO_TICKS(5000)); abort(); } /** * @brief Enable Wi-Fi Radar Detection */ radar_start(); ping_router_start(RADAR_PING_DATA_INTERVAL); }