intermediate: OTA update
This commit is contained in:
parent
8b9fabe9e4
commit
428f55e87c
|
@ -9,6 +9,7 @@ class UpdateParam{
|
||||||
public:
|
public:
|
||||||
String error;
|
String error;
|
||||||
size_t uplodaded=0;
|
size_t uplodaded=0;
|
||||||
|
bool otherUpdate=false;
|
||||||
bool hasError(){
|
bool hasError(){
|
||||||
return ! error.isEmpty();
|
return ! error.isEmpty();
|
||||||
}
|
}
|
||||||
|
@ -23,12 +24,12 @@ bool GwUpdate::delayedRestart(){
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
},"reset",2000,logger,0,NULL) == pdPASS;
|
},"reset",2000,logger,0,NULL) == pdPASS;
|
||||||
}
|
}
|
||||||
GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker checker)
|
GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker ckr)
|
||||||
{
|
{
|
||||||
this->checker=checker;
|
this->checker=ckr;
|
||||||
this->logger = log;
|
this->logger = log;
|
||||||
this->server = webserver->getServer();
|
this->server = webserver->getServer();
|
||||||
server->on("/update", HTTP_POST, [&](AsyncWebServerRequest *request) {
|
server->on("/api/update", HTTP_POST, [&](AsyncWebServerRequest *request) {
|
||||||
// the request handler is triggered after the upload has finished...
|
// the request handler is triggered after the upload has finished...
|
||||||
// create the response, add header, and send response
|
// create the response, add header, and send response
|
||||||
String result="{\"status\":\"OK\"}";
|
String result="{\"status\":\"OK\"}";
|
||||||
|
@ -50,11 +51,13 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker checker)
|
||||||
if (!updateOk){
|
if (!updateOk){
|
||||||
result=jsonError(String("Update failed: ")+param->error);
|
result=jsonError(String("Update failed: ")+param->error);
|
||||||
}
|
}
|
||||||
|
if (! param->otherUpdate) {
|
||||||
|
updateRunning=0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, "application/json",result);
|
AsyncWebServerResponse *response = request->beginResponse(200, "application/json",result);
|
||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
updateRunning=false;
|
|
||||||
if (updateOk) delayedRestart();
|
if (updateOk) delayedRestart();
|
||||||
}, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
}, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||||
//Upload handler chunks in data
|
//Upload handler chunks in data
|
||||||
|
@ -67,29 +70,37 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker checker)
|
||||||
param=new UpdateParam();
|
param=new UpdateParam();
|
||||||
request->_tempObject=param;
|
request->_tempObject=param;
|
||||||
}
|
}
|
||||||
if (updateRunning){
|
if (updateRunning > 0 && (updateRunning + 60000) >= millis()){
|
||||||
param->error="another update is running";
|
param->error="another update is running";
|
||||||
|
param->otherUpdate=true;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
updateRunning=true;
|
updateRunning=millis();
|
||||||
}
|
}
|
||||||
if (!param->hasError())
|
if (!param->hasError())
|
||||||
{
|
{
|
||||||
if (!request->hasParam("_hash", true))
|
AsyncWebParameter *hash=request->getParam("_hash");
|
||||||
|
if (! hash){
|
||||||
|
hash=request->getParam("_hash",true);
|
||||||
|
}
|
||||||
|
if (!hash)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::ERROR, "missing _hash in update");
|
LOG_DEBUG(GwLog::ERROR, "missing _hash in update");
|
||||||
param->error = "missing _hash";
|
param->error = "missing _hash";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!checker(request->getParam("_hash")->value()))
|
String shash=hash->value();
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"checking hash %s",shash.c_str());
|
||||||
|
if (!checker(shash))
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::ERROR, "invalid _hash in update");
|
LOG_DEBUG(GwLog::ERROR, "invalid _hash in update %s",shash.c_str());
|
||||||
param->error = "invalid password";
|
param->error = "invalid password";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (! param->hasError()){
|
if (! param->hasError()){
|
||||||
|
Update.abort(); //abort any dangling update
|
||||||
int cmd=U_FLASH;
|
int cmd=U_FLASH;
|
||||||
if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) { // Start with max available size
|
if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) { // Start with max available size
|
||||||
LOG_DEBUG(GwLog::ERROR,"unable to start update %s",Update.errorString());
|
LOG_DEBUG(GwLog::ERROR,"unable to start update %s",Update.errorString());
|
||||||
|
@ -107,6 +118,7 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker checker)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::ERROR, "invalid write, expected %d got %d", (int)len, (int)wr);
|
LOG_DEBUG(GwLog::ERROR, "invalid write, expected %d got %d", (int)len, (int)wr);
|
||||||
param->error="unable to write";
|
param->error="unable to write";
|
||||||
|
Update.abort();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
param->uplodaded+=wr;
|
param->uplodaded+=wr;
|
||||||
|
@ -115,10 +127,12 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker checker)
|
||||||
|
|
||||||
if (final && ! param->hasError())
|
if (final && ! param->hasError())
|
||||||
{ // if the final flag is set then this is the last frame of data
|
{ // if the final flag is set then this is the last frame of data
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"finalizing update");
|
||||||
if (!Update.end(true))
|
if (!Update.end(true))
|
||||||
{ //true to set the size to the current progress
|
{ //true to set the size to the current progress
|
||||||
LOG_DEBUG(GwLog::ERROR, "unable to end update %s", Update.errorString());
|
LOG_DEBUG(GwLog::ERROR, "unable to end update %s", Update.errorString());
|
||||||
param->error=String("unable to end update:") + String(Update.errorString());
|
param->error=String("unable to end update:") + String(Update.errorString());
|
||||||
|
Update.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -7,7 +7,7 @@ class GwUpdate{
|
||||||
AsyncWebServer *server;
|
AsyncWebServer *server;
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
PasswordChecker checker;
|
PasswordChecker checker;
|
||||||
bool updateRunning=false;
|
unsigned long updateRunning=0;
|
||||||
public:
|
public:
|
||||||
bool delayedRestart();
|
bool delayedRestart();
|
||||||
GwUpdate(GwLog *log,GwWebServer *webserver, PasswordChecker checker);
|
GwUpdate(GwLog *log,GwWebServer *webserver, PasswordChecker checker);
|
||||||
|
|
|
@ -169,7 +169,7 @@ bool checkPass(String hash){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GwUpdate updater(&logger,&webserver,checkPass);
|
GwUpdate updater(&logger,&webserver,&checkPass);
|
||||||
|
|
||||||
void updateNMEACounter(int id,const char *msg,bool incoming,bool fail=false){
|
void updateNMEACounter(int id,const char *msg,bool incoming,bool fail=false){
|
||||||
//we rely on the msg being long enough
|
//we rely on the msg being long enough
|
||||||
|
|
|
@ -5,31 +5,57 @@ import sys
|
||||||
import http
|
import http
|
||||||
import http.server
|
import http.server
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
import posixpath
|
import posixpath
|
||||||
import os
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_proxy(self):
|
||||||
p=self.path
|
p=self.path
|
||||||
print("path=%s"%p)
|
print("path=%s"%p)
|
||||||
if p.startswith("/api/"):
|
if p.startswith("/api/"):
|
||||||
apiurl=self.server.proxyUrl
|
apiurl=self.server.proxyUrl
|
||||||
url=apiurl+p.replace("/api","")
|
url=apiurl+p.replace("/api","")
|
||||||
|
hostname=urllib.parse.urlparse(url).netloc
|
||||||
print("proxy to %s"%url)
|
print("proxy to %s"%url)
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(url,timeout=10) as response:
|
body = None
|
||||||
|
if self.headers.get('content-length') is not None:
|
||||||
|
content_len = int(self.headers.get('content-length'))
|
||||||
|
print("reading %d bytes body"%content_len)
|
||||||
|
body = self.rfile.read(content_len)
|
||||||
|
|
||||||
|
# set new headers
|
||||||
|
new_headers = {}
|
||||||
|
for item in self.headers.items():
|
||||||
|
new_headers[item[0]] = item[1]
|
||||||
|
new_headers['host'] = hostname
|
||||||
|
try:
|
||||||
|
del new_headers['accept-encoding']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
req=urllib.request.Request(url,headers=new_headers,data=body)
|
||||||
|
with urllib.request.urlopen(req,timeout=50) as response:
|
||||||
self.send_response(http.HTTPStatus.OK)
|
self.send_response(http.HTTPStatus.OK)
|
||||||
self.send_header("Content-type", response.getheader("Content-type"))
|
for h in response.getheaders():
|
||||||
|
self.send_header(h[0],h[1])
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
shutil.copyfileobj(response,self.wfile)
|
shutil.copyfileobj(response,self.wfile)
|
||||||
|
return True
|
||||||
return None
|
return None
|
||||||
self.send_error(http.HTTPStatus.NOT_FOUND, "api not found")
|
self.send_error(http.HTTPStatus.NOT_FOUND, "api not found")
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print("Exception: %s"%traceback.format_exc())
|
||||||
self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, "api error %s"%str(e))
|
self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, "api error %s"%str(e))
|
||||||
return None
|
return None
|
||||||
super().do_GET()
|
def do_GET(self):
|
||||||
|
if not self.do_proxy():
|
||||||
|
super().do_GET()
|
||||||
|
def do_POST(self):
|
||||||
|
if not self.do_proxy():
|
||||||
|
super().do_POST()
|
||||||
def translate_path(self, path):
|
def translate_path(self, path):
|
||||||
"""Translate a /-separated PATH to the local filename syntax.
|
"""Translate a /-separated PATH to the local filename syntax.
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<div class="tab" data-page="configPage">Config</div>
|
<div class="tab" data-page="configPage">Config</div>
|
||||||
<div class="tab" data-page="xdrPage">XDR</div>
|
<div class="tab" data-page="xdrPage">XDR</div>
|
||||||
<div class="tab" data-page="dashboardPage">Data</div>
|
<div class="tab" data-page="dashboardPage">Data</div>
|
||||||
|
<div class="tab" data-page="updatePage">Update</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="statusPage" class="tabPage">
|
<div id="statusPage" class="tabPage">
|
||||||
<div id="statusPageContent">
|
<div id="statusPageContent">
|
||||||
|
@ -74,6 +75,12 @@
|
||||||
<div class="tabPage hidden" id="dashboardPage">
|
<div class="tabPage hidden" id="dashboardPage">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tabPage hidden" id="updatePage">
|
||||||
|
<form action="api/update" method="post" id="uploadForm">
|
||||||
|
<input type="file" name="file1" id="uploadFile">
|
||||||
|
</form>
|
||||||
|
<button id="uploadBin">Upload</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="overlayContainer hidden" id="overlayContainer">
|
<div class="overlayContainer hidden" id="overlayContainer">
|
||||||
|
|
18
web/index.js
18
web/index.js
|
@ -1445,7 +1445,23 @@ function updateDashboard(data) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function uploadBin(){
|
||||||
|
let el=document.getElementById("uploadFile");
|
||||||
|
if (! el) return;
|
||||||
|
if ( el.files.length < 1) return;
|
||||||
|
ensurePass()
|
||||||
|
.then (function(hash){
|
||||||
|
let req = new XMLHttpRequest();
|
||||||
|
req.onloadend=function(){
|
||||||
|
alert("upload complete");
|
||||||
|
}
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("file1", el.files[0]);
|
||||||
|
req.open("POST", '/api/update?_hash='+encodeURIComponent(hash));
|
||||||
|
req.send(formData);
|
||||||
|
})
|
||||||
|
.catch(function(e){});
|
||||||
|
}
|
||||||
window.setInterval(update, 1000);
|
window.setInterval(update, 1000);
|
||||||
window.setInterval(function () {
|
window.setInterval(function () {
|
||||||
let dp = document.getElementById('dashboardPage');
|
let dp = document.getElementById('dashboardPage');
|
||||||
|
|
Loading…
Reference in New Issue