mirror of
https://github.com/ok-home/ota_ws_update.git
synced 2025-11-13 22:03:27 +03:00
ota
This commit is contained in:
96
source/ota_esp.c
Normal file
96
source/ota_esp.c
Normal 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
138
source/ota_ws.c
Normal 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
244
source/ota_ws.html
Normal 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>
|
||||
Reference in New Issue
Block a user