This commit is contained in:
ok-home
2023-09-23 14:23:28 +07:00
parent b753de7b5a
commit 9dc7641c60
9 changed files with 800 additions and 16 deletions

View File

@@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.16)
# (Not part of the boilerplate) # (Not part of the boilerplate)
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. # This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) #set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ota) project(ota_ws)

23
include/ota_ws.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include "ota_ws_private.h"
#ifdef __cplusplus
extern "C"
{
#endif
/*
* @brief register provision handlers ( web page & ws handlers) on existing httpd server with ws support
* uri page -> CONFIG_DEFAULT_URI
* @param httpd_handle_t server -> existing server handle
* @return
* ESP_OK -> register OK
* ESP_FAIL -> register FAIL
*/
esp_err_t ota_ws_register_uri_handler(httpd_handle_t server);
#ifdef __cplusplus
}
#endif

View File

@@ -2,9 +2,9 @@
idf_component_register( idf_component_register(
SRCS SRCS
example_ota_ws.c
INCLUDE_DIRS INCLUDE_DIRS
"." "."
EMBED_FILES #EMBED_FILES
ota_ws.html
) )

View File

@@ -12,13 +12,6 @@
#include "esp_event.h" #include "esp_event.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_ota_ops.h" #include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "protocol_examples_common.h"
#include "string.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_wifi.h" #include "esp_wifi.h"
//#include <esp_log.h> //#include <esp_log.h>
@@ -26,9 +19,6 @@
#include "prv_wifi_connect.h" #include "prv_wifi_connect.h"
static const char *TAG = "ota_ws"; static const char *TAG = "ota_ws";
void example_echo_ws_server(void);
esp_err_t example_register_uri_handler(httpd_handle_t server);
#define MDNS #define MDNS
#ifdef MDNS #ifdef MDNS
#include "mdns.h" #include "mdns.h"
@@ -62,6 +52,6 @@ void app_main(void)
netbiosns_set_name("esp"); netbiosns_set_name("esp");
#endif // MDNS #endif // MDNS
prv_start_http_server(PRV_MODE_STAY_ACTIVE,example_register_uri_handler); // run server prv_start_http_server(PRV_MODE_STAY_ACTIVE,NULL); // run server
//example_echo_ws_server(); //example_echo_ws_server();
} }

530
private_include/jsmn.h Normal file
View 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 */

View File

@@ -0,0 +1,7 @@
#pragma once
#include "esp_event.h"
#include "freertos/event_groups.h"
#include <esp_log.h>
#include <esp_system.h>
#include "esp_http_server.h"

96
source/ota_esp.c Normal file
View File

@@ -0,0 +1,96 @@
#include "esp_ota_ops.h"
#include "esp_flash_partitions.h"
#include "esp_partition.h"
static const char *TAG = "ota_esp";
/*an ota data write buffer ready to write to the flash*/
//static char ota_write_data[BUFFSIZE + 1] = {0};
static const esp_partition_t *update_partition = NULL;
static bool image_header_was_checked = false;
static int binary_file_length = 0;
static esp_ota_handle_t update_handle = 0;
esp_err_t start_ota(void)
{
// return ESP_OK; // debug return
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 != running)
{
ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x",
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%08x)",
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%x",
update_partition->subtype, update_partition->address);
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK)
{
ESP_LOGI(TAG, "esp_ota_begin failed ");
return -1;
}
ESP_LOGI(TAG, "esp_ota_begin succeeded");
image_header_was_checked = false;
return ESP_OK;
}
esp_err_t write_ota(int data_read, uint8_t *ota_write_data)
{
// return data_read; // 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 -1;
}
}
esp_err_t err = esp_ota_write(update_handle, (const void *)ota_write_data, data_read);
if (err != ESP_OK)
{
return -1;
}
binary_file_length += data_read;
ESP_LOGD(TAG, "Written image length %d", binary_file_length);
return ESP_OK;
}
esp_err_t end_ota(void)
{
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 -1;
}
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 -1;
}
return ESP_OK;
}

138
source/ota_ws.c Normal file
View File

@@ -0,0 +1,138 @@
#include "ota_ws_private.h"
#include "ota_ws.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "jsmn.h"
#define OTA_DEFAULT_WS_URI "/ws"
#define OTA_DEFAULT_URI "/"
static const char *TAG = "ota_ws";
// 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;
}
static void 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);
httpd_ws_send_frame(req, &ws_pkt);
}
// write wifi data from ws to nvs
static esp_err_t ota_ws_handler(httpd_req_t *req)
{
if (req->method == HTTP_GET)
{
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
send_nvs_data(req); // read & send initial wifi data from nvs
return ESP_OK;
}
httpd_ws_frame_t ws_pkt;
uint8_t *buf = NULL;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
// ws_pkt.type = HTTPD_WS_TYPE_TEXT;
/* 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)
{
ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
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)
{
ESP_LOGE(TAG, "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)
{
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
goto _recv_ret;
}
}
ret = ESP_OK;
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT)
{
// json data
}
else if (ws_pkt.type == HTTPD_WS_TYPE_BIN)
{
// ota data
}
else
{
ESP_LOGE(TAG, "httpd_ws_recv_frame unknown frame type %d", ws_pkt.type);
ret = ESP_FAIL;
goto _recv_ret;
}
_recv_ret:
free(buf);
return ret;
}
static esp_err_t ota_get_handler(httpd_req_t *req)
{
extern const unsigned char ota_ws_html_start[] asm("_binary_ota_ws_html_start");
extern const unsigned char ota_ws_html_end[] asm("_binary_ota_ws_html_end");
const size_t ota_ws_html_size = (ota_ws_html_end - ota_ws_html_start);
httpd_resp_send_chunk(req, (const char *)ota_ws_html_start, ota_ws_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};
esp_err_t ota_ws_register_uri_handler(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;
}