project_description.json not being created

philicibine
Posts: 2
Joined: Sun Jul 06, 2025 3:09 pm

project_description.json not being created

Postby philicibine » Sun Jul 06, 2025 3:13 pm

I am trying to compile a project on the idf. When i build it, it says the following

Code: Select all

Executing action: all (aliases: build)
Running cmake in directory /home/phil/esp/yale_lock_checker/build
Executing "cmake -G Ninja -DPYTHON_DEPS_CHECKED=1 -DPYTHON=/home/phil/.espressif/python_env/idf5.4_py3.12_env/bin/python -DESP_PLATFORM=1 -DCCACHE_ENABLE=0 /home/phil/esp/yale_lock_checker"...
-- The CXX compiler identification is GNU 13.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Git: /usr/bin/git (found version "2.43.0") 
-- Building for target: esp32c3
-- Configuring done (0.6s)
-- Generating done (0.0s)
CMake Warning:
  Manually-specified variables were not used by the project:

    CCACHE_ENABLE
    ESP_PLATFORM


-- Build files have been written to: /home/phil/esp/yale_lock_checker/build
Cannot load /home/phil/esp/yale_lock_checker/build/project_description.json: [Errno 2] No such file or directory: '/home/phil/esp/yale_lock_checker/build/project_description.json'
Can anyone help me debug this? I have tried using gemini to help debug but it has me going round in circles. No real error messages as to why it is happening.

philicibine
Posts: 2
Joined: Sun Jul 06, 2025 3:09 pm

Re: project_description.json not being created

Postby philicibine » Sun Jul 06, 2025 3:24 pm

Here are the project files


CMakelists.txt (project root)

Code: Select all

# CMakeLists.txt for the root of your IDF project

# The IDF version that this project is compatible with.
# Updated minimum CMake version for broader compatibility with IDF.
cmake_minimum_required(VERSION 3.21)

# Set the project name
# Combined project name, version, and language declaration.
project(yale_lock_checker VERSION 1.0.0 LANGUAGES CXX)

# Explicitly set the IDF_TARGET CMake variable
set(IDF_TARGET esp32c3 CACHE STRING "Set the target chip for the project")

# Include the ESP-IDF build system
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# Define the components your project uses
# 'main' is your application code component
# 'ArduinoJson' if you put it in a 'components' folder
# IDF will automatically find 'main' and components in 'components/'
# No need to explicitly list them in COMPONENTS if using add_subdirectory for main
# and EXTRA_COMPONENT_DIRS for custom components.

# Add any extra component directories if you have custom components
# This is crucial for finding ArduinoJson in your 'components' folder
set(EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/components")

# Add a message to confirm target during configuration
message(STATUS "Building for target: ${IDF_TARGET}")

# Add the main component as a subdirectory
add_subdirectory(main)

sdkconfig.defaults

Code: Select all

# sdkconfig.defaults for Yale Lock Checker IDF project

# --- General ESP-IDF Configuration ---
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
CONFIG_ESP_CONSOLE_UART_NUM=0
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200

# --- Wi-Fi Configuration ---
CONFIG_ESP_WIFI_STA_MODE=y
CONFIG_ESP_WIFI_AP_MODE=y
# CONFIG_ESP_WIFI_SSID="ESP32_SSID" # Default SSID if not set, handled by NVS now
# CONFIG_ESP_WIFI_PASSWORD="password" # Default password if not set, handled by NVS now
CONFIG_ESP_WIFI_AUTH_WPA2_PSK=y
CONFIG_ESP_WIFI_TX_POWER_20_DBM=y # Max TX power (approx 19.5 dBm)
CONFIG_ESP_WIFI_NVS_ENABLED=y # Enable NVS for Wi-Fi credentials storage
CONFIG_ESP_WIFI_SOFTAP_BEACON_INTERVAL=100 # Beacon interval for AP mode (ms)
CONFIG_ESP_WIFI_SOFTAP_MAX_CONN_QUANTITY=4 # Max connections for AP

# --- Networking ---
CONFIG_LWIP_IPV4=y
CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # Enable mDNS for AP mode hostname resolution
CONFIG_LWIP_DHCP_SERVER=y # Enable DHCP server for AP mode
CONFIG_LWIP_DNS_SERVER=y # Enable DNS server for AP mode

# --- HTTP Client (for Seam API) ---
CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
CONFIG_ESP_HTTP_CLIENT_HTTP_REQUEST_BUFFER_SIZE=4096 # Increased buffer size
CONFIG_ESP_HTTP_CLIENT_HTTP_RECEIVE_BUFFER_SIZE=4096 # Increased buffer size
CONFIG_ESP_HTTP_CLIENT_ENABLE_SSL_ALPN=y # Needed for some TLS handshakes
CONFIG_ESP_TLS_INSECURE=y # WARNING: For development only, disable in production!

# --- NVS Flash (for saving Wi-Fi credentials) ---
CONFIG_NVS_FLASH_ENABLED=y

# --- FreeRTOS Configuration ---
CONFIG_FREERTOS_HZ=1000 # 1000 ticks per second for finer delays
CONFIG_FREERTOS_VTASKLIST_INCLUDE_CORE_ID=y # Useful for debugging multi-core tasks

# --- mDNS (for AP hostname) ---
CONFIG_MDNS_ENABLED=y
CONFIG_MDNS_HOSTNAME="yale-lock-setup" # Hostname for your AP

# --- SNTP (Time Synchronization for HTTPS) ---
CONFIG_LWIP_SNTP_ENABLED=y
CONFIG_LWIP_SNTP_SERVER_DNS=y
CONFIG_LWIP_SNTP_MAX_SERVERS=1
CONFIG_LWIP_SNTP_SERVER_ADDRESS="pool.ntp.org"

# --- Component specific settings ---
# Ensure the 'driver' component is included for GPIO functions
# Ensure 'esp_http_server' is included for the web server
# Ensure 'mdns' is included for mDNS
# Ensure 'esp_sntp' is included for SNTP

# --- ArduinoJson specific settings (if compiled as IDF component) ---
# CONFIG_ARDUINOJSON_USE_LONG_LONG=y # Example setting if you need long long support

main/main.cpp

Code: Select all

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "esp_netif.h"
#include "esp_http_client.h"
#include "esp_tls.h"
#include "driver/gpio.h"
#include "mdns.h" // For mDNS if you want to access the AP by name
#include "esp_sntp.h" // For time synchronization (important for HTTPS)

// ArduinoJson library (can be used directly in IDF C++ project)
#include "ArduinoJson.h"

// --- Wi-Fi Configuration ---
#define STORAGE_NAMESPACE "wifi_creds"
#define MAX_SSID_LEN 32
#define MAX_PASS_LEN 64
static char saved_ssid[MAX_SSID_LEN + 1] = {0};
static char saved_password[MAX_PASS_LEN + 1] = {0};

// --- Seam API Configuration ---
// !! IMPORTANT: Replace these with your actual Seam API Key !!
static const char *SEAM_API_KEY = "YOUR_SEAM_API_KEY";
static const char *SEAM_API_HOST = "connect.getseam.com";
static const char *LOCKS_ENDPOINT = "/locks/list";

// --- Lock Specifics ---
// !! IMPORTANT: Replace this with your actual Yale Conexis L2 Lock ID from Seam !!
static const char *TARGET_LOCK_ID = "YOUR_LOCK_ID_FROM_SEAM"; // Example: "124e9043-22c3-4f5e-b42d-b537d73674c0"

// --- LED Pin Configuration ---
// Using GPIOs 4 (Red), 5 (Green), and 7 (Yellow)
#define RED_LED_PIN   GPIO_NUM_4
#define GREEN_LED_PIN GPIO_NUM_5
#define YELLOW_LED_PIN GPIO_NUM_7

// --- Polling Interval ---
#define POLLING_INTERVAL_MS 10000 // Poll every 10 seconds in milliseconds

// --- Web Server and DNS Server for AP Mode ---
#include "esp_http_server.h"
#include "lwip/dns.h"
static httpd_handle_t server = NULL;
static const char *TAG = "YALE_LOCK_APP";

// --- AP Mode Configuration ---
#define AP_SSID "YaleLockSetup"
#define AP_IP_ADDR_OCTET1 192
#define AP_IP_ADDR_OCTET2 168
#define AP_IP_ADDR_OCTET3 4
#define AP_IP_ADDR_OCTET4 1

// --- State variable to track if we are in AP mode ---
static bool in_ap_mode = false;
static esp_netif_t *ap_netif = NULL;
static esp_netif_t *sta_netif = NULL;
static wifi_mode_t s_wifi_mode = WIFI_MODE_NULL; // To store current Wi-Fi mode

// --- Function Prototypes ---
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
static esp_err_t http_server_start(void);
static void http_server_stop(void);
static esp_err_t root_get_handler(httpd_req_t *req);
static esp_err_t connect_post_handler(httpd_req_t *req);
static void set_led_state(bool locked, bool door_open, bool api_error);
static bool get_lock_and_door_status(bool *is_locked, bool *is_door_open);
static void wifi_init_sta(void);
static void wifi_init_ap(void);
static void polling_task(void *pvParameters);
static void obtain_time(void);
static void initialize_sntp(void);

// --- Function to set LED states based on simplified lock and door status ---
static void set_led_state(bool locked, bool door_open, bool api_error) {
    gpio_set_level(GREEN_LED_PIN, 0);
    gpio_set_level(RED_LED_PIN, 0);
    gpio_set_level(YELLOW_LED_PIN, 0);

    if (api_error) {
        ESP_LOGI(TAG, "API Error: All LEDs off (cannot determine status).");
    } else if (door_open) {
        ESP_LOGI(TAG, "Status: DOOR OPEN. Setting Yellow LED ON.");
        gpio_set_level(YELLOW_LED_PIN, 1);
    } else if (!locked) {
        ESP_LOGI(TAG, "Status: UNLOCKED & CLOSED. Setting Red LED ON.");
        gpio_set_level(RED_LED_PIN, 1);
    } else { // locked && !door_open
        ESP_LOGI(TAG, "Status: LOCKED & CLOSED. Setting Green LED ON.");
        gpio_set_level(GREEN_LED_PIN, 1);
    }
}

// --- Function to get lock and door status from Seam API ---
static bool get_lock_and_door_status(bool *is_locked, bool *is_door_open) {
    esp_http_client_config_t config = {
        .host = SEAM_API_HOST,
        .path = LOCKS_ENDPOINT,
        .transport_type = HTTP_TRANSPORT_OVER_SSL,
        .skip_cert_verify = true, // WARNING: For production, set this to false and provide CA certs!
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);
    if (!client) {
        ESP_LOGE(TAG, "Failed to initialize HTTP client");
        return false;
    }

    esp_http_client_set_method(client, HTTP_METHOD_POST);
    esp_http_client_set_header(client, "Authorization", SEAM_API_KEY);
    esp_http_client_set_header(client, "Content-Type", "application/json");

    // Empty string for the body as parameters are optional or in JSON payload
    esp_err_t err = esp_http_client_open(client, 0); // 0 for empty body
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
        esp_http_client_cleanup(client);
        return false;
    }

    int content_length = esp_http_client_fetch_headers(client);
    if (content_length < 0) {
        ESP_LOGE(TAG, "HTTP client fetch headers failed");
        esp_http_client_cleanup(client);
        return false;
    }

    char *buffer = (char *)malloc(content_length + 1);
    if (!buffer) {
        ESP_LOGE(TAG, "Failed to allocate buffer for HTTP response");
        esp_http_client_cleanup(client);
        return false;
    }
    int read_len = esp_http_client_read(client, buffer, content_length);
    buffer[read_len] = '\0'; // Null-terminate the string

    ESP_LOGI(TAG, "HTTP Response code: %d", esp_http_client_get_status_code(client));
    ESP_LOGI(TAG, "Response Payload:\n%s", buffer);

    StaticJsonDocument<2048> doc; // Adjust size as needed
    DeserializationError json_error = deserializeJson(doc, buffer);
    free(buffer); // Free buffer after deserialization

    if (json_error) {
        ESP_LOGE(TAG, "deserializeJson() failed: %s", json_error.f_str());
        esp_http_client_cleanup(client);
        return false;
    }

    JsonObject root = doc.as<JsonObject>();
    JsonArray locks = root["locks"].as<JsonArray>();

    if (!locks.isNull()) {
        for (JsonObject lock : locks) {
            const char* device_id = lock["device_id"];
            if (device_id && strcmp(device_id, TARGET_LOCK_ID) == 0) {
                if (lock.containsKey("properties")) {
                    JsonObject properties = lock["properties"];
                    if (properties.containsKey("locked")) {
                        *is_locked = properties["locked"].as<bool>();
                        ESP_LOGI(TAG, "Target lock (ID: %s) status: %s", TARGET_LOCK_ID, *is_locked ? "LOCKED" : "UNLOCKED");
                    } else {
                        ESP_LOGW(TAG, "Warning: 'properties.locked' not found for target lock. Defaulting to unlocked.");
                        *is_locked = false;
                    }

                    if (properties.containsKey("door_open")) {
                        *is_door_open = properties["door_open"].as<bool>();
                        ESP_LOGI(TAG, "Target door (ID: %s) status: %s", TARGET_LOCK_ID, *is_door_open ? "OPEN" : "CLOSED");
                    } else {
                        ESP_LOGW(TAG, "Warning: 'properties.door_open' not found for target lock (DoorSense might not be active/available). Defaulting to closed.");
                        *is_door_open = false;
                    }
                    esp_http_client_cleanup(client);
                    return true;
                } else {
                    ESP_LOGE(TAG, "Error: 'properties' object not found in target lock object.");
                    esp_http_client_cleanup(client);
                    return false;
                }
            }
        }
        ESP_LOGE(TAG, "Error: Target lock ID not found in the 'locks' array.");
        esp_http_client_cleanup(client);
        return false;
    } else {
        ESP_LOGE(TAG, "Error: 'locks' array not found or is invalid in the API response.");
        esp_http_client_cleanup(client);
        return false;
    }
}

// --- Wi-Fi Event Handler ---
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (!in_ap_mode) { // Only retry if not intentionally in AP mode
            ESP_LOGI(TAG, "Wi-Fi disconnected. Retrying connection...");
            esp_wifi_connect();
            set_led_state(false, false, true); // Indicate API error (all LEDs off)
        }
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
        in_ap_mode = false; // Successfully connected to STA, exit AP mode state
        if (server) {
            http_server_stop(); // Stop AP web server if running
        }
        ESP_LOGI(TAG, "Wi-Fi connected in STA mode. Starting polling task.");
        // Start the polling task after successful connection
        xTaskCreate(polling_task, "polling_task", 4096, NULL, 5, NULL);
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "Station " MACSTR " joined, AID=%d", MAC2STR(event->mac), event->aid);
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "Station " MACSTR " left, AID=%d", MAC2STR(event->mac), event->aid);
    }
}

// --- Wi-Fi Station Initialization ---
static void wifi_init_sta(void) {
    sta_netif = esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "", // Will be filled from NVS
            .password = "", // Will be filled from NVS
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
            .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, // Keep for compatibility, will default to WPA2 if not WPA3
        },
    };
    strncpy((char*)wifi_config.sta.ssid, saved_ssid, sizeof(wifi_config.sta.ssid) - 1);
    strncpy((char*)wifi_config.sta.password, saved_password, sizeof(wifi_config.sta.password) - 1);

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); // Disable power save for continuous operation
    ESP_ERROR_CHECK(esp_wifi_set_max_tx_power(84)); // Set max TX power (corresponds to 19.5 dBm)

    ESP_LOGI(TAG, "wifi_init_sta finished.");
}

// --- Wi-Fi AP Initialization ---
static void wifi_init_ap(void) {
    ap_netif = esp_netif_create_default_wifi_ap();

    wifi_config_t wifi_config = {
        .ap = {
            .ssid = AP_SSID,
            .ssid_len = strlen(AP_SSID),
            .channel = 1, // Default channel
            .password = "", // No password for AP
            .max_connection = 4, // Max 4 clients
            .authmode = WIFI_AUTH_OPEN,
            .pmf_cfg = {},
        },
    };

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    ESP_LOGI(TAG, "wifi_init_ap finished. SSID:%s IP:" IPSTR, AP_SSID, IP2STR(&ap_netif->ip_info.ip));

    // For captive portal, mDNS is needed for some devices, DNS server for others
    ESP_ERROR_ERROR_CHECK(mdns_init());
    ESP_ERROR_CHECK(mdns_hostname_set(AP_SSID));
    ESP_ERROR_CHECK(mdns_instance_name_set(AP_SSID));

    // Set DNS server to AP IP (for captive portal)
    ip_addr_t dnsserver_ip;
    IP_ADDR4(&dnsserver_ip, AP_IP_ADDR_OCTET1, AP_IP_ADDR_OCTET2, AP_IP_ADDR_OCTET3, AP_IP_ADDR_OCTET4);
    dns_setserver(0, &dnsserver_ip);
}

// --- HTTP Server Handlers ---
static const httpd_uri_t root = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = root_get_handler,
    .user_ctx  = NULL
};

static const httpd_uri_t connect_uri = {
    .uri       = "/connect",
    .method    = HTTP_POST,
    .handler   = connect_post_handler,
    .user_ctx  = NULL
};

static esp_err_t root_get_handler(httpd_req_t *req) {
    ESP_LOGI(TAG, "Handling root request");
    
    // Scan for networks
    uint16_t number = 20; // Max 20 APs
    wifi_ap_record_t ap_info[20];
    uint16_t ap_count = 0;
    memset(ap_info, 0, sizeof(ap_info));
    
    esp_wifi_scan_start(NULL, true); // Blocking scan
    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
    ESP_ERROR_CHECK(esp_wifi_scan_stop());
    ap_count = number;
    ESP_LOGI(TAG, "Total APs scanned = %u", ap_count);

    // Build HTML response
    char *html_response;
    size_t html_len = 1500 + ap_count * 100; // Estimate size
    html_response = (char*)malloc(html_len);
    if (!html_response) {
        ESP_LOGE(TAG, "Failed to allocate HTML buffer");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    snprintf(html_response, html_len, "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
             "<title>Wi-Fi Setup</title>"
             "<style>body{font-family:sans-serif; text-align:center; margin-top:50px;} form{margin:20px auto; padding:20px; border:1px solid #ccc; border-radius:8px; max-width:400px;} input[type=text],input[type=password],select{width:90%; padding:10px; margin:8px 0; display:inline-block; border:1px solid #ccc; border-radius:4px; box-sizing:border-box;} input[type=submit]{background-color:#4CAF50; color:white; padding:12px 20px; border:none; border-radius:4px; cursor:pointer; width:100%;} input[type=submit]:hover{background-color:#45a049;} .scan-btn{background-color:#008CBA; color:white; padding:12px 20px; border:none; border-radius:4px; cursor:pointer; width:100%;} .scan-btn:hover{background-color:#007B9E;}</style>"
             "</head><body>"
             "<h1>Yale Lock Wi-Fi Setup</h1>"
             "<form action=\"/connect\" method=\"post\">"
             "<label for=\"ssid\">Select Network:</label><br>"
             "<select id=\"ssid\" name=\"ssid\">");

    if (ap_count == 0) {
        strcat(html_response, "<option value=\"\">No networks found</option>");
    } else {
        for (int i = 0; i < ap_count; i++) {
            char option_buffer[256];
            snprintf(option_buffer, sizeof(option_buffer), "<option value=\"%s\">%s (%d dBm)%s</option>",
                     (char*)ap_info[i].ssid, (char*)ap_info[i].ssid, ap_info[i].rssi,
                     (ap_info[i].authmode == WIFI_AUTH_OPEN) ? "" : "*");
            strcat(html_response, option_buffer);
        }
    }
    strcat(html_response, "</select><br><br>");
    
    strcat(html_response, "<label for=\"manual_ssid\">Or Enter Manually:</label><br>");
    strcat(html_response, "<input type=\"text\" id=\"manual_ssid\" name=\"manual_ssid\" placeholder=\"Enter SSID if not listed\"><br><br>");
    strcat(html_response, "<label for=\"pass\">Password:</label><br>");
    strcat(html_response, "<input type=\"password\" id=\"pass\" name=\"password\" placeholder=\"Enter password\"><br><br>");
    strcat(html_response, "<input type=\"submit\" value=\"Connect\">");
    strcat(html_response, "</form>");
    strcat(html_response, "<button class=\"scan-btn\" onclick=\"window.location.reload();\">Rescan Networks</button>");
    strcat(html_response, "</body></html>");

    httpd_resp_send(req, html_response, HTTPD_RESP_USE_STRLEN);
    free(html_response);
    return ESP_OK;
}

static esp_err_t connect_post_handler(httpd_req_t *req) {
    char content[req->content_len + 1];
    int ret = httpd_req_recv(req, content, req->content_len);
    if (ret <= 0) {  // 0 for EOF, < 0 for error
        if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
            httpd_resp_send_408(req);
        }
        return ESP_FAIL;
    }
    content[req->content_len] = '\0'; // Null-terminate the received content

    char ssid_buf[MAX_SSID_LEN + 1] = {0};
    char pass_buf[MAX_PASS_LEN + 1] = {0};

    // Basic URL-encoded form parsing
    // Find SSID
    char *ssid_start = strstr(content, "ssid=");
    if (ssid_start) {
        ssid_start += 5; // Move past "ssid="
        char *ssid_end = strchr(ssid_start, '&');
        size_t len = (ssid_end ? (size_t)(ssid_end - ssid_start) : strlen(ssid_start));
        strncpy(ssid_buf, ssid_start, MIN(len, MAX_SSID_LEN));
        ssid_buf[MIN(len, MAX_SSID_LEN)] = '\0';
    }

    // Check for manual_ssid override
    char *manual_ssid_start = strstr(content, "manual_ssid=");
    if (manual_ssid_start) {
        manual_ssid_start += 12; // Move past "manual_ssid="
        char *manual_ssid_end = strchr(manual_ssid_start, '&');
        size_t len = (manual_ssid_end ? (size_t)(manual_ssid_end - manual_ssid_start) : strlen(manual_ssid_start));
        if (len > 0) { // If manual SSID is not empty
            strncpy(ssid_buf, manual_ssid_start, MIN(len, MAX_SSID_LEN));
            ssid_buf[MIN(len, MAX_SSID_LEN)] = '\0';
        }
    }

    // Find Password
    char *pass_start = strstr(content, "password=");
    if (pass_start) {
        pass_start += 9; // Move past "password="
        strncpy(pass_buf, pass_start, MAX_PASS_LEN); // Password is usually last param
        pass_buf[MAX_PASS_LEN] = '\0';
    }

    // URL decode (simple for spaces, more robust needed for full URL encoding)
    for (int i = 0; ssid_buf[i] != '\0'; i++) {
        if (ssid_buf[i] == '+') ssid_buf[i] = ' ';
    }
    for (int i = 0; pass_buf[i] != '\0'; i++) {
        if (pass_buf[i] == '+') pass_buf[i] = ' ';
    }

    ESP_LOGI(TAG, "Received SSID: %s, Password: %s", ssid_buf, pass_buf);

    // Save credentials to NVS Flash
    nvs_handle_t nvs_handle;
    esp_err_t err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &nvs_handle);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err));
    } else {
        err = nvs_set_str(nvs_handle, "ssid", ssid_buf);
        if (err != ESP_OK) ESP_LOGE(TAG, "Failed to write SSID to NVS (%s)", esp_err_to_name(err));
        err = nvs_set_str(nvs_handle, "password", pass_buf);
        if (err != ESP_OK) ESP_LOGE(TAG, "Failed to write password to NVS (%s)", esp_err_to_name(err));
        nvs_commit(nvs_handle);
        nvs_close(nvs_handle);
        ESP_LOGI(TAG, "Wi-Fi credentials saved to NVS.");
    }

    const char* html_response = "<h1>Connecting to Wi-Fi...</h1>"
                                "<p>SSID: %s</p>"
                                "<p>Please wait, the device will restart and try to connect.</p>"
                                "<p>You can close this page now.</p>";
    char response_buffer[256];
    snprintf(response_buffer, sizeof(response_buffer), html_response, ssid_buf);
    httpd_resp_send(req, response_buffer, HTTPD_RESP_USE_STRLEN);

    // Give client time to receive response, then restart
    vTaskDelay(pdMS_TO_TICKS(1000));
    esp_restart();
    return ESP_OK;
}

static esp_err_t http_server_start(void) {
    httpd_handle_t start_server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.uri_wildcard_subtree = true; // Allow handling of all URIs

    ESP_LOGI(TAG, "Starting HTTP server on port: '%d'", config.server_port);
    if (httpd_start(&start_server, &config) == ESP_OK) {
        ESP_LOGI(TAG, "Registering URI handlers");
        httpd_register_uri_handler(start_server, &root);
        httpd_register_uri_handler(start_server, &connect_uri);
        server = start_server;
        return ESP_OK;
    }
    ESP_LOGE(TAG, "Error starting http server!");
    return ESP_FAIL;
}

static void http_server_stop(void) {
    if (server) {
        httpd_stop(server);
        server = NULL;
        ESP_LOGI(TAG, "HTTP server stopped.");
    }
}

// --- Polling Task (runs after successful Wi-Fi connection) ---
static void polling_task(void *pvParameters) {
    bool current_lock_status = false;
    bool current_door_status = false;
    bool api_success = false;

    // Initialize SNTP for time synchronization (important for HTTPS certificate validation)
    initialize_sntp();
    obtain_time();

    for (;;) {
        ESP_LOGI(TAG, "--- Starting Lock Status Polling Cycle ---");

        wifi_ap_record_t ap_info;
        // Check if STA mode is connected. esp_wifi_sta_get_ap_info implicitly checks connection.
        if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
            ESP_LOGI(TAG, "WiFi is connected. Fetching lock status...");
            api_success = get_lock_and_door_status(&current_lock_status, &current_door_status);
        } else {
            ESP_LOGW(TAG, "WiFi is NOT connected in STA mode or disconnected. Re-initializing STA mode.");
            // If Wi-Fi disconnected, stop polling task and let app_main handle re-connection
            set_led_state(false, false, true); // All LEDs off to indicate problem
            vTaskDelete(NULL); // Delete this task, app_main will handle re-init
        }

        ESP_LOGI(TAG, "Updating LED state...");
        set_led_state(current_lock_status, current_door_status, !api_success);
        ESP_LOGI(TAG, "LED state updated.");

        ESP_LOGI(TAG, "Waiting for next polling interval (%d seconds)...", POLLING_INTERVAL_MS / 1000);
        vTaskDelay(pdMS_TO_TICKS(POLLING_INTERVAL_MS));
        ESP_LOGI(TAG, "--- Polling Cycle Complete ---");
    }
}

// --- Time Synchronization (for HTTPS) ---
static void obtain_time(void) {
    ESP_LOGI(TAG, "Initializing SNTP");
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, "pool.ntp.org");
    sntp_init();

    // Wait for time to be set
    time_t now = 0;
    struct tm timeinfo = { 0 };
    int retry = 0;
    const int retry_count = 10;
    while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
    time(&now);
    localtime_r(&now, &timeinfo);
    ESP_LOGI(TAG, "Time is: %s", asctime(&timeinfo));
}

static void initialize_sntp(void) {
    esp_sntp_init();
}

// --- Main application entry point (IDF equivalent of setup()) ---
extern "C" void app_main(void) {
    // Give USB CDC a moment to initialize (similar to Arduino's delay(2000))
    vTaskDelay(pdMS_TO_TICKS(2000));
    ESP_LOGI(TAG, "--- Starting app_main ---");

    // Initialize NVS (Non-Volatile Storage)
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    ESP_LOGI(TAG, "NVS flash initialized.");

    // Configure LED GPIOs
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << GREEN_LED_PIN) | (1ULL << RED_LED_PIN) | (1ULL << YELLOW_LED_PIN),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    gpio_config(&io_conf);
    ESP_LOGI(TAG, "LED pins configured.");

    // Turn all LEDs off initially
    set_led_state(false, false, true); // All off to indicate initial state
    ESP_LOGI(TAG, "LEDs initialized to OFF.");

    // Initialize network interfaces and event loop
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // Read saved credentials from NVS
    nvs_handle_t nvs_handle;
    size_t ssid_len = MAX_SSID_LEN;
    size_t pass_len = MAX_PASS_LEN;
    esp_err_t err = nvs_open(STORAGE_NAMESPACE, NVS_READONLY, &nvs_handle);
    if (err == ESP_OK) {
        err = nvs_get_str(nvs_handle, "ssid", saved_ssid, &ssid_len);
        if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
            ESP_LOGE(TAG, "Error (%s) reading SSID from NVS!", esp_err_to_name(err));
        }
        err = nvs_get_str(nvs_handle, "password", saved_password, &pass_len);
        if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
            ESP_LOGE(TAG, "Error (%s) reading password from NVS!", esp_err_to_name(err));
        }
        nvs_close(nvs_handle);
    } else {
        ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err));
    }

    if (strlen(saved_ssid) > 0) {
        ESP_LOGI(TAG, "Found saved Wi-Fi credentials. Attempting to connect to STA.");
        wifi_init_sta();
    } else {
        ESP_LOGI(TAG, "No saved Wi-Fi credentials. Starting AP for provisioning.");
        wifi_init_ap();
        ESP_ERROR_CHECK(http_server_start());
        in_ap_mode = true;
    }

    // If in AP mode, the app_main will exit, and FreeRTOS will run the HTTP server task.
    // If in STA mode, the polling_task will be created by the event handler upon connection.
    // The main thread can then be suspended or do other work.
    // For this example, we'll just keep the main task alive.
    for (;;) {
        if (in_ap_mode) {
            // Blink yellow LED in AP mode
            gpio_set_level(YELLOW_LED_PIN, !gpio_get_level(YELLOW_LED_PIN));
            vTaskDelay(pdMS_TO_TICKS(500)); // Blink every 500ms
        } else {
            // If not in AP mode, the polling task handles LED updates.
            // This main loop just keeps app_main alive.
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
}

main/CMakeLists.txt

Code: Select all

set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_REQUIRES
    esp_system
    esp_event
    esp_wifi
    esp_netif
    esp_log
    nvs_flash
    lwip
    esp_http_client
    esp_tls
    driver
    mdns
    esp_sntp
    esp_http_server
    ArduinoJson
)
#idf_component_register()

frhr
Espressif staff
Espressif staff
Posts: 12
Joined: Tue Jan 10, 2023 10:41 am

Re: project_description.json not being created

Postby frhr » Thu Jul 10, 2025 9:02 am

Hello,

the project's CMakeLists.txt seems strange. First the project() is called before project.cmake is included. Note that the whole esp-idf build logic is part of the project() macro which is defined in project.cmake and overrides the default cmake's project() function. Also the build system does the component discovery and calls add_subdirectory() for component directories. There should be no need to call it manually for the main component.

The idf_component_register() should be called, or at least the register_component() compatibility function, in the main component CMakeLists.txt

So it looks like the esp-idf build system is not called at all and hence the project_description.json is not created. Please try to take a look at Build System and Build Your First Project for more information.

HTH

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 1 guest