wip:
This commit is contained in:
@@ -1 +1 @@
|
|||||||
idf_component_register(SRCS "main.c" INCLUDE_DIRS "include")
|
idf_component_register(SRCS "zh_ota_server.c" INCLUDE_DIRS "include" REQUIRES app_update esp_http_server EMBED_FILES "zh_ota_server.html")
|
||||||
43
Kconfig.projbuild
Normal file
43
Kconfig.projbuild
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
menu "OTA websocket update"
|
||||||
|
|
||||||
|
config OTA_DEFAULT_URI
|
||||||
|
string "OTA page URI"
|
||||||
|
default "/ota"
|
||||||
|
help
|
||||||
|
WEB page URI to OTA update.
|
||||||
|
|
||||||
|
config OTA_DEFAULT_WS_URI
|
||||||
|
string "OTA ws URI"
|
||||||
|
default "/ota/ws"
|
||||||
|
help
|
||||||
|
WEB ws URI to OTA update.
|
||||||
|
|
||||||
|
config OTA_CHUNK_SIZE
|
||||||
|
int "Ota chunk size"
|
||||||
|
default 8192
|
||||||
|
help
|
||||||
|
Ota download chunk size.
|
||||||
|
|
||||||
|
config OTA_PRE_ENCRYPTED_MODE
|
||||||
|
bool "Ota pre-encrypted mode"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Ota pre-encrypted mode.
|
||||||
|
|
||||||
|
choice OTA_PRE_ENCRYPTED_RSA_KEY_LOCATION
|
||||||
|
depends on OTA_PRE_ENCRYPTED_MODE
|
||||||
|
prompt "RSA key directory"
|
||||||
|
default OTA_PRE_ENCRYPTED_RSA_KEY_ON_COMPONENT_LOCATION
|
||||||
|
config OTA_PRE_ENCRYPTED_RSA_KEY_ON_PROJECT_LOCATION
|
||||||
|
bool "PROJECT_DIR"
|
||||||
|
config OTA_PRE_ENCRYPTED_RSA_KEY_ON_COMPONENT_LOCATION
|
||||||
|
bool "COMPONENT_DIR"
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
config OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY
|
||||||
|
depends on OTA_PRE_ENCRYPTED_MODE
|
||||||
|
string "Ota pre-encrypted RSA key directory"
|
||||||
|
default "rsa_key"
|
||||||
|
|
||||||
|
|
||||||
|
endmenu
|
||||||
38
README.md
38
README.md
@@ -1,3 +1,39 @@
|
|||||||
# esp_component_template
|
# esp_component_template
|
||||||
|
|
||||||
esp_component_template
|
esp_component_template
|
||||||
|
|
||||||
|
#include "zh_ota_server.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
#define WIFI_SSID "ZH_OTA_TEST"
|
||||||
|
#define WIFI_PASS "zh_ota_test"
|
||||||
|
#define WIFI_CHANNEL 1
|
||||||
|
#define MAX_STA_CONNECTION 4
|
||||||
|
|
||||||
|
static httpd_handle_t ota_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(&ota_server_handle, &config);
|
||||||
|
zh_ota_server_init(ota_server_handle);
|
||||||
|
}
|
||||||
530
include/jsmn.h
Normal file
530
include/jsmn.h
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2010 Serge Zaitsev
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifndef JSMN_H
|
||||||
|
#define JSMN_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define JSMN_STATIC
|
||||||
|
|
||||||
|
#ifdef JSMN_STATIC
|
||||||
|
#define JSMN_API static
|
||||||
|
#else
|
||||||
|
#define JSMN_API extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON type identifier. Basic types are:
|
||||||
|
* o Object
|
||||||
|
* o Array
|
||||||
|
* o String
|
||||||
|
* o Other primitive: number, boolean (true/false) or null
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
JSMN_UNDEFINED = 0,
|
||||||
|
JSMN_OBJECT = 1,
|
||||||
|
JSMN_ARRAY = 2,
|
||||||
|
JSMN_STRING = 3,
|
||||||
|
JSMN_PRIMITIVE = 4
|
||||||
|
} jsmntype_t;
|
||||||
|
|
||||||
|
enum jsmnerr
|
||||||
|
{
|
||||||
|
/* Not enough tokens were provided */
|
||||||
|
JSMN_ERROR_NOMEM = -1,
|
||||||
|
/* Invalid character inside JSON string */
|
||||||
|
JSMN_ERROR_INVAL = -2,
|
||||||
|
/* The string is not a full JSON packet, more bytes expected */
|
||||||
|
JSMN_ERROR_PART = -3
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON token description.
|
||||||
|
* type type (object, array, string etc.)
|
||||||
|
* start start position in JSON data string
|
||||||
|
* end end position in JSON data string
|
||||||
|
*/
|
||||||
|
typedef struct jsmntok
|
||||||
|
{
|
||||||
|
jsmntype_t type;
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
int size;
|
||||||
|
#ifdef JSMN_PARENT_LINKS
|
||||||
|
int parent;
|
||||||
|
#endif
|
||||||
|
} jsmntok_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON parser. Contains an array of token blocks available. Also stores
|
||||||
|
* the string being parsed now and current position in that string.
|
||||||
|
*/
|
||||||
|
typedef struct jsmn_parser
|
||||||
|
{
|
||||||
|
unsigned int pos; /* offset in the JSON string */
|
||||||
|
unsigned int toknext; /* next token to allocate */
|
||||||
|
int toksuper; /* superior token node, e.g. parent object or array */
|
||||||
|
} jsmn_parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create JSON parser over an array of tokens
|
||||||
|
*/
|
||||||
|
JSMN_API void jsmn_init(jsmn_parser *parser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run JSON parser. It parses a JSON data string into and array of tokens, each
|
||||||
|
* describing
|
||||||
|
* a single JSON object.
|
||||||
|
*/
|
||||||
|
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
|
||||||
|
jsmntok_t *tokens, const unsigned int num_tokens);
|
||||||
|
|
||||||
|
#ifndef JSMN_HEADER
|
||||||
|
/**
|
||||||
|
* Allocates a fresh unused token from the token pool.
|
||||||
|
*/
|
||||||
|
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
|
||||||
|
const size_t num_tokens)
|
||||||
|
{
|
||||||
|
jsmntok_t *tok;
|
||||||
|
if (parser->toknext >= num_tokens)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
tok = &tokens[parser->toknext++];
|
||||||
|
tok->start = tok->end = -1;
|
||||||
|
tok->size = 0;
|
||||||
|
#ifdef JSMN_PARENT_LINKS
|
||||||
|
tok->parent = -1;
|
||||||
|
#endif
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills token type and boundaries.
|
||||||
|
*/
|
||||||
|
static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
|
||||||
|
const int start, const int end)
|
||||||
|
{
|
||||||
|
token->type = type;
|
||||||
|
token->start = start;
|
||||||
|
token->end = end;
|
||||||
|
token->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills next available token with JSON primitive.
|
||||||
|
*/
|
||||||
|
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
|
||||||
|
const size_t len, jsmntok_t *tokens,
|
||||||
|
const size_t num_tokens)
|
||||||
|
{
|
||||||
|
jsmntok_t *token;
|
||||||
|
int start;
|
||||||
|
|
||||||
|
start = parser->pos;
|
||||||
|
|
||||||
|
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
|
||||||
|
{
|
||||||
|
switch (js[parser->pos])
|
||||||
|
{
|
||||||
|
#ifndef JSMN_STRICT
|
||||||
|
/* In strict mode primitive must be followed by "," or "}" or "]" */
|
||||||
|
case ':':
|
||||||
|
#endif
|
||||||
|
case '\t':
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
case ' ':
|
||||||
|
case ',':
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
goto found;
|
||||||
|
default:
|
||||||
|
/* to quiet a warning from gcc*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (js[parser->pos] < 32 || js[parser->pos] >= 127)
|
||||||
|
{
|
||||||
|
parser->pos = start;
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef JSMN_STRICT
|
||||||
|
/* In strict mode primitive must be followed by a comma/object/array */
|
||||||
|
parser->pos = start;
|
||||||
|
return JSMN_ERROR_PART;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
found:
|
||||||
|
if (tokens == NULL)
|
||||||
|
{
|
||||||
|
parser->pos--;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||||
|
if (token == NULL)
|
||||||
|
{
|
||||||
|
parser->pos = start;
|
||||||
|
return JSMN_ERROR_NOMEM;
|
||||||
|
}
|
||||||
|
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
|
||||||
|
#ifdef JSMN_PARENT_LINKS
|
||||||
|
token->parent = parser->toksuper;
|
||||||
|
#endif
|
||||||
|
parser->pos--;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills next token with JSON string.
|
||||||
|
*/
|
||||||
|
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
|
||||||
|
const size_t len, jsmntok_t *tokens,
|
||||||
|
const size_t num_tokens)
|
||||||
|
{
|
||||||
|
jsmntok_t *token;
|
||||||
|
|
||||||
|
int start = parser->pos;
|
||||||
|
|
||||||
|
parser->pos++;
|
||||||
|
|
||||||
|
/* Skip starting quote */
|
||||||
|
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
|
||||||
|
{
|
||||||
|
char c = js[parser->pos];
|
||||||
|
|
||||||
|
/* Quote: end of string */
|
||||||
|
if (c == '\"')
|
||||||
|
{
|
||||||
|
if (tokens == NULL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||||
|
if (token == NULL)
|
||||||
|
{
|
||||||
|
parser->pos = start;
|
||||||
|
return JSMN_ERROR_NOMEM;
|
||||||
|
}
|
||||||
|
jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
|
||||||
|
#ifdef JSMN_PARENT_LINKS
|
||||||
|
token->parent = parser->toksuper;
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Backslash: Quoted symbol expected */
|
||||||
|
if (c == '\\' && parser->pos + 1 < len)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
parser->pos++;
|
||||||
|
switch (js[parser->pos])
|
||||||
|
{
|
||||||
|
/* Allowed escaped symbols */
|
||||||
|
case '\"':
|
||||||
|
case '/':
|
||||||
|
case '\\':
|
||||||
|
case 'b':
|
||||||
|
case 'f':
|
||||||
|
case 'r':
|
||||||
|
case 'n':
|
||||||
|
case 't':
|
||||||
|
break;
|
||||||
|
/* Allows escaped symbol \uXXXX */
|
||||||
|
case 'u':
|
||||||
|
parser->pos++;
|
||||||
|
for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
|
||||||
|
i++)
|
||||||
|
{
|
||||||
|
/* If it isn't a hex character we have an error */
|
||||||
|
if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
|
||||||
|
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
|
||||||
|
(js[parser->pos] >= 97 && js[parser->pos] <= 102)))
|
||||||
|
{ /* a-f */
|
||||||
|
parser->pos = start;
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
parser->pos++;
|
||||||
|
}
|
||||||
|
parser->pos--;
|
||||||
|
break;
|
||||||
|
/* Unexpected symbol */
|
||||||
|
default:
|
||||||
|
parser->pos = start;
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parser->pos = start;
|
||||||
|
return JSMN_ERROR_PART;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse JSON string and fill tokens.
|
||||||
|
*/
|
||||||
|
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
|
||||||
|
jsmntok_t *tokens, const unsigned int num_tokens)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
int i;
|
||||||
|
jsmntok_t *token;
|
||||||
|
int count = parser->toknext;
|
||||||
|
|
||||||
|
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
|
||||||
|
{
|
||||||
|
char c;
|
||||||
|
jsmntype_t type;
|
||||||
|
|
||||||
|
c = js[parser->pos];
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
count++;
|
||||||
|
if (tokens == NULL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||||
|
if (token == NULL)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_NOMEM;
|
||||||
|
}
|
||||||
|
if (parser->toksuper != -1)
|
||||||
|
{
|
||||||
|
jsmntok_t *t = &tokens[parser->toksuper];
|
||||||
|
#ifdef JSMN_STRICT
|
||||||
|
/* In strict mode an object or array can't become a key */
|
||||||
|
if (t->type == JSMN_OBJECT)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
t->size++;
|
||||||
|
#ifdef JSMN_PARENT_LINKS
|
||||||
|
token->parent = parser->toksuper;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
|
||||||
|
token->start = parser->pos;
|
||||||
|
parser->toksuper = parser->toknext - 1;
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (tokens == NULL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
|
||||||
|
#ifdef JSMN_PARENT_LINKS
|
||||||
|
if (parser->toknext < 1)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
token = &tokens[parser->toknext - 1];
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (token->start != -1 && token->end == -1)
|
||||||
|
{
|
||||||
|
if (token->type != type)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
token->end = parser->pos + 1;
|
||||||
|
parser->toksuper = token->parent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (token->parent == -1)
|
||||||
|
{
|
||||||
|
if (token->type != type || parser->toksuper == -1)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
token = &tokens[token->parent];
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
for (i = parser->toknext - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
token = &tokens[i];
|
||||||
|
if (token->start != -1 && token->end == -1)
|
||||||
|
{
|
||||||
|
if (token->type != type)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
parser->toksuper = -1;
|
||||||
|
token->end = parser->pos + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Error if unmatched closing bracket */
|
||||||
|
if (i == -1)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
for (; i >= 0; i--)
|
||||||
|
{
|
||||||
|
token = &tokens[i];
|
||||||
|
if (token->start != -1 && token->end == -1)
|
||||||
|
{
|
||||||
|
parser->toksuper = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case '\"':
|
||||||
|
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
if (parser->toksuper != -1 && tokens != NULL)
|
||||||
|
{
|
||||||
|
tokens[parser->toksuper].size++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
case ' ':
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
parser->toksuper = parser->toknext - 1;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
if (tokens != NULL && parser->toksuper != -1 &&
|
||||||
|
tokens[parser->toksuper].type != JSMN_ARRAY &&
|
||||||
|
tokens[parser->toksuper].type != JSMN_OBJECT)
|
||||||
|
{
|
||||||
|
#ifdef JSMN_PARENT_LINKS
|
||||||
|
parser->toksuper = tokens[parser->toksuper].parent;
|
||||||
|
#else
|
||||||
|
for (i = parser->toknext - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT)
|
||||||
|
{
|
||||||
|
if (tokens[i].start != -1 && tokens[i].end == -1)
|
||||||
|
{
|
||||||
|
parser->toksuper = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#ifdef JSMN_STRICT
|
||||||
|
/* In strict mode primitives are: numbers and booleans */
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
case 't':
|
||||||
|
case 'f':
|
||||||
|
case 'n':
|
||||||
|
/* And they must not be keys of the object */
|
||||||
|
if (tokens != NULL && parser->toksuper != -1)
|
||||||
|
{
|
||||||
|
const jsmntok_t *t = &tokens[parser->toksuper];
|
||||||
|
if (t->type == JSMN_OBJECT ||
|
||||||
|
(t->type == JSMN_STRING && t->size != 0))
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/* In non-strict mode every unquoted value is a primitive */
|
||||||
|
default:
|
||||||
|
#endif
|
||||||
|
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
if (parser->toksuper != -1 && tokens != NULL)
|
||||||
|
{
|
||||||
|
tokens[parser->toksuper].size++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
#ifdef JSMN_STRICT
|
||||||
|
/* Unexpected char in strict mode */
|
||||||
|
default:
|
||||||
|
return JSMN_ERROR_INVAL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens != NULL)
|
||||||
|
{
|
||||||
|
for (i = parser->toknext - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
/* Unmatched opened object or array */
|
||||||
|
if (tokens[i].start != -1 && tokens[i].end == -1)
|
||||||
|
{
|
||||||
|
return JSMN_ERROR_PART;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new parser based over a given buffer with an array of tokens
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
|
JSMN_API void jsmn_init(jsmn_parser *parser)
|
||||||
|
{
|
||||||
|
parser->pos = 0;
|
||||||
|
parser->toknext = 0;
|
||||||
|
parser->toksuper = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* JSMN_HEADER */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* JSMN_H */
|
||||||
29
include/zh_ota_server.h
Normal file
29
include/zh_ota_server.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_flash_partitions.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_image_format.h"
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include "esp_http_server.h"
|
||||||
|
|
||||||
|
#include "jsmn.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief register ota_ws httpd handlers ( web page & ws handlers) on existing httpd server with ws support
|
||||||
|
* uri page -> CONFIG_OTA_DEFAULT_WS_URI
|
||||||
|
* @param httpd_handle_t server -> existing server handle
|
||||||
|
* @return
|
||||||
|
* ESP_OK -> register OK
|
||||||
|
* ESP_FAIL -> register FAIL
|
||||||
|
*/
|
||||||
|
esp_err_t zh_ota_server_init(httpd_handle_t server);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
420
zh_ota_server.c
Normal file
420
zh_ota_server.c
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
/*
|
||||||
|
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, this
|
||||||
|
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
*/
|
||||||
|
// #include "ota_ws_update_private.h"
|
||||||
|
#include "zh_ota_server.h"
|
||||||
|
// #include "esp_ota_ops.h"
|
||||||
|
// #include "esp_flash_partitions.h"
|
||||||
|
// #include "esp_partition.h"
|
||||||
|
// #include "esp_image_format.h"
|
||||||
|
// #include <esp_log.h>
|
||||||
|
// #include "esp_http_server.h"
|
||||||
|
|
||||||
|
// #include "jsmn.h"
|
||||||
|
|
||||||
|
#define OTA_RESTART_ESP "otaRestartEsp"
|
||||||
|
#define OTA_SIZE_START "otaSize"
|
||||||
|
#define OTA_SET_CHUNK_SIZE "otaSetChunkSize"
|
||||||
|
#define OTA_GET_CHUNK "otaGetChunk"
|
||||||
|
#define OTA_END "otaEnd"
|
||||||
|
#define OTA_ERROR "otaError"
|
||||||
|
#define OTA_CANCEL "otaCancel"
|
||||||
|
#define OTA_CHECK_ROLLBACK "otaCheckRollback"
|
||||||
|
#define OTA_PROCESS_ROLLBACK "otaProcessRollback"
|
||||||
|
|
||||||
|
|
||||||
|
esp_err_t start_ota_ws(void);
|
||||||
|
esp_err_t write_ota_ws(int data_read, uint8_t *ota_write_data);
|
||||||
|
esp_err_t end_ota_ws(void);
|
||||||
|
esp_err_t abort_ota_ws(void);
|
||||||
|
bool check_ota_ws_rollback_enable(void);
|
||||||
|
esp_err_t rollback_ota_ws(bool rollback);
|
||||||
|
|
||||||
|
#define OTA_DEFAULT_WS_URI CONFIG_OTA_DEFAULT_WS_URI
|
||||||
|
#define OTA_DEFAULT_URI CONFIG_OTA_DEFAULT_URI
|
||||||
|
#define OTA_CHUNK_SIZE (CONFIG_OTA_CHUNK_SIZE & ~0xf)
|
||||||
|
|
||||||
|
|
||||||
|
static const char *TAG = "zh_ota_server";
|
||||||
|
|
||||||
|
static int ota_size; // ota firmware size
|
||||||
|
static int ota_start_chunk; // start address of http chunk
|
||||||
|
static int ota_started; // ota download started
|
||||||
|
|
||||||
|
static esp_err_t json_to_str_parm(char *jsonstr, char *nameStr, char *valStr);
|
||||||
|
static esp_err_t send_json_string(char *str, httpd_req_t *req);
|
||||||
|
static esp_err_t ota_ws_handler(httpd_req_t *req);
|
||||||
|
static void ota_error(httpd_req_t *req, char *code, char *msg);
|
||||||
|
|
||||||
|
static const esp_partition_t *update_partition = NULL;
|
||||||
|
static bool image_header_was_checked = false;
|
||||||
|
static esp_ota_handle_t update_handle = 0;
|
||||||
|
|
||||||
|
//static int tstc=0;
|
||||||
|
|
||||||
|
esp_err_t start_ota_ws(void)
|
||||||
|
{
|
||||||
|
//return ESP_OK; // debug return
|
||||||
|
//tstc=0;
|
||||||
|
|
||||||
|
esp_err_t err;
|
||||||
|
ESP_LOGI(TAG, "Starting OTA");
|
||||||
|
|
||||||
|
const esp_partition_t *configured = esp_ota_get_boot_partition();
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
if(configured==NULL || running == NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG,"OTA data not found");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configured != running)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08lx, but running from offset 0x%08lx",
|
||||||
|
configured->address, running->address);
|
||||||
|
ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08lx)",
|
||||||
|
running->type, running->subtype, running->address);
|
||||||
|
|
||||||
|
update_partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%lx",
|
||||||
|
update_partition->subtype, update_partition->address);
|
||||||
|
|
||||||
|
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "esp_ota_begin failed ");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "esp_ota_begin succeeded");
|
||||||
|
|
||||||
|
image_header_was_checked = false;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
esp_err_t write_ota_ws(int data_read, uint8_t *ota_write_data)
|
||||||
|
{
|
||||||
|
//return ESP_OK; // debug return
|
||||||
|
|
||||||
|
|
||||||
|
if (image_header_was_checked == false) // first segment
|
||||||
|
{
|
||||||
|
esp_app_desc_t new_app_info;
|
||||||
|
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t))
|
||||||
|
{
|
||||||
|
// check current version with downloading
|
||||||
|
memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
|
||||||
|
ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
|
||||||
|
|
||||||
|
image_header_was_checked = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Received package is not fit len");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esp_err_t err = esp_ota_write(update_handle, (const void *)ota_write_data, data_read);
|
||||||
|
//tstc+=data_read;
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
//ESP_LOGI("tstc","%d",tstc);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
esp_err_t end_ota_ws(void)
|
||||||
|
{
|
||||||
|
//return ESP_OK; // debug return
|
||||||
|
|
||||||
|
esp_err_t err = esp_ota_end(update_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
|
||||||
|
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
|
||||||
|
}
|
||||||
|
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
err = esp_ota_set_boot_partition(update_partition);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
esp_err_t abort_ota_ws(void)
|
||||||
|
{
|
||||||
|
return esp_ota_abort(update_handle);
|
||||||
|
}
|
||||||
|
// false - rollback disable
|
||||||
|
// true - rollback enable
|
||||||
|
bool check_ota_ws_rollback_enable(void)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
|
||||||
|
esp_ota_img_states_t ota_state_running_part;
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
if (esp_ota_get_state_partition(running, &ota_state_running_part) == ESP_OK) {
|
||||||
|
if (ota_state_running_part == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||||
|
ESP_LOGI(TAG, "Running app has ESP_OTA_IMG_PENDING_VERIFY state");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// rollback == true - rollback
|
||||||
|
// rollback == false - app valid? confirm update -> no rollback
|
||||||
|
esp_err_t rollback_ota_ws(bool rollback)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
|
||||||
|
if(rollback == false)
|
||||||
|
{
|
||||||
|
return esp_ota_mark_app_valid_cancel_rollback(); // app valid
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return esp_ota_mark_app_invalid_rollback_and_reboot(); // app rolback & reboot
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// abort OTA, send error/cancel msg to ws
|
||||||
|
static void ota_error(httpd_req_t *req, char *code, char *msg)
|
||||||
|
{
|
||||||
|
char json_str[128];
|
||||||
|
ota_size = ota_start_chunk = ota_started = 0;
|
||||||
|
abort_ota_ws();
|
||||||
|
ESP_LOGE(TAG, "%s %s", code, msg);
|
||||||
|
snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":\"%s\"}", code, msg);
|
||||||
|
send_json_string(json_str, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple json parse -> only one parametr name/val
|
||||||
|
static esp_err_t json_to_str_parm(char *jsonstr, char *nameStr, char *valStr) // распаковать строку json в пару name/val
|
||||||
|
{
|
||||||
|
int r; // количество токенов
|
||||||
|
jsmn_parser p;
|
||||||
|
jsmntok_t t[5]; // только 2 пары параметров и obj
|
||||||
|
|
||||||
|
jsmn_init(&p);
|
||||||
|
r = jsmn_parse(&p, jsonstr, strlen(jsonstr), t, sizeof(t) / sizeof(t[0]));
|
||||||
|
if (r < 2)
|
||||||
|
{
|
||||||
|
valStr[0] = 0;
|
||||||
|
nameStr[0] = 0;
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
strncpy(nameStr, jsonstr + t[2].start, t[2].end - t[2].start);
|
||||||
|
nameStr[t[2].end - t[2].start] = 0;
|
||||||
|
if (r > 3)
|
||||||
|
{
|
||||||
|
strncpy(valStr, jsonstr + t[4].start, t[4].end - t[4].start);
|
||||||
|
valStr[t[4].end - t[4].start] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
valStr[0] = 0;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
// send string to ws
|
||||||
|
static esp_err_t send_json_string(char *str, httpd_req_t *req)
|
||||||
|
{
|
||||||
|
httpd_ws_frame_t ws_pkt;
|
||||||
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||||
|
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||||
|
ws_pkt.payload = (uint8_t *)str;
|
||||||
|
ws_pkt.len = strlen(str);
|
||||||
|
return httpd_ws_send_frame(req, &ws_pkt);
|
||||||
|
}
|
||||||
|
// main ws OTA handler
|
||||||
|
// Handshake and process OTA
|
||||||
|
static esp_err_t ota_ws_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
char json_key[64] = {0};
|
||||||
|
char json_value[64] = {0};
|
||||||
|
char json_str[128] = {0};
|
||||||
|
|
||||||
|
httpd_ws_frame_t ws_pkt;
|
||||||
|
uint8_t *buf = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
if (req->method == HTTP_GET)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
|
||||||
|
if(check_ota_ws_rollback_enable()) // check rollback enable, send cmd to enable rollback dialog on html
|
||||||
|
{
|
||||||
|
snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":\"%s\" }", OTA_CHECK_ROLLBACK, "true");
|
||||||
|
send_json_string(json_str, req);
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||||
|
// Set max_len = 0 to get the frame len
|
||||||
|
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "httpd_ws_recv_frame failed to get frame len");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (ws_pkt.len)
|
||||||
|
{
|
||||||
|
// ws_pkt.len + 1 is for NULL termination as we are expecting a string
|
||||||
|
buf = calloc(1, ws_pkt.len + 1);
|
||||||
|
if (buf == NULL)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Failed to calloc memory for buf");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
ws_pkt.payload = buf;
|
||||||
|
// Set max_len = ws_pkt.len to get the frame payload
|
||||||
|
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "httpd_ws_recv_frame failed");
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = ESP_OK;
|
||||||
|
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) // process json cmd
|
||||||
|
{
|
||||||
|
if (json_to_str_parm((char *)buf, json_key, json_value)) // decode json to key/value parm
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Error json str");
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
if (strncmp(json_key, OTA_SIZE_START, sizeof(OTA_SIZE_START)) == 0) // start ota
|
||||||
|
{
|
||||||
|
ota_size = atoi(json_value);
|
||||||
|
if (ota_size == 0)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Error ota size = 0");
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
ret = start_ota_ws();
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Error start ota");
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
ota_started = 1;
|
||||||
|
ota_start_chunk = 0;
|
||||||
|
snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":%d}", OTA_SET_CHUNK_SIZE, OTA_CHUNK_SIZE); // set download chunk
|
||||||
|
send_json_string(json_str, req);
|
||||||
|
snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":%d}", OTA_GET_CHUNK, ota_start_chunk); // cmd -> send first chunk with start addresss = 0
|
||||||
|
send_json_string(json_str, req);
|
||||||
|
}
|
||||||
|
if (strncmp(json_key, OTA_CANCEL, sizeof(OTA_CANCEL)) == 0) // cancel ota
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_CANCEL, "Cancel command");
|
||||||
|
ret = ESP_OK;
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
if (strncmp(json_key, OTA_ERROR, sizeof(OTA_ERROR)) == 0) // error ota
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Error command");
|
||||||
|
ret = ESP_OK;
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
if (strncmp(json_key, OTA_PROCESS_ROLLBACK, sizeof(OTA_PROCESS_ROLLBACK)) == 0) // process rollback &
|
||||||
|
{
|
||||||
|
if(strncmp(json_value,"true",sizeof("true")) == 0)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG,"Rollback and restart");
|
||||||
|
ret = rollback_ota_ws(true); // rollback and restart
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG,"App veryfied, fix ota update");
|
||||||
|
ret = rollback_ota_ws(false); // app veryfied
|
||||||
|
}
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
if (strncmp(json_key, OTA_RESTART_ESP, sizeof(OTA_RESTART_ESP)) == 0) // cancel ota
|
||||||
|
{
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (ws_pkt.type == HTTPD_WS_TYPE_BINARY && ota_started) // download OTA firmware with chunked part
|
||||||
|
{
|
||||||
|
|
||||||
|
if (ota_start_chunk + ws_pkt.len < ota_size) //read chuk of ota
|
||||||
|
{
|
||||||
|
ret = write_ota_ws(ws_pkt.len, buf); // write chunk of ota
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Error write ota");
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
ota_start_chunk += ws_pkt.len;
|
||||||
|
snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\": %d }", OTA_GET_CHUNK, ota_start_chunk); // cmd -> next chunk
|
||||||
|
send_json_string(json_str, req);
|
||||||
|
|
||||||
|
}
|
||||||
|
else // last chunk and end ota
|
||||||
|
{
|
||||||
|
ret = write_ota_ws(ws_pkt.len, buf); // write last chunk of ota
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Error write ota");
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
ret = end_ota_ws(); // end ota
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
ota_error(req, OTA_ERROR, "Error end ota");
|
||||||
|
goto _recv_ret;
|
||||||
|
}
|
||||||
|
ota_size = 0;
|
||||||
|
ota_start_chunk = 0;
|
||||||
|
ota_started = 0;
|
||||||
|
ESP_LOGI(TAG,"OTA END OK");
|
||||||
|
snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":\"%s\" }", OTA_END, "OK"); // send ota end cmd ( ota ok )
|
||||||
|
send_json_string(json_str, req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_recv_ret:
|
||||||
|
free(buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// main http get handler
|
||||||
|
// send http initial page and js code
|
||||||
|
static esp_err_t ota_get_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
extern const unsigned char ota_ws_update_html_start[] asm("_binary_zh_ota_server_html_start");
|
||||||
|
extern const unsigned char ota_ws_update_html_end[] asm("_binary_zh_ota_server_html_end");
|
||||||
|
const size_t ota_ws_update_html_size = (ota_ws_update_html_end - ota_ws_update_html_start);
|
||||||
|
|
||||||
|
httpd_resp_send_chunk(req, (const char *)ota_ws_update_html_start, ota_ws_update_html_size);
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static const httpd_uri_t gh = {
|
||||||
|
.uri = OTA_DEFAULT_URI,
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = ota_get_handler,
|
||||||
|
.user_ctx = NULL};
|
||||||
|
static const httpd_uri_t ws = {
|
||||||
|
.uri = OTA_DEFAULT_WS_URI,
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = ota_ws_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
.is_websocket = true};
|
||||||
|
|
||||||
|
// register all ota uri handler
|
||||||
|
esp_err_t zh_ota_server_init(httpd_handle_t server)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
ret = httpd_register_uri_handler(server, &gh);
|
||||||
|
if (ret)
|
||||||
|
goto _ret;
|
||||||
|
ret = httpd_register_uri_handler(server, &ws);
|
||||||
|
if (ret)
|
||||||
|
goto _ret;
|
||||||
|
_ret:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
247
zh_ota_server.html
Normal file
247
zh_ota_server.html
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="ru">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>OTA UPDATE</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
|
<style>
|
||||||
|
.column {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
margin: 2px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl1 {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
margin: 2px;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl01 {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl02 {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hdr {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
background-color: blue;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logstr {
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="hdr">OTA UPDATE</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<button class="btn" id="goHome">Home Page</button>
|
||||||
|
</div>
|
||||||
|
<div id="rollback" style="display:none">
|
||||||
|
<div class="column">
|
||||||
|
<button class="btn" id="otaVerifyApp">Click to confirm and commit OTA update</button>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<button class="btn" id="otaRollback">Cancel OTA. Click to rollback update and restart</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="update" style="display:block">
|
||||||
|
<div class="cl1" style="display:none">
|
||||||
|
<label class="cl01" for="otaFile">Select the new OTA firmware file</label>
|
||||||
|
<input class="cl02" type="file" id="otaFile" placeholder="select file" onchange="readOtaFile(this)">
|
||||||
|
</div>
|
||||||
|
<div class="column" style="display:block" id="otaFileSelectVisible">
|
||||||
|
<button class="btn" id="otaFileSelect" onclick="document.getElementById('otaFile').click()">File
|
||||||
|
Select</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column" style="display:none" id="otaStartVisible">
|
||||||
|
<button class="btn" id="otaStartCancel">Start OTA update</button>
|
||||||
|
</div>
|
||||||
|
<div class="column" style="display:none" id="otaReStartVisible">
|
||||||
|
<button class="btn" id="otaReStart">Reboot with new OTA firmware</button>
|
||||||
|
</div>
|
||||||
|
<div id="otaProgressVisible" style="display:none">
|
||||||
|
<div class="cl1">
|
||||||
|
<progress class="cl02" id="otaPogress" max=100 value=0>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
let otaData;
|
||||||
|
let otaSetChunkSize = 0;
|
||||||
|
let otaStartsegment = 0;
|
||||||
|
let otaStarted = 0;
|
||||||
|
|
||||||
|
function readOtaFile(input) {
|
||||||
|
let reader = new FileReader();
|
||||||
|
let file = input.files[0];
|
||||||
|
document.getElementById('otaFileSelect').innerHTML = "Selected firmware file: " + file.name;
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
input.value = null;
|
||||||
|
|
||||||
|
reader.onload = function () {
|
||||||
|
otaData = new Uint8Array(reader.result);
|
||||||
|
document.getElementById("otaStartVisible").style.display = "block";
|
||||||
|
document.getElementById("otaProgressVisible").style.display = "none";
|
||||||
|
document.getElementById("otaReStartVisible").style.display = "none";
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = function () {
|
||||||
|
console.log(reader.error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
document.getElementById("otaStartCancel").addEventListener("click", function (e) {
|
||||||
|
if (otaData.length > 0 && otaStarted == 0) {
|
||||||
|
|
||||||
|
socket.send(JSON.stringify({ name: "otaSize", value: otaData.length }));
|
||||||
|
otaStarted = 1;
|
||||||
|
this.innerHTML = "Click to Cancel";
|
||||||
|
document.getElementById("otaFileSelect").disabled = true;
|
||||||
|
document.getElementById("otaProgressVisible").style.display = "block";
|
||||||
|
document.getElementById("otaPogress").max = otaData.length;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
otaStarted = 0;
|
||||||
|
socket.send(JSON.stringify({ name: "otaCancel", value: "Cancel" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
document.getElementById("goHome").addEventListener("click", function (e) {
|
||||||
|
//onclick="window.location.href = '/'"
|
||||||
|
socket.close();
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
|
document.getElementById("otaReStart").addEventListener("click", function (e) {
|
||||||
|
socket.send(JSON.stringify({ name: "otaRestartEsp", value: "restart" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
function receiveWsData(data) {
|
||||||
|
try {
|
||||||
|
let obj = JSON.parse(data);
|
||||||
|
switch (obj.name) {
|
||||||
|
case "otaSetChunkSize":
|
||||||
|
otaSetChunkSize = obj.value;
|
||||||
|
break;
|
||||||
|
case "otaGetChunk":
|
||||||
|
let otaDataSend = otaData.subarray(obj.value, obj.value + otaSetChunkSize);
|
||||||
|
document.getElementById("otaPogress").value = obj.value;
|
||||||
|
document.getElementById("otaStartCancel").innerHTML = "Ota download. Size = " + otaData.length + " Segment = " + obj.value + " Click to Cancel";
|
||||||
|
socket.send(otaDataSend);
|
||||||
|
break;
|
||||||
|
case "otaEnd":
|
||||||
|
otaStartsegment = 0;
|
||||||
|
otaStarted = 0;
|
||||||
|
document.getElementById("otaStartVisible").style.display = "none";
|
||||||
|
document.getElementById("otaStartCancel").innerHTML = "Start OTA update";
|
||||||
|
document.getElementById("otaPogress").value = otaData.length;
|
||||||
|
document.getElementById("otaFileSelect").disabled = false;
|
||||||
|
document.getElementById("otaReStartVisible").style.display = "block";
|
||||||
|
document.getElementById("otaReStart").innerHTML = "The firmware is loaded. Click to reboot with new OTA firmware";
|
||||||
|
document.getElementById("otaReStart").disabled = false;
|
||||||
|
break;
|
||||||
|
case "otaError":
|
||||||
|
case "otaCancel":
|
||||||
|
otaStartsegment = 0;
|
||||||
|
otaStarted = 0;
|
||||||
|
document.getElementById("otaStartVisible").style.display = "none";
|
||||||
|
document.getElementById("otaStartCancel").innerHTML = "Start OTA update";
|
||||||
|
document.getElementById("otaPogress").value = otaData.length;
|
||||||
|
document.getElementById("otaFileSelect").disabled = false;
|
||||||
|
document.getElementById("otaReStartVisible").style.display = "block";
|
||||||
|
document.getElementById("otaReStart").innerHTML = "ОТА firmware download canceled " + obj.value;
|
||||||
|
document.getElementById("otaReStart").disabled = true;
|
||||||
|
break;
|
||||||
|
case "otaCheckRollback":
|
||||||
|
document.getElementById("rollback").style.display = "block";
|
||||||
|
document.getElementById("update").style.display = "none";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
console.log(data + "Error msg");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script> // rollback
|
||||||
|
document.getElementById("otaVerifyApp").addEventListener("click", function (e) {
|
||||||
|
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "false" }));
|
||||||
|
document.getElementById("rollback").style.display = "none";
|
||||||
|
document.getElementById("update").style.display = "block";
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("otaRollback").addEventListener("click", function (e) {
|
||||||
|
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "true" }));
|
||||||
|
document.getElementById("rollback").style.display = "none";
|
||||||
|
document.getElementById("update").style.display = "block";
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script> // основной старт скрипта, открыть сокет
|
||||||
|
// создать сокет по адресу
|
||||||
|
let protocol = "ws:"
|
||||||
|
if(document.location.protocol == "https:") protocol = "wss:"
|
||||||
|
let wsHostStr = protocol + "//" + document.location.host + document.location.pathname;
|
||||||
|
wsHostStr += (document.location.pathname == '/') ? "ws" : "/ws";
|
||||||
|
var socket = new WebSocket(wsHostStr);
|
||||||
|
socket.binaryType = "arraybuffer";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script> // события WS
|
||||||
|
socket.onopen = function () {
|
||||||
|
console.log("connect ws");
|
||||||
|
};
|
||||||
|
socket.onclose = function (event) {
|
||||||
|
console.log("close ws - reload");
|
||||||
|
setTimeout(() => document.location.reload(), 2000);
|
||||||
|
};
|
||||||
|
socket.onerror = function () {
|
||||||
|
console.log("error ws");
|
||||||
|
setTimeout(() => document.location.reload(), 2000);
|
||||||
|
};
|
||||||
|
socket.onmessage = function (event) {
|
||||||
|
receiveWsData(event.data);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user