diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab5c51..dbd18c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1 @@ -idf_component_register(SRCS "main.c" INCLUDE_DIRS "include") \ No newline at end of file +idf_component_register(SRCS "zh_ota_server.c" INCLUDE_DIRS "include" REQUIRES app_update esp_http_server EMBED_FILES "zh_ota_server.html") \ No newline at end of file diff --git a/README.md b/README.md index 3df3e4d..7f5aa61 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,64 @@ -# esp_component_template +# ESP32 ESP-IDF component for simple OTA server -esp_component_template \ No newline at end of file +## Tested on + +1. [ESP32 ESP-IDF v5.5.3](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32/index.html) + +## SAST Tools + +[PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. + +## Using + +In an existing project, run the following command to install the component: + +```text +cd ../your_project/components +git clone http://git.zh.com.ru/esp_components/zh_ota_server +``` + +In the application, add the component: + +```c +#include "zh_ota_server.h" +``` + +## Example + +OTA server by address http://192.168.4.1/ota + +```c +#include "nvs_flash.h" +#include "esp_wifi.h" +#include "zh_ota_server.h" + +#define WIFI_SSID "SSID" +#define WIFI_PASS "password" + +static httpd_handle_t http_server_handle = NULL; + +void app_main(void) +{ + esp_log_level_set("zh_ota_server", ESP_LOG_ERROR); + nvs_flash_init(); + esp_event_loop_create_default(); + esp_netif_init(); + esp_netif_create_default_wifi_ap(); + wifi_init_config_t wifi_config = WIFI_INIT_CONFIG_DEFAULT(); + esp_wifi_init(&wifi_config); + wifi_config_t ap_config = { + .ap = { + .ssid = WIFI_SSID, + .password = WIFI_PASS, + .max_connection = 4, + .authmode = WIFI_AUTH_WPA2_PSK, + }, + }; + esp_wifi_set_mode(WIFI_MODE_AP); + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + esp_wifi_start(); + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + httpd_start(&http_server_handle, &config); + zh_ota_server_init(http_server_handle, "/ota"); +} +``` diff --git a/component.mk b/component.mk deleted file mode 100644 index e69de29..0000000 diff --git a/include/main.h b/include/main.h deleted file mode 100644 index e69de29..0000000 diff --git a/include/zh_ota_server.h b/include/zh_ota_server.h new file mode 100644 index 0000000..44e46df --- /dev/null +++ b/include/zh_ota_server.h @@ -0,0 +1,28 @@ +/** + * @file zh_ota_server.h + */ + +#pragma once + +#include "esp_ota_ops.h" +#include "esp_log.h" +#include "esp_http_server.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Start OTA server. + * + * @param[in] server HTTP server handle. + * @param[in] path OTA server path. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_ota_server_init(httpd_handle_t server, const char *path); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/main.c b/main.c deleted file mode 100644 index e69de29..0000000 diff --git a/version.txt b/version.txt index e69de29..afaf360 100644 --- a/version.txt +++ b/version.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/zh_ota_server.c b/zh_ota_server.c new file mode 100644 index 0000000..1e99271 --- /dev/null +++ b/zh_ota_server.c @@ -0,0 +1,100 @@ +#include "zh_ota_server.h" + +static const char *TAG = "zh_ota_server"; + +#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) +#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) + +#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \ + if (!(cond)) \ + { \ + ZH_LOGE(msg, err, ##__VA_ARGS__); \ + cleanup; \ + return err; \ + } + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +static char *_ota_path = NULL; +static char *_ota_ws_path = "/zh_ota_server"; + +static esp_err_t _ota_page_handler(httpd_req_t *req); +static esp_err_t _ota_ws_page_handler(httpd_req_t *req); + +extern const uint8_t zh_ota_server_html_start[] asm("_binary_zh_ota_server_html_start"); +extern const uint8_t zh_ota_server_html_end[] asm("_binary_zh_ota_server_html_end"); + +httpd_uri_t _ota_page = { + .uri = NULL, + .method = HTTP_GET, + .handler = _ota_page_handler, + .user_ctx = NULL}; + +httpd_uri_t _ota_page_ws = { + .uri = NULL, + .method = HTTP_POST, + .handler = _ota_ws_page_handler, + .user_ctx = NULL}; + +esp_err_t zh_ota_server_init(httpd_handle_t server, const char *path) +{ + ZH_LOGI("OTA server initialization started."); + ZH_ERROR_CHECK(server != NULL && path != NULL, ESP_ERR_INVALID_ARG, NULL, "OTA server initialization failed. Invalid argument."); + _ota_path = (char *)heap_caps_calloc(1, strlen(path), MALLOC_CAP_8BIT); + strcpy(_ota_path, path); + _ota_page.uri = _ota_path; + _ota_page_ws.uri = _ota_ws_path; + esp_err_t err = httpd_register_uri_handler(server, &_ota_page); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "OTA server initialization failed. Register uri handler failed."); + err = httpd_register_uri_handler(server, &_ota_page_ws); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "OTA server initialization failed. Register uri handler failed."); + ZH_LOGI("OTA server initialization completed successfully."); + return ESP_OK; +} + +esp_err_t _ota_page_handler(httpd_req_t *req) +{ + esp_err_t err = httpd_resp_send(req, (const char *)zh_ota_server_html_start, zh_ota_server_html_end - zh_ota_server_html_start); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "HTTP server internal error."); + return ESP_OK; +} + +esp_err_t _ota_ws_page_handler(httpd_req_t *req) // -V2008 +{ + char buf[1000] = {0}; + esp_ota_handle_t ota_handle = {0}; + int remaining = req->content_len; + const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL); + ZH_ERROR_CHECK(ota_partition != NULL, ESP_FAIL, NULL, "OTA get next update partition failed."); + esp_err_t err = esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "OTA begin failed."); + while (remaining > 0) + { + int recv_len = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf))); + if (recv_len == HTTPD_SOCK_ERR_TIMEOUT) + { + continue; + } + else if (recv_len <= 0) + { + err = httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Protocol Error"); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "HTTP server internal error."); + return ESP_FAIL; + } + err = esp_ota_write(ota_handle, (const void *)buf, recv_len); + ZH_ERROR_CHECK(err == ESP_OK, err, err = httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Flash error."); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "HTTP server internal error."), "OTA write failed."); + remaining -= recv_len; + } + err = esp_ota_end(ota_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, err = httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error."); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "HTTP server internal error."), "OTA end failed."); + err = esp_ota_set_boot_partition(ota_partition); + ZH_ERROR_CHECK(err == ESP_OK, err, err = httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error."); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "HTTP server internal error."), "OTA set boot partition failed."); + err = httpd_resp_sendstr(req, "Firmware update complete, rebooting now!\n"); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "HTTP server internal error."); + vTaskDelay(500 / portTICK_PERIOD_MS); + esp_restart(); + return ESP_OK; +} \ No newline at end of file diff --git a/zh_ota_server.html b/zh_ota_server.html new file mode 100644 index 0000000..bf82512 --- /dev/null +++ b/zh_ota_server.html @@ -0,0 +1,79 @@ + + + +
+ + + +