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

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;
}

244
source/ota_ws.html Normal file
View File

@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<title>WiFi connect</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</div>
<div class="column">
<button class="btn" id="goHome">На главную</button>
</div>
<div class="cl1" style="display:none">
<label class="cl01" for="otaFile">Ota File Name</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">Ota Start</button>
</div>
<div class="column" style="display:none" id="otaReStartVisible">
<button class="btn" id="otaReStart">Ota ReStart</button>
</div>
<div id="otaProgressVisible" style="display:none">
<div class="cl1">
<progress class="cl02" id="otaPogress" max=100 value=0>
</div>
</div>
<script>
let otaData;
let otaSetchunksSize = 0;
let otaStartsegment = 0;
let otaStarted = 0;
function readOtaFile(input) {
let reader = new FileReader();
let file = input.files[0];
document.getElementById('otaFileSelect').innerHTML = "Selected file: " + file.name;
reader.readAsArrayBuffer(file);
input.value = null;
reader.onload = function () {
otaData = new Uint8Array(reader.result);
// console.log(reader.result);
// console.log(otaData.length);
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>/*WIFI обновление*/
document.getElementById("otaStartCancel").addEventListener("click", function (e) {
if (otaData.length > 0 && otaStarted == 0) {
//socket.send(JSON.stringify({ name: "otasize", msg: otaData.length }));
console.log(JSON.stringify({ name: "otasize", msg: 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;
tstReceive();
}
else {
otaStarted = 0;
receiveWsData(JSON.stringify({ name: "otaCancel", msg: "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", msg: "restart" }));
console.log(JSON.stringify({ name: "otarestartesp", msg: "restart" }));
});
//
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function tstReceive() {
receiveWsData(JSON.stringify({ name: "otaSetchunksSize", msg: 1024 }));
while (otaStartsegment + otaSetchunksSize <= otaData.length && otaStarted == 1) {
await sleep(1000);
if(otaStarted == 1){
receiveWsData(JSON.stringify({ name: "otaGetChunk", msg: otaStartsegment }));
}
otaStartsegment += otaSetchunksSize;
}
//console.log(otaStartsegment + " " + otaSetchunksSize + " " + otaData.length + " " + (otaData.length - otaStartsegment));
if (otaStarted == 1) {
receiveWsData(JSON.stringify({ name: "otaGetChunk", msg: otaStartsegment }));
receiveWsData(JSON.stringify({ name: "otaEnd", msg: "OK" }));
}
}
//
function receiveWsData(data) {
try {
let obj = JSON.parse(data);
console.log(data);
switch (obj.name) {
case "otaSetchunksSize":
otaSetchunksSize = obj.msg;
break;
case "otaGetChunk":
let otaDataSend = otaData.subarray(obj.msg, obj.msg + otaSetchunksSize);
document.getElementById("otaPogress").value = obj.msg;
document.getElementById("otaStartCancel").innerHTML = "Ota Transfer. Size = " + otaData.length + " Segment = " + obj.msg + " Click to Cancel";
console.log("sock send " + obj.msg + " " + otaDataSend.length);
//socket.send(otaDataSend);
break;
case "otaEnd":
otaStartsegment = 0;
otaStarted = 0;
document.getElementById("otaStartVisible").style.display = "none";
document.getElementById("otaStartCancel").innerHTML = "Ota Start";
document.getElementById("otaPogress").value = otaData.length;
document.getElementById("otaFileSelect").disabled = false;
document.getElementById("otaReStartVisible").style.display = "block";
document.getElementById("otaReStart").innerHTML = "Transfer Done Click to restart ESP";
document.getElementById("otaReStart").disabled = false;
break;
case "otaError":
case "otaCancel":
otaStartsegment = 0;
otaStarted = 0;
document.getElementById("otaStartVisible").style.display = "none";
document.getElementById("otaStartCancel").innerHTML = "Ota Start";
document.getElementById("otaPogress").value = otaData.length;
document.getElementById("otaFileSelect").disabled = false;
document.getElementById("otaReStartVisible").style.display = "block";
document.getElementById("otaReStart").innerHTML = "Transfer Cancel " + obj.msg;
document.getElementById("otaReStart").disabled = true;
}
}
catch
{
console.log(data + " catch");
}
};
</script>
<script> // Прием, обработка и отправка данных в WS
</script>
<script> // основной старт скрипта, открыть сокет
// создать сокет по адресу
let wsHostStr = "ws://" + 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");
};
socket.onclose = function (event) {
console.log("close");
};
socket.onerror = function () {
console.log("error");
};
socket.onmessage = function (event) {
receiveWsData(event.data);
};
</script>
</body>
</html>