maintenance mode (experimental)

This commit is contained in:
cyberman54 2021-03-04 21:22:49 +01:00
parent ad2c889889
commit b1e08f0269
14 changed files with 186 additions and 138 deletions

17
include/boot.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef BOOT_H
#define BOOT_H
#include "globals.h"
#include "hash.h"
#include <Update.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
void IRAM_ATTR exit_boot_menu(void);
void start_boot_menu(void);
#endif // BOOT_H

View File

@ -4,6 +4,6 @@
#include <Arduino.h>
#include <RokkitHash.h>
uint32_t IRAM_ATTR hash(const char *data, int len);
uint32_t IRAM_ATTR myhash(const char *data, int len);
#endif

View File

@ -20,5 +20,6 @@
#include "lorawan.h"
#include "timekeeper.h"
#include "corona.h"
#include "boot.h"
#endif

View File

@ -3,6 +3,7 @@
#include "globals.h"
#include "rcommand.h"
#include "hash.h"
#include <MQTT.h>
#include <ETH.h>
#include <mbedtls/base64.h>

View File

@ -13,13 +13,8 @@
#include <WiFiClientSecure.h>
#include <BintrayClient.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
int do_ota_update();
void start_ota_update();
void start_maintenance();
void ota_display(const uint8_t row, const std::string status,
const std::string msg);
void show_progress(unsigned long current, unsigned long size);

145
src/boot.cpp Normal file
View File

@ -0,0 +1,145 @@
#include "boot.h"
#include "reset.h"
// Local logging tag
static const char TAG[] = __FILE__;
void IRAM_ATTR exit_boot_menu() {
RTC_runmode = RUNMODE_NORMAL;
esp_restart();
}
// start local web server with user interface for maintenance mode
// used for manually uploading a firmware file via wifi
void start_boot_menu(void) {
uint8_t mac[6];
char clientId[20];
// hash 6 byte MAC to 4 byte hash
esp_eth_get_mac(mac);
const uint32_t hashedmac = myhash((const char *)mac, 6);
snprintf(clientId, 20, "paxcounter_%08x", hashedmac);
const char *host = clientId;
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
hw_timer_t *timer = NULL;
timer = timerBegin(2, 80, true); // timer 2, div 80, countup
timerAttachInterrupt(timer, &exit_boot_menu, true); // attach callback
timerAlarmWrite(timer, BOOTDELAY * 1000000, false); // set time in us
timerAlarmEnable(timer); // enable interrupt
WebServer server(80);
/*
const char *serverIndex =
"<form method='POST' action='/update' "
"enctype='multipart/form-data'><input type='file' name='update'><input "
"type='submit' value='Update'></form>";
*/
const char *serverIndex =
"<script "
"src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/"
"jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' "
"id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Update'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
// Connect to WiFi network
// WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
MDNS.begin(host);
server.on("/", HTTP_GET, [&server, &serverIndex]() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverIndex);
});
// handling uploading firmware file
server.on(
"/update", HTTP_POST,
[&server]() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
},
[&server, &timer]() {
HTTPUpload &upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
timerAlarmWrite(timer, BOOTTIMEOUT * 1000000, false);
ESP_LOGI(TAG, "Update: %s\n", upload.filename.c_str());
if (!Update.begin(
UPDATE_SIZE_UNKNOWN)) { // start with max available size
ESP_LOGE(TAG, "Error: %s", Update.errorString());
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
// flashing firmware to ESP
if (Update.write(upload.buf, upload.currentSize) !=
upload.currentSize) {
ESP_LOGE(TAG, "Error: %s", Update.errorString());
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(
true)) { // true to set the size to the current progress
ESP_LOGI(TAG, "Update finished, %u bytes written",
upload.totalSize);
WiFi.disconnect(true, true);
do_reset(false); // coldstart
} else {
ESP_LOGE(TAG, "Update failed");
}
}
});
server.begin();
MDNS.addService("http", "tcp", 80);
ESP_LOGI(TAG, "WiFi connected to '%s', open http://%s in your browser",
WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
while (1) {
server.handleClient();
timerWrite(timer, 0); // reset timer (feed watchdog)
delay(1);
}
}

View File

@ -20,7 +20,7 @@ void doHousekeeping() {
// check if update or maintenance mode trigger switch was set by rcommand
if ((RTC_runmode == RUNMODE_UPDATE) || (RTC_runmode == RUNMODE_MAINTENANCE))
do_reset(true);
do_reset(true); // warmstart
// heap and task storage debugging
ESP_LOGD(TAG, "Heap: Free:%d, Min:%d, Size:%d, Alloc:%d, StackHWM:%d",

View File

@ -40,6 +40,6 @@
#undef ROKKIT_ENABLE_8BIT_OPTIMIZATIONS
#endif
uint32_t IRAM_ATTR hash(const char *data, int len) {
uint32_t IRAM_ATTR myhash(const char *data, int len) {
return rokkit(data, len);
}

View File

@ -141,7 +141,7 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) {
// hashed 4 byte MAC
// to save RAM, we use only lower 2 bytes of hash, since collisions don't
// matter in our use case
hashedmac = hash((const char *)&saltedmac, 4);
hashedmac = myhash((const char *)&saltedmac, 4);
auto newmac = macs.insert(hashedmac); // add hashed MAC, if new unique
bool added =

View File

@ -51,6 +51,7 @@ So don't do it if you do not own a digital oscilloscope.
-------------------------------------------------------------------------------
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS)
1 ppsIRQ -> pps clock irq -> 1sec
2 watchdog -> used in boot.cpp
3 MatrixDisplayIRQ -> matrix mux cycle -> 0,5ms (MATRIX_DISPLAY_SCAN_US)
@ -290,9 +291,16 @@ void setup() {
start_ota_update();
#endif
// start local webserver if maintenance trigger switch is set
#if (BOOTMENU)
// start local webserver after device powers up or on rcommand request
if ((RTC_runmode == RUNMODE_POWERCYCLE) ||
(RTC_runmode == RUNMODE_MAINTENANCE))
start_boot_menu();
#else
// start local webserver on rcommand request only
if (RTC_runmode == RUNMODE_MAINTENANCE)
start_maintenance();
start_boot_menu();
#endif
// start mac processing task
ESP_LOGI(TAG, "Starting MAC processor...");

View File

@ -48,7 +48,7 @@ int mqtt_connect(const char *my_host, const uint16_t my_port) {
// hash 6 byte MAC to 4 byte hash
esp_eth_get_mac(mac);
const uint32_t hashedmac = hash((const char *)mac, 6);
const uint32_t hashedmac = myhash((const char *)mac, 6);
snprintf(clientId, 20, "paxcounter_%08x", hashedmac);
ESP_LOGI(TAG, "MQTT name is %s", MQTT_CLIENTNAME);

View File

@ -330,125 +330,4 @@ void show_progress(unsigned long current, unsigned long size) {
#endif
}
// start local web server with user interface for maintenance mode
// used for manually uploading a firmware file via wifi
void start_maintenance(void) {
// code snippets taken from
// github.com/espressif/arduino-esp32/blob/master/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino
const char *host = "paxcounter";
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
WebServer server(80);
const char *serverIndex =
"<script "
"src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/"
"jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' "
"id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Update'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
// Connect to WiFi network
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED)
delay(500);
ESP_LOGI(TAG, "Connected to %s", ssid);
ESP_LOGI(TAG, "Open http://%s.local in your browser", host);
// use mdns for host name resolution
if (!MDNS.begin(host)) {
ESP_LOGI(TAG, "Error setting up mDNS responder!");
delay(3000);
do_reset(false);
}
server.on("/", HTTP_GET, [&server, &serverIndex]() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverIndex);
});
// handling uploading firmware file
server.on(
"/update", HTTP_POST,
[&server]() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
do_reset(false);
},
[&server]() {
HTTPUpload &upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
ESP_LOGI(TAG, "Update: %s\n", upload.filename.c_str());
if (!Update.begin(
UPDATE_SIZE_UNKNOWN)) { // start with max available size
ESP_LOGE(TAG, "Error: %s", Update.errorString());
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
// flashing firmware to ESP
if (Update.write(upload.buf, upload.currentSize) !=
upload.currentSize) {
ESP_LOGE(TAG, "Error: %s", Update.errorString());
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(
true)) { // true to set the size to the current progress
ESP_LOGI(TAG, "Update finished, %u bytes written",
upload.totalSize);
} else {
ESP_LOGE(TAG, "Update failed");
}
delay(3000);
do_reset(false);
}
});
server.begin();
MDNS.addService("http", "tcp", 80);
while (1) {
server.handleClient();
delay(1);
}
}
#endif // USE_OTA

View File

@ -32,11 +32,11 @@ void set_reset(uint8_t val[]) {
do_reset(true);
break;
case 8: // reset and start local web server for manual software update
ESP_LOGI(TAG, "Remote command: software update via user interface");
ESP_LOGI(TAG, "Remote command: reboot to maintenance mode");
RTC_runmode = RUNMODE_MAINTENANCE;
break;
case 9: // reset and ask OTA server via Wifi for automated software update
ESP_LOGI(TAG, "Remote command: software update via Wifi");
ESP_LOGI(TAG, "Remote command: reboot to ota update mode");
#if (USE_OTA)
// check power status before scheduling ota update
if (batt_sufficient())

View File

@ -14,7 +14,8 @@ RTC_DATA_ATTR struct timeval RTC_sleep_start_time;
RTC_DATA_ATTR unsigned long long RTC_millis = 0;
timeval sleep_stop_time;
const char *runmode[6] = {"powercycle", "normal", "wakeup", "update", "sleep", "maintenance"};
const char *runmode[6] = {"powercycle", "normal", "wakeup",
"update", "sleep", "maintenance"};
void do_reset(bool warmstart) {
if (warmstart) {
@ -45,7 +46,8 @@ void do_after_reset(void) {
break;
case SW_CPU_RESET: // 0x0c Software reset CPU
// keep previous runmode (could be RUNMODE_UPDATE)
// keep previous runmode
// (i.e. RUNMODE_UPDATE or RUNMODE_MAINTENANCE)
break;
case DEEPSLEEP_RESET: // 0x05 Deep Sleep reset digital core