tonyp7 / esp32-wifi-manager

Captive Portal for ESP32 that can connect to a saved wireless network or start an access point where you can connect to existing wifis.
MIT License
648 stars 214 forks source link

Need help - problem getting OTA to work with Wifi Manager #156

Closed Shakir555 closed 10 months ago

Shakir555 commented 10 months ago

Need help - problem getting OTA to work with Wifi Manager #156

Shakir555 commented 10 months ago
// Libraries
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <esp_wifi.h>
#include <esp_netif.h>
#include <http_app.h>
#include <freertos/FreeRTOS.h>
#include <esp_http_server.h>
#include <freertos/task.h>
#include <esp_ota_ops.h>
#include <esp_system.h>
#include <lwip/ip4_addr.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <sys/param.h>

#include <wifi_manager.h>

#define WIFI_SSID "ESP32 OTA Update"

// Tag used for ESP Serial Console Messages
static const char TAG[] = "main";

void monitoring_task(void *pvParameter) {
    for (;;) {
        ESP_LOGI(TAG, "free heap: %"PRIu32, esp_get_free_heap_size());
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

/*
 * Serve OTA update portal (index.html)
 */
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
extern const uint8_t index_html_end[] asm("_binary_index_html_end");

esp_err_t index_get_handler(httpd_req_t *req)
{
    httpd_resp_send(req, (const char *) index_html_start, index_html_end - index_html_start);
    return ESP_OK;
}

esp_err_t update_post_handler(httpd_req_t *req)
{
    char buf[2000];
    esp_ota_handle_t ota_handle;
    int remaining = req->content_len;

    const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
    ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle));

    while (remaining > 0) {
        int recv_len = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));

        // Timeout Error: Just retry
        if (recv_len == HTTPD_SOCK_ERR_TIMEOUT) {
            continue;

        // Serious Error: Abort OTA
        } else if (recv_len <= 0) {
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Protocol Error");
            return ESP_FAIL;
        }

        // Successful Upload: Flash firmware chunk
        if (esp_ota_write(ota_handle, (const void *)buf, recv_len) != ESP_OK) {
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Flash Error");
            return ESP_FAIL;
        }

        remaining -= recv_len;
    }

    // Validate and switch to new OTA image and reboot
    if (esp_ota_end(ota_handle) != ESP_OK || esp_ota_set_boot_partition(ota_partition) != ESP_OK) {
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error");
            return ESP_FAIL;
    }

    httpd_resp_sendstr(req, "Firmware update complete, rebooting now!\n");

    vTaskDelay(500 / portTICK_PERIOD_MS);
    esp_restart();

    return ESP_OK;
}

/*
 * HTTP Server
 */
httpd_uri_t index_get = {
    .uri      = "/",
    .method   = HTTP_GET,
    .handler  = index_get_handler,
    .user_ctx = NULL
};

httpd_uri_t update_post = {
    .uri      = "/update",
    .method   = HTTP_POST,
    .handler  = update_post_handler,
    .user_ctx = NULL
};

static esp_err_t http_server_init(void)
{
    static httpd_handle_t http_server = NULL;

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    if (httpd_start(&http_server, &config) == ESP_OK) {
        httpd_register_uri_handler(http_server, &index_get);
        httpd_register_uri_handler(http_server, &update_post);
    }

    return http_server == NULL ? ESP_FAIL : ESP_OK;
}

void ota_update_task(void *pvParameters) {
    // Mark current app as valid
    const esp_partition_t *partition = esp_ota_get_running_partition();
    ESP_LOGI(TAG, "Currently running partition:%s\r\n", partition->label);

    esp_ota_img_states_t ota_state;
    if (esp_ota_get_state_partition(partition, &ota_state) == ESP_OK) {
        if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
            esp_ota_mark_app_valid_cancel_rollback();
        }
    }
    for (;;) {
        ESP_LOGI(TAG, "OTA Running");
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

void ota_update_task_create(void) {

    http_server_init();

    // Create the OTA updates task
    xTaskCreatePinnedToCore(&ota_update_task, "ota task", 4056, NULL, 1, NULL, 1);
}

void cb_connection_ok(void *pvParameter) {

    ip_event_got_ip_t *param = (ip_event_got_ip_t *)pvParameter;

    //Transform IP to human readable string
    char str_ip[16];
    esp_ip4addr_ntoa(&param->ip_info.ip, str_ip, IP4ADDR_STRLEN_MAX);

    esp_ip4_addr_t zero_ip;
    memset(&zero_ip, 0, sizeof(esp_ip4_addr_t));

    if (memcmp(&param->ip_info.ip, &zero_ip, sizeof(esp_ip4_addr_t)) != 0) {
        ota_update_task_create();
    }
}

void app_main(void) {
    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);

    // Start the Wifi Manager
    wifi_manager_start();

    // Register a callback to integrate code with the wifi manager
    wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok);

    // Here, create a task on core 1 that monitors free heap memory
    xTaskCreatePinnedToCore(&monitoring_task, "monitoring task", 4056, NULL, 1, NULL, 1);
}
Shakir555 commented 10 months ago
/*
Copyright (c) 2017-2020 Tony Pottier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@file http_app.c
@author Tony Pottier
@brief Defines all functions necessary for the HTTP server to run.

Contains the freeRTOS task for the HTTP listener and all necessary support
function to process requests, decode URLs, serve files, etc. etc.

@note http_server task cannot run without the wifi_manager task!
@see https://idyl.io
@see https://github.com/tonyp7/esp32-wifi-manager
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_system.h>
#include <esp_netif.h>
#include <esp_http_server.h>

#include <wifi_manager.h>
#include <http_app.h>

/* @brief tag used for ESP serial console messages */
static const char TAG[] = "http_server";

/* @brief the HTTP server handle */
static httpd_handle_t httpd_handle = NULL;

/* function pointers to URI handlers that can be user made */
esp_err_t (*custom_get_httpd_uri_handler)(httpd_req_t *r) = NULL;
esp_err_t (*custom_post_httpd_uri_handler)(httpd_req_t *r) = NULL;

/* strings holding the URLs of the wifi manager */
static char* http_root_url = NULL;
static char* http_redirect_url = NULL;
static char* http_js_url = NULL;
static char* http_css_url = NULL;
static char* http_connect_url = NULL;
static char* http_ap_url = NULL;
static char* http_status_url = NULL;

/**
 * @brief embedded binary data.
 * @see file "component.mk"
 * @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data
 */
extern const uint8_t style_css_start[] asm("_binary_style_css_start");
extern const uint8_t style_css_end[]   asm("_binary_style_css_end");
extern const uint8_t code_js_start[] asm("_binary_code_js_start");
extern const uint8_t code_js_end[] asm("_binary_code_js_end");
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
extern const uint8_t index_html_end[] asm("_binary_index_html_end");

/* const httpd related values stored in ROM */
const static char http_200_hdr[] = "200 OK";
const static char http_302_hdr[] = "302 Found";
const static char http_400_hdr[] = "400 Bad Request";
const static char http_404_hdr[] = "404 Not Found";
const static char http_503_hdr[] = "503 Service Unavailable";
const static char http_location_hdr[] = "Location";
const static char http_content_type_html[] = "text/html";
const static char http_content_type_js[] = "text/javascript";
const static char http_content_type_css[] = "text/css";
const static char http_content_type_json[] = "application/json";
const static char http_cache_control_hdr[] = "Cache-Control";
const static char http_cache_control_no_cache[] = "no-store, no-cache, must-revalidate, max-age=0";
const static char http_cache_control_cache[] = "public, max-age=31536000";
const static char http_pragma_hdr[] = "Pragma";
const static char http_pragma_no_cache[] = "no-cache";

esp_err_t http_app_set_handler_hook( httpd_method_t method,  esp_err_t (*handler)(httpd_req_t *r)  ){

    if(method == HTTP_GET){
        custom_get_httpd_uri_handler = handler;
        return ESP_OK;
    }
    else if(method == HTTP_POST){
        custom_post_httpd_uri_handler = handler;
        return ESP_OK;
    }
    else{
        return ESP_ERR_INVALID_ARG;
    }

}

static esp_err_t http_server_delete_handler(httpd_req_t *req){

    ESP_LOGI(TAG, "DELETE %s", req->uri);

    /* DELETE /connect.json */
    if(strcmp(req->uri, http_connect_url) == 0){
        wifi_manager_disconnect_async();

        httpd_resp_set_status(req, http_200_hdr);
        httpd_resp_set_type(req, http_content_type_json);
        httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache);
        httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache);
        httpd_resp_send(req, NULL, 0);
    }
    else{
        httpd_resp_set_status(req, http_404_hdr);
        httpd_resp_send(req, NULL, 0);
    }

    return ESP_OK;
}

static esp_err_t http_server_post_handler(httpd_req_t *req){

    esp_err_t ret = ESP_OK;

    ESP_LOGI(TAG, "POST %s", req->uri);

    /* POST /connect.json */
    if(strcmp(req->uri, http_connect_url) == 0){

        /* buffers for the headers */
        size_t ssid_len = 0, password_len = 0;
        char *ssid = NULL, *password = NULL;

        /* len of values provided */
        ssid_len = httpd_req_get_hdr_value_len(req, "X-Custom-ssid");
        password_len = httpd_req_get_hdr_value_len(req, "X-Custom-pwd");

        if(ssid_len && ssid_len <= MAX_SSID_SIZE && password_len && password_len <= MAX_PASSWORD_SIZE){

            /* get the actual value of the headers */
            ssid = malloc(sizeof(char) * (ssid_len + 1));
            password = malloc(sizeof(char) * (password_len + 1));
            httpd_req_get_hdr_value_str(req, "X-Custom-ssid", ssid, ssid_len+1);
            httpd_req_get_hdr_value_str(req, "X-Custom-pwd", password, password_len+1);

            wifi_config_t* config = wifi_manager_get_wifi_sta_config();
            memset(config, 0x00, sizeof(wifi_config_t));
            memcpy(config->sta.ssid, ssid, ssid_len);
            memcpy(config->sta.password, password, password_len);
            ESP_LOGI(TAG, "ssid: %s, password: %s", ssid, password);
            ESP_LOGD(TAG, "http_server_post_handler: wifi_manager_connect_async() call");
            wifi_manager_connect_async();

            /* free memory */
            free(ssid);
            free(password);

            httpd_resp_set_status(req, http_200_hdr);
            httpd_resp_set_type(req, http_content_type_json);
            httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache);
            httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache);
            httpd_resp_send(req, NULL, 0);

        }
        else{
            /* bad request the authentification header is not complete/not the correct format */
            httpd_resp_set_status(req, http_400_hdr);
            httpd_resp_send(req, NULL, 0);
        }

    }
    else{

        if(custom_post_httpd_uri_handler == NULL){
            httpd_resp_set_status(req, http_404_hdr);
            httpd_resp_send(req, NULL, 0);
        }
        else{

            /* if there's a hook, run it */
            ret = (*custom_post_httpd_uri_handler)(req);
        }
    }

    return ret;
}

static esp_err_t http_server_get_handler(httpd_req_t *req){

    char* host = NULL;
    size_t buf_len;
    esp_err_t ret = ESP_OK;

    ESP_LOGD(TAG, "GET %s", req->uri);

    /* Get header value string length and allocate memory for length + 1,
     * extra byte for null termination */
    buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
    if (buf_len > 1) {
        host = malloc(buf_len);
        if(httpd_req_get_hdr_value_str(req, "Host", host, buf_len) != ESP_OK){
            /* if something is wrong we just 0 the whole memory */
            memset(host, 0x00, buf_len);
        }
    }

    /* determine if Host is from the STA IP address */
    wifi_manager_lock_sta_ip_string(portMAX_DELAY);
    bool access_from_sta_ip = host != NULL?strstr(host, wifi_manager_get_sta_ip_string()):false;
    wifi_manager_unlock_sta_ip_string();

    if (host != NULL && !strstr(host, DEFAULT_AP_IP) && !access_from_sta_ip) {

        /* Captive Portal functionality */
        /* 302 Redirect to IP of the access point */
        httpd_resp_set_status(req, http_302_hdr);
        httpd_resp_set_hdr(req, http_location_hdr, http_redirect_url);
        httpd_resp_send(req, NULL, 0);

    }
    else{

        /* GET /  */
        if(strcmp(req->uri, http_root_url) == 0){
            httpd_resp_set_status(req, http_200_hdr);
            httpd_resp_set_type(req, http_content_type_html);
            httpd_resp_send(req, (char*)index_html_start, index_html_end - index_html_start);
        }
        /* GET /code.js */
        else if(strcmp(req->uri, http_js_url) == 0){
            httpd_resp_set_status(req, http_200_hdr);
            httpd_resp_set_type(req, http_content_type_js);
            httpd_resp_send(req, (char*)code_js_start, code_js_end - code_js_start);
        }
        /* GET /style.css */
        else if(strcmp(req->uri, http_css_url) == 0){
            httpd_resp_set_status(req, http_200_hdr);
            httpd_resp_set_type(req, http_content_type_css);
            httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_cache);
            httpd_resp_send(req, (char*)style_css_start, style_css_end - style_css_start);
        }
        /* GET /ap.json */
        else if(strcmp(req->uri, http_ap_url) == 0){

            /* if we can get the mutex, write the last version of the AP list */
            if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){

                httpd_resp_set_status(req, http_200_hdr);
                httpd_resp_set_type(req, http_content_type_json);
                httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache);
                httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache);
                char* ap_buf = wifi_manager_get_ap_list_json();
                httpd_resp_send(req, ap_buf, strlen(ap_buf));
                wifi_manager_unlock_json_buffer();
            }
            else{
                httpd_resp_set_status(req, http_503_hdr);
                httpd_resp_send(req, NULL, 0);
                ESP_LOGE(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex");
            }

            /* request a wifi scan */
            wifi_manager_scan_async();
        }
        /* GET /status.json */
        else if(strcmp(req->uri, http_status_url) == 0){

            if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
                char *buff = wifi_manager_get_ip_info_json();
                if(buff){
                    httpd_resp_set_status(req, http_200_hdr);
                    httpd_resp_set_type(req, http_content_type_json);
                    httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache);
                    httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache);
                    httpd_resp_send(req, buff, strlen(buff));
                    wifi_manager_unlock_json_buffer();
                }
                else{
                    httpd_resp_set_status(req, http_503_hdr);
                    httpd_resp_send(req, NULL, 0);
                }
            }
            else{
                httpd_resp_set_status(req, http_503_hdr);
                httpd_resp_send(req, NULL, 0);
                ESP_LOGE(TAG, "http_server_netconn_serve: GET /status.json failed to obtain mutex");
            }
        }
        else{

            if(custom_get_httpd_uri_handler == NULL){
                httpd_resp_set_status(req, http_404_hdr);
                httpd_resp_send(req, NULL, 0);
            }
            else{

                /* if there's a hook, run it */
                ret = (*custom_get_httpd_uri_handler)(req);
            }
        }

    }

    /* memory clean up */
    if(host != NULL){
        free(host);
    }

    return ret;

}

/* URI wild card for any GET request */
static const httpd_uri_t http_server_get_request = {
    .uri       = "*",
    .method    = HTTP_GET,
    .handler   = http_server_get_handler
};

static const httpd_uri_t http_server_post_request = {
    .uri    = "*",
    .method = HTTP_POST,
    .handler = http_server_post_handler
};

static const httpd_uri_t http_server_delete_request = {
    .uri    = "*",
    .method = HTTP_DELETE,
    .handler = http_server_delete_handler
};

void http_app_stop(){

    if(httpd_handle != NULL){

        /* dealloc URLs */
        if(http_root_url) {
            free(http_root_url);
            http_root_url = NULL;
        }
        if(http_redirect_url){
            free(http_redirect_url);
            http_redirect_url = NULL;
        }
        if(http_js_url){
            free(http_js_url);
            http_js_url = NULL;
        }
        if(http_css_url){
            free(http_css_url);
            http_css_url = NULL;
        }
        if(http_connect_url){
            free(http_connect_url);
            http_connect_url = NULL;
        }
        if(http_ap_url){
            free(http_ap_url);
            http_ap_url = NULL;
        }
        if(http_status_url){
            free(http_status_url);
            http_status_url = NULL;
        }

        /* stop server */
        httpd_stop(httpd_handle);
        httpd_handle = NULL;
    }
}

/**
 * @brief helper to generate URLs of the wifi manager
 */
static char* http_app_generate_url(const char* page){

    char* ret;

    int root_len = strlen(WEBAPP_LOCATION);
    const size_t url_sz = sizeof(char) * ( (root_len+1) + ( strlen(page) + 1) );

    ret = malloc(url_sz);
    memset(ret, 0x00, url_sz);
    strcpy(ret, WEBAPP_LOCATION);
    ret = strcat(ret, page);

    return ret;
}

void http_app_start(bool lru_purge_enable){

    esp_err_t err;

    if(httpd_handle == NULL){

        httpd_config_t config = HTTPD_DEFAULT_CONFIG();

        /* this is an important option that isn't set up by default.
         * We could register all URLs one by one, but this would not work while the fake DNS is active */
        config.uri_match_fn = httpd_uri_match_wildcard;
        config.lru_purge_enable = lru_purge_enable;

        /* generate the URLs */
        if(http_root_url == NULL){
            int root_len = strlen(WEBAPP_LOCATION);

            /* all the pages */
            const char page_js[] = "code.js";
            const char page_css[] = "style.css";
            const char page_connect[] = "connect.json";
            const char page_ap[] = "ap.json";
            const char page_status[] = "status.json";

            /* root url, eg "/"   */
            const size_t http_root_url_sz = sizeof(char) * (root_len+1);
            http_root_url = malloc(http_root_url_sz);
            memset(http_root_url, 0x00, http_root_url_sz);
            strcpy(http_root_url, WEBAPP_LOCATION);

            /* redirect url */
            size_t redirect_sz = 22 + root_len + 1; /* strlen(http://255.255.255.255) + strlen("/") + 1 for \0 */
            http_redirect_url = malloc(sizeof(char) * redirect_sz);
            *http_redirect_url = '\0';

            if(root_len == 1){
                snprintf(http_redirect_url, redirect_sz, "http://%s", DEFAULT_AP_IP);
            }
            else{
                snprintf(http_redirect_url, redirect_sz, "http://%s%s", DEFAULT_AP_IP, WEBAPP_LOCATION);
            }

            /* generate the other pages URLs*/
            http_js_url = http_app_generate_url(page_js);
            http_css_url = http_app_generate_url(page_css);
            http_connect_url = http_app_generate_url(page_connect);
            http_ap_url = http_app_generate_url(page_ap);
            http_status_url = http_app_generate_url(page_status);

        }

        err = httpd_start(&httpd_handle, &config);

        if (err == ESP_OK) {
            ESP_LOGI(TAG, "Registering URI handlers");
            httpd_register_uri_handler(httpd_handle, &http_server_get_request);
            httpd_register_uri_handler(httpd_handle, &http_server_post_request);
            httpd_register_uri_handler(httpd_handle, &http_server_delete_request);
        }
    }

}
CJCombrink commented 10 months ago

Hi @Shakir555

First off

Just some general guidance on posting in GitHub in general (or any other project)

  1. Keep the title short and on point It helps the owner to see at a glance what the ticket is about
  2. Add the needed information in the body of the new template.
  3. Make sure to follow the template presented when you "Create a ticket". Fill out all information that is requested in full. If you think some information that is requested is not relevant, always state why you think it is not relevant, don't just remove or leave it empty.

For this specific issue, the code added to the ticket has no context and there is no meaningful way to assist. If you change existing code and need help, fork the repo and make your changes and make a PR. That way one can assist by looking at the changes you have made.

Please, as a first step, reword your title and edit the first comment and complete the information there as required.

Trying to answer

Ok, sorry I had to state those, now to try and assist: I am successfully using this repo along with OTA. I am not sure where you are struggling with, but I could plugin in all of my OTA logic on top of this library by using the http_hook example as-is. Look at the my_get_handler()

PS: I am not associated with this repo, I just find it useful. I will most likely close an issue like this if it pops up on my repo.

CJCombrink commented 10 months ago

A quick glance at the code, you most certainly should not try to start a new web server. This line makes me think you are trying to:

err = httpd_start(&httpd_handle, &config);

You have to plug into the web server started by the wifi manager (this repo) and then handle the request you get, as per the function I linked above.

Shakir555 commented 10 months ago

@CJCombrink Hi CJ thanksso much. will try on this. any ideas how to do the fork the repo and make your changes and make a PR. Im so sorry because Im novice to github etc....

Shakir555 commented 10 months ago

Hi I am back, here my new issue still got problem with error 404. Below is what i working on. It does not support the HTTP_POST 404 error wifi

This my code below


// Libraries
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <esp_wifi.h>
#include <esp_netif.h>
#include <http_app.h>
#include <freertos/FreeRTOS.h>
#include <esp_http_server.h>
#include <freertos/task.h>
#include <esp_ota_ops.h>
#include <esp_system.h>
#include <lwip/ip4_addr.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <sys/param.h>

#include <wifi_manager.h>

/* @brief tag used for ESP serial console messages */
static const char TAG[] = "main";

void monitoring_task(void *pvParameter) {
    for (;;) {
        ESP_LOGI(TAG, "free heap: %"PRIu32, esp_get_free_heap_size());
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

/*
 * Serve OTA update portal (index.html)
 */
extern const uint8_t ota_html_start[] asm("_binary_ota_html_start");
extern const uint8_t ota_html_end[] asm("_binary_ota_html_end");

static esp_err_t my_get_handler(httpd_req_t *req){

    /* our custom page sits at /helloworld in this example */
    if(strcmp(req->uri, "/ota") == 0){

        ESP_LOGI(TAG, "Serving page /ota");
        httpd_resp_send(req, (const char *) ota_html_start, ota_html_end - ota_html_start);

        httpd_resp_set_status(req, "200 OK");
        httpd_resp_set_type(req, "text/html");

    }
    else{
        /* send a 404 otherwise */
        httpd_resp_send_404(req);
    }

    return ESP_OK;
}

esp_err_t update_post_handler(httpd_req_t *req)
{
    char buf[1000];
    esp_ota_handle_t ota_handle;
    int remaining = req->content_len;

    const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
    ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle));

    while (remaining > 0) {
        int recv_len = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));

        // Timeout Error: Just retry
        if (recv_len == HTTPD_SOCK_ERR_TIMEOUT) {
            continue;

        // Serious Error: Abort OTA
        } else if (recv_len <= 0) {
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Protocol Error");
            return ESP_FAIL;
        }

        // Successful Upload: Flash firmware chunk
        if (esp_ota_write(ota_handle, (const void *)buf, recv_len) != ESP_OK) {
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Flash Error");
            return ESP_FAIL;
        }

        remaining -= recv_len;
    }

    // Validate and switch to new OTA image and reboot
    if (esp_ota_end(ota_handle) != ESP_OK || esp_ota_set_boot_partition(ota_partition) != ESP_OK) {
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error");
            return ESP_FAIL;
    }

    httpd_resp_sendstr(req, "Firmware update complete, rebooting now!\n");

    vTaskDelay(500 / portTICK_PERIOD_MS);
    esp_restart();

    return ESP_OK;
}

httpd_uri_t update_post = {
    .uri      = "/ota",
    .method   = HTTP_POST,
    .handler  = update_post_handler,
    .user_ctx = NULL
};

void ota_update_task(void *pvParameters) {
    // Mark current app as valid
    const esp_partition_t *partition = esp_ota_get_running_partition();
    ESP_LOGI(TAG, "Currently running partition:%s\r\n", partition->label);

    esp_ota_img_states_t ota_state;
    if (esp_ota_get_state_partition(partition, &ota_state) == ESP_OK) {
        if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
            esp_ota_mark_app_valid_cancel_rollback();
        }
    }
    for (;;) {
        ESP_LOGI(TAG, "OTA Running");
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

void ota_update_task_create(void) {
    // Create the OTA updates task
    xTaskCreatePinnedToCore(&ota_update_task, "ota task", 4056, NULL, 1, NULL, 1);
}

void cb_connection_ok(void *pvParameter) {

    ip_event_got_ip_t *param = (ip_event_got_ip_t *)pvParameter;

    //Transform IP to human readable string
    char str_ip[16];
    esp_ip4addr_ntoa(&param->ip_info.ip, str_ip, IP4ADDR_STRLEN_MAX);

    esp_ip4_addr_t zero_ip;
    memset(&zero_ip, 0, sizeof(esp_ip4_addr_t));

    if (memcmp(&param->ip_info.ip, &zero_ip, sizeof(esp_ip4_addr_t)) != 0) {
        ota_update_task_create();
    }
}

void app_main()
{
    /* start the wifi manager */
    wifi_manager_start();

    // Register a callback to integrate code with the wifi manager
    wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok);

    /* set custom handler for the http server
     * Now navigate to /helloworld to see the custom page
     * */
    http_app_set_handler_hook(HTTP_GET, &my_get_handler);
}
CJCombrink commented 10 months ago

@Shakir555 Thanks for updating the comment, now please update the header, something like: "Need help - problem getting OTA to work with Wifi Manager"

any ideas how to do the fork the repo and make your changes and make a PR.

For your specific issue, you don't need a PR since you can use this repo (Wifi Manager) as-is in your application, you don't need to modify it. A PR is for when you are trying to make or suggest changes to someone else's repo.

here my new issue still got problem with error 404.

Ok, that issue looks familiar. If I look at your code I am going to guess it is this part:

    httpd_resp_sendstr(req, "Firmware update complete, rebooting now!\n");
    vTaskDelay(500 / portTICK_PERIOD_MS);
    esp_restart();

If I look at the screenshot it appears that the upload worked (reported 100% progress) Can you confirm that the OTA did work? Did your device at least restart and applied the new OTA? I am assuming yes.

The issue is then that you reboot the ESP before the ESP gets a chance to send the last response to the device. The solution is to move the reboot to another task. Look at this example: https://github.com/backinabit/ESP32-OTA-Webserver/blob/master/OTAServer.c Specifically the systemRebootTask and where it is called from OTA_update_status_handler

PS: The backinabit/ESP32-OTA-Webserver repo is almost exactly the code I use for my OTA with this repo and it works without issues.

Shakir555 commented 10 months ago

@CJCombrink Hi CJ, I already changes the header. I try following the ota repo that you recommended. try to mix out the code. but still seems no luck on it on the 404 error. Below is my main code and my html code. i quite suspect my html code somehow prevent the OTA to update on the esp32.

user_main.c


//Libraries
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <esp_wifi.h>
#include <esp_netif.h>
#include <http_app.h>
#include <freertos/FreeRTOS.h>
#include <esp_http_server.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_ota_ops.h>
#include <esp_system.h>
#include <lwip/ip4_addr.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <sys/param.h>

#include <wifi_manager.h>

static const char TAG[] = "main";

//Serve OTA update portal (index.html)
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
extern const uint8_t index_html_end[] asm("_binary_index_html_end");

//Variables
httpd_handle_t ota_server = NULL;
int8_t flash_status = 0;

EventGroupHandle_t reboot_event_group;
const int REBOOT_BIT = BIT0;

//System Reboot Task
void systemRebootTask(void *pvParameter)
{
    //Init the event group
    reboot_event_group = xEventGroupCreate();

    //Clear the bit
    xEventGroupClearBits(reboot_event_group, REBOOT_BIT);

    for (;;)
    {
        //Wait here until the bits gets set for reboot
        EventBits_t staBits = xEventGroupWaitBits(reboot_event_group, REBOOT_BIT, pdTRUE, pdFALSE, portMAX_DELAY);

        //Did portMAX_DELAY evet timeout, not sure so let just check to be sure
        if ((staBits & REBOOT_BIT) != 0)
        {
            ESP_LOGI(TAG, "Reboot command, Restarting");
            vTaskDelay(2000 / portTICK_PERIOD_MS);

            esp_restart();
        }
    }
}

//Send index_html Page
esp_err_t ota_index_html_handler(httpd_req_t *req)
{
    ESP_LOGI(TAG, "index_html Requested");

    //Clear this very time page is requested 
    flash_status = 0;

    httpd_resp_set_type(req, "text/html");

    httpd_resp_send(req, (const char *)index_html_start, index_html_end - index_html_start);
    return ESP_OK;
}

//Status
esp_err_t ota_update_status_handler(httpd_req_t *req)
{
    char ledJSON[100];

    ESP_LOGI(TAG, "Status Requested");

    sprintf(ledJSON, "{\"status\":%d,\"compile_time\":\"%s\",\"compile_date\":\"%s\"}", flash_status, __TIME__, __DATE__);

    httpd_resp_set_type(req, "application/json");
    httpd_resp_send(req, ledJSON, strlen(ledJSON));

    if (flash_status == 1)
    {
        //Cannot directly call reboot here because need
        //browser to get the ack back
        xEventGroupSetBits(reboot_event_group, REBOOT_BIT);
    }
    return ESP_OK;
}

//Receive .bin File
esp_err_t ota_update_post_handler(httpd_req_t *req)
{
    esp_ota_handle_t ota_handle;

    char ota_buff[1024];
    int content_length = req->content_len;
    int content_received = 0;
    int recv_len;
    bool is_req_body_started = false;
    const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);

    //Unsuccesful Flashing
    flash_status = -1;

    do
    {
        //Read the data for the request
        if ((recv_len = httpd_req_recv(req, ota_buff, MIN(content_length, sizeof(ota_buff)))) < 0)
        {
            if (recv_len == HTTPD_SOCK_ERR_TIMEOUT)
            {
                ESP_LOGI(TAG, "Socket Timeout");
                //Retry Receiving if timeout occured
                continue;
            }

            ESP_LOGI(TAG, "OTA Other Error %d", recv_len);
            return ESP_FAIL;

        }
        printf("OTA RX: %d of %d\r", content_received, content_length);

        //If this the first data are receiving
        //If so, it will have the information in the header we need.
        if (!is_req_body_started)
        {
            is_req_body_started = true;

            //Lets find out where the actual data start aster header info
            char *body_start_p = strstr(ota_buff, "\r\n\r\n") + 4;
            int body_part_len = recv_len - (body_start_p - ota_buff);

            esp_err_t err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &ota_handle);
            if (err != ESP_OK)
            {
                printf("Error with OTA Begin, Cancelling OTA\r\n");
                return ESP_FAIL;

            } else {

                printf("Writing to partition subtype %d at offset 0x%lx\r\n", update_partition->subtype, update_partition->address);

            }

            //Lets Write this first part of data out
            esp_ota_write(ota_handle, body_start_p, body_part_len);
        }
        else {
            //Write OTA data
            esp_ota_write(ota_handle, ota_buff, recv_len);

            content_received += recv_len;
        }
    } while (recv_len > 0 && content_received < content_length);

    if (esp_ota_end(ota_handle) == ESP_OK)
    {
        //lets Update the partition
        if(esp_ota_set_boot_partition(update_partition) == ESP_OK)
        {
            const esp_partition_t *boot_partition = esp_ota_get_boot_partition();

            //Webpage will request status when complete
            //This is to let it know it was successfull
            flash_status = 1;

            ESP_LOGI(TAG, "Next boot partition subtype %d at offset 0x%lx", boot_partition->subtype, boot_partition->address);
            ESP_LOGI(TAG, "Please Restart System...");
        }
        else {
            ESP_LOGI(TAG, "\r\n\r\n !!! Flashed Error !!!");
        }
    }
    else {
        ESP_LOGI(TAG, "\r\n\r\n !!! OTA End Error !!!");
    }
    return ESP_OK;
}

//HTTP Server
httpd_uri_t ota_index_get = {
    .uri      =   "/ota",
    .method   =   HTTP_GET,
    .handler  =   ota_index_html_handler,
    .user_ctx =   NULL
};

httpd_uri_t ota_update_post = {
    .uri      =  "/update",
    .method   =  HTTP_POST,
    .handler  =  ota_update_post_handler,
    .user_ctx =  NULL
};

httpd_uri_t ota_status_post = {
    .uri      =  "/status",
    .method   =  HTTP_POST,
    .handler  =  ota_update_status_handler,
    .user_ctx =  NULL
};

void ota_update_task(void *pvParameters) {
    // Mark current app as valid
    const esp_partition_t *partition = esp_ota_get_running_partition();
    ESP_LOGI(TAG, "Currently running partition:%s\r\n", partition->label);

    esp_ota_img_states_t ota_state;
    if (esp_ota_get_state_partition(partition, &ota_state) == ESP_OK) {
        if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
            esp_ota_mark_app_valid_cancel_rollback();
        }
    }
    for (;;) {
        ESP_LOGI(TAG, "OTA Running");
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

void ota_update_task_create(void) {

    http_server_init();

    // Create the OTA updates task
    xTaskCreatePinnedToCore(&ota_update_task, "ota task", 4056, NULL, 1, NULL, 1);
}

void cb_connection_ok(void *pvParameter) {

    ip_event_got_ip_t *param = (ip_event_got_ip_t *)pvParameter;

    //Transform IP to human readable string
    char str_ip[16];
    esp_ip4addr_ntoa(&param->ip_info.ip, str_ip, IP4ADDR_STRLEN_MAX);

    esp_ip4_addr_t zero_ip;
    memset(&zero_ip, 0, sizeof(esp_ip4_addr_t));

    if (memcmp(&param->ip_info.ip, &zero_ip, sizeof(esp_ip4_addr_t)) != 0) {
        ota_update_task_create();
    }
}

void app_main()
{
    /* start the wifi manager */
    wifi_manager_start();

    // Register a callback to integrate code with the wifi manager
    wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok);

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    //Need this task to spin up, see why in task
    xTaskCreate(&systemRebootTask, "Reboot Task", 2048, NULL, 5, NULL);

    //Lets bump up the stack size (default was 4096)
    config.stack_size = 8192;

    //Start the httpd server
    ESP_LOGI(TAG, "Starting Server on Port: '%d'", config.server_port);

    if (httpd_start(&ota_server, &config) == ESP_OK) {
        httpd_register_uri_handler(ota_server, &ota_index_get);
        httpd_register_uri_handler(ota_server, &ota_update_post);
        httpd_register_uri_handler(ota_server, &ota_status_post);
    }

    return ota_server == NULL ? ESP_FAIL : ESP_OK;
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <link rel="stylesheet" href="style.css">
    <script async src="code.js"></script>
    <title>esp32-wifi-manager</title>
</head>
<body>
    <div id="app">
        <div id="app-wrap">
            <div id="wifi">
                <header>
                    <h1>Wi-Fi</h1>
                </header>
                <div id="wifi-status">
                    <h2>Connected to:</h2>
                    <section id="connected-to">
                        <div class="ape"><div class="w0"><div class="pw"><span></span></div></div></div>
                    </section>
                </div>
                <h2>Manual connect</h2>
                <section id="manual_add">
                    <div class="ape">ADD (HIDDEN) SSID</div>
                </section>
                <h2>or choose a network...</h2>
                <section id="wifi-list">
                </section>
                <div id="pwrdby"><em>Powered by </em><a id="acredits" href="#"><strong>esp32-wifi-manager</strong></a>.</div>
            </div>
            <div id="connect_manual">
                <header>
                    <h1>Enter Details</h1>
                </header>
                <h2>Manual Connection</h2>
                <section>
                    <input id="manual_ssid" type="text" placeholder="SSID" value="">
                    <input id="manual_pwd" type="password" placeholder="Password" value="">
                </section>
                <div class="buttons">
                    <input id="manual_join" type="button" value="Join" data-connect="manual" />
                    <input id="manual_cancel" type="button" value="Cancel"/>
                </div>
            </div>
            <div id="connect">
                <header>
                    <h1>Enter Password</h1>
                </header>
                <h2>Password for <span id="ssid-pwd"></span></h2>
                <section>
                    <input id="pwd" type="password" placeholder="Password" value="">
                </section>
                <div class="buttons">
                    <input id="join" type="button" value="Join" />
                    <input id="cancel" type="button" value="Cancel"/>
                </div>
            </div>
            <div id="connect-wait">
                <header>
                    <h1>Please wait...</h1>
                </header>
                <h2>Connecting to <span id="ssid-wait"></span></h2>
                <section>
                    <div id="loading">
                        <div class="spinner"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>
                        <p class="tctr">You may lose wifi access while the esp32 recalibrates its radio. Please wait until your device automatically reconnects. This can take up to 30s.</p>
                    </div>
                    <div id="connect-success">
                        <h3 class="gr">Success!</h3>
                    </div>
                    <div id="connect-fail">
                        <h3 class="rd">Connection failed</h3>
                        <p class="tctr">Please double-check wifi password if any and make sure the access point has good signal.</p>
                    </div>
                </section>
                <div class="buttons">
                    <input id="ok-connect" type="button" value="OK" class="ctr" />
                </div>
            </div>
            <div id="connect-details">
                <div id="connect-details-wrap">
                    <header>
                        <h1></h1>
                    </header>
                    <h2></h2>
                    <section>
                        <div class="buttons">
                            <input id="disconnect" type="button" value="Disconnect" class="ctr"/>
                        </div>
                    </section>
                    <h2>IP Address</h2>
                    <section>
                        <div class="ape brdb">IP Address:<div id="ip" class="fr"></div></div>
                        <div class="ape brdb">Subnet Mask:<div id="netmask" class="fr"></div></div>
                        <div class="ape">Default Gateway:<div id="gw" class="fr"></div></div>
                    </section>
                    <div class="buttons">
                        <input id="ok-details" type="button" value="OK" class="ctr" />
                    </div>
                </div>                    
                <div id="diag-disconnect" class="diag-box">
                    <div class="diag-box-win">
                        <p>Are you sure you would like to disconnect from this wifi?</p>
                        <div class="buttons">
                            <input id="no-disconnect" type="button" value="No" />
                            <input id="yes-disconnect" type="button" value="Yes" />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div id="credits">
        <header>
            <h1>About this app...</h1>
        </header>
        <h2></h2>
        <section>
            <p><strong>esp32-wifi-manager</strong>, &copy; 2017-2020, Tony Pottier<br />Licensed under the MIT License.</p>
            <p>
                This app would not be possible without the following libraries:
            </p>
            <ul>
                <li>SpinKit, &copy; 2015, Tobias Ahlin. Licensed under the MIT License.</li>
                <li>jQuery, The jQuery Foundation. Licensed under the MIT License.</li>
                <li>cJSON, &copy; 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.</li>
            </ul>
        </section>
        <div class="buttons">
            <input id="ok-credits" type="button" value="OK" class="ctr" />
        </div>
    </div>
    <!-- Your ESP32 OTA Update content starts here -->
<h1>ESP32 OTA Firmware Update</h1>
<div>
    <label for="otafile"><strong>Firmware file: click here to Browse the File</strong> </label>
    <input type="file" id="otafile" name="otafile" />
</div>
<div>
    <button id="upload" type="button" onclick="startUpload()">Upload</button>
</div>
<div id="progress"></div>
<div id="status"></div> <!-- New element for displaying status -->

<script>
    function startUpload() {
        var otafile = document.getElementById("otafile").files;

        if (otafile.length == 0) {
            alert("No File Selected!");
        } else {
            document.getElementById("otafile").disabled = true;
            document.getElementById("upload").disabled = true;

            var file = otafile[0];
                var xhr  = new XMLHttpRequest();
                xhr.onreadystatechange = function() {
                    if (xhr.readyState == 4) {
                        if (xhr.status == 200) {
                            document.open();
                            document.write(xhr.responseText);
                            document.close();
                        } else if (xhr.status == 0) {
                            alert("Server closed the connection abruptly!");
                            location.reload()
                        } else {
                            alert(xhr.status + " Error!\n" + xhr.responseText);
                            location.reload()
                        }
                    }
                };

            xhr.upload.onprogress = function(e) {
                var progress = document.getElementById("progress");
                progress.textContent =
                    "Progress: " + ((e.loaded / e.total) * 100).toFixed(0) + "%";
            };
            xhr.open("POST", "/update", true);
            xhr.send(file);
            xhr.close("Done");
        }
    }
</script>
</body>
</html>
Shakir555 commented 10 months ago

When I try to Post/Update. it got this Warning/Error W (23504) httpd_txrx: httpd_sock_err: error in recv : 104 Wether i try to update it got this error in recv_104

Shakir555 commented 10 months ago

@CJCombrink Hi CJ, Okay Im back, I already solved the issue following your repo using the http_app_set_handler_hook on the void app_main(). Copy some of the important OTA src code and put it in the src file in the WM. It works good. Thanks buddy

        http_app_set_handler_hook(HTTP_GET, &OTA_html_handler);
    http_app_set_handler_hook(HTTP_POST, &OTA_update_post_handler);
    http_app_set_handler_hook(HTTP_POST, &OTA_update_status_handler);

last software update