update update screen to new design; ability to choose repo source for OTA

This commit is contained in:
Nic Limper
2023-09-28 11:40:29 +02:00
parent fa97daef3c
commit aa484575b8
13 changed files with 600 additions and 454 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -71,6 +71,8 @@ struct Config {
char timeZone[52];
uint8_t sleepTime1;
uint8_t sleepTime2;
String repo;
String env;
};
struct HwType {
@@ -90,7 +92,6 @@ extern Config config;
extern std::vector<tagRecord*> tagDB;
extern std::unordered_map<int, HwType> hwtype;
extern std::unordered_map<std::string, varStruct> varDB;
extern DynamicJsonDocument APconfig;
extern String tagDBtoJson(const uint8_t mac[8] = nullptr, uint8_t startPos = 0);
extern bool deleteRecord(const uint8_t mac[8]);
extern void fillNode(JsonObject& tag, const tagRecord* taginfo);

View File

@@ -7,6 +7,7 @@
#include "esp32_port.h"
#include "esp_littlefs.h"
#include "storage.h"
#include "tag_db.h"
#include "web.h"
esp_loader_error_t connect_to_target(uint32_t higher_transmission_rate) {
@@ -163,8 +164,7 @@ bool downloadAndWriteBinary(String &filename, const char *url) {
}
bool doC6flash(uint8_t doDownload) {
const char *githubUrl = "https://raw.githubusercontent.com/jjwbruijn/OpenEPaperLink/master/binaries/ESP32-C6/firmware.json";
const String githubUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6/firmware.json";
HTTPClient http;
Serial.println(githubUrl);
http.begin(githubUrl);
@@ -181,7 +181,7 @@ bool doC6flash(uint8_t doDownload) {
JsonArray jsonArray = jsonDoc.as<JsonArray>();
for (JsonObject obj : jsonArray) {
String filename = "/" + obj["filename"].as<String>();
String binaryUrl = "https://raw.githubusercontent.com/jjwbruijn/OpenEPaperLink/master/binaries/ESP32-C6" + String(filename);
String binaryUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6" + String(filename);
for (int retry = 0; retry < 10; retry++) {
if (downloadAndWriteBinary(filename, binaryUrl.c_str())) {
break;

View File

@@ -176,7 +176,6 @@ bool flasher::getInfoBlockType() {
bool flasher::findTagByMD5() {
DynamicJsonDocument doc(3000);
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
@@ -206,7 +205,6 @@ bool flasher::findTagByMD5() {
bool flasher::findTagByType(uint8_t type) {
DynamicJsonDocument doc(3000);
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
@@ -446,7 +444,6 @@ bool flasher::writeFlashFromPackOffset(fs::File *file, uint16_t length) {
bool flasher::writeFlashFromPack(String filename, uint8_t type) {
StaticJsonDocument<512> doc;
DynamicJsonDocument APconfig(512);
fs::File readfile = contentFS->open(filename, "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
@@ -505,7 +502,6 @@ bool flasher::writeBlock(uint16_t offset, uint8_t *data, uint16_t len, bool info
uint16_t getAPUpdateVersion(uint8_t type) {
StaticJsonDocument<512> doc;
DynamicJsonDocument APconfig(512);
fs::File readfile = contentFS->open("/AP_FW_Pack.bin", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {

View File

@@ -11,6 +11,9 @@
#include "storage.h"
#include "util.h"
#define STR_IMPL(x) #x
#define STR(x) STR_IMPL(x)
std::vector<tagRecord*> tagDB;
std::unordered_map<std::string, varStruct> varDB;
std::unordered_map<int, HwType> hwdata = {
@@ -309,6 +312,8 @@ void initAPconfig() {
// default wifi power 8.5 dbM
// see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.h#L111
config.wifiPower = APconfig["wifipower"] | 34;
config.repo = APconfig["repo"] | "jjwbruijn/OpenEPaperLink";
config.env = APconfig["env"] | STR(BUILD_ENV_NAME);
if (APconfig["timezone"]) {
strlcpy(config.timeZone, APconfig["timezone"], sizeof(config.timeZone));
} else {
@@ -331,6 +336,8 @@ void saveAPconfig() {
APconfig["timezone"] = config.timeZone;
APconfig["sleeptime1"] = config.sleepTime1;
APconfig["sleeptime2"] = config.sleepTime2;
APconfig["repo"] = config.repo;
APconfig["env"] = config.env;
serializeJsonPretty(APconfig, configFile);
configFile.close();
xSemaphoreGive(fsMutex);

View File

@@ -26,8 +26,6 @@
extern uint8_t data_to_send[];
// const char *http_username = "admin";
// const char *http_password = "admin";
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
WifiManager wm;
@@ -189,7 +187,6 @@ void init_web() {
wm.connectToWifi();
// server.addHandler(new SPIFFSEditor(*contentFS, http_username, http_password));
server.addHandler(new SPIFFSEditor(*contentFS));
server.addHandler(&ws);
@@ -424,48 +421,55 @@ void init_web() {
});
server.on("/save_apcfg", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("alias", true) && request->hasParam("channel", true)) {
if (request->hasParam("alias", true)) {
String aliasValue = request->getParam("alias", true)->value();
size_t aliasLength = aliasValue.length();
if (aliasLength > 31) aliasLength = 31;
aliasValue.toCharArray(config.alias, aliasLength + 1);
config.alias[aliasLength] = '\0';
config.channel = static_cast<uint8_t>(request->getParam("channel", true)->value().toInt());
if (request->hasParam("led", true)) {
config.led = static_cast<int16_t>(request->getParam("led", true)->value().toInt());
updateBrightnessFromConfig();
}
if (request->hasParam("language", true)) {
config.language = static_cast<uint8_t>(request->getParam("language", true)->value().toInt());
updateLanguageFromConfig();
}
if (request->hasParam("maxsleep", true)) {
config.maxsleep = static_cast<uint8_t>(request->getParam("maxsleep", true)->value().toInt());
}
if (request->hasParam("stopsleep", true)) {
config.stopsleep = static_cast<uint8_t>(request->getParam("stopsleep", true)->value().toInt());
}
if (request->hasParam("preview", true)) {
config.preview = static_cast<uint8_t>(request->getParam("preview", true)->value().toInt());
}
if (request->hasParam("sleeptime1", true)) {
config.sleepTime1 = static_cast<uint8_t>(request->getParam("sleeptime1", true)->value().toInt());
config.sleepTime2 = static_cast<uint8_t>(request->getParam("sleeptime2", true)->value().toInt());
}
if (request->hasParam("wifipower", true)) {
config.wifiPower = static_cast<uint8_t>(request->getParam("wifipower", true)->value().toInt());
WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower));
}
if (request->hasParam("timezone", true)) {
strncpy(config.timeZone, request->getParam("timezone", true)->value().c_str(), sizeof(config.timeZone) - 1);
config.timeZone[sizeof(config.timeZone) - 1] = '\0';
setenv("TZ", config.timeZone, 1);
tzset();
}
saveAPconfig();
setAPchannel();
}
if (request->hasParam("channel", true)) {
config.channel = static_cast<uint8_t>(request->getParam("channel", true)->value().toInt());
}
if (request->hasParam("led", true)) {
config.led = static_cast<int16_t>(request->getParam("led", true)->value().toInt());
updateBrightnessFromConfig();
}
if (request->hasParam("language", true)) {
config.language = static_cast<uint8_t>(request->getParam("language", true)->value().toInt());
updateLanguageFromConfig();
}
if (request->hasParam("maxsleep", true)) {
config.maxsleep = static_cast<uint8_t>(request->getParam("maxsleep", true)->value().toInt());
}
if (request->hasParam("stopsleep", true)) {
config.stopsleep = static_cast<uint8_t>(request->getParam("stopsleep", true)->value().toInt());
}
if (request->hasParam("preview", true)) {
config.preview = static_cast<uint8_t>(request->getParam("preview", true)->value().toInt());
}
if (request->hasParam("sleeptime1", true)) {
config.sleepTime1 = static_cast<uint8_t>(request->getParam("sleeptime1", true)->value().toInt());
config.sleepTime2 = static_cast<uint8_t>(request->getParam("sleeptime2", true)->value().toInt());
}
if (request->hasParam("wifipower", true)) {
config.wifiPower = static_cast<uint8_t>(request->getParam("wifipower", true)->value().toInt());
WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower));
}
if (request->hasParam("timezone", true)) {
strncpy(config.timeZone, request->getParam("timezone", true)->value().c_str(), sizeof(config.timeZone) - 1);
config.timeZone[sizeof(config.timeZone) - 1] = '\0';
setenv("TZ", config.timeZone, 1);
tzset();
}
if (request->hasParam("repo", true)) {
config.repo = request->getParam("repo", true)->value();
}
if (request->hasParam("env", true)) {
config.env = request->getParam("env", true)->value();
}
saveAPconfig();
setAPchannel();
request->send(200, "text/plain", "Ok, saved");
});

View File

@@ -39,376 +39,409 @@
</nav>
</header>
<form>
<div class="container">
<div class="container">
<div class="window">
<div class="window">
<div id="hometab" class="tabcontent">
<table>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#239f26" id="dashboardStatusIcon">
check_circle
</td>
<td id="dashboardStatus" style="color:#239f26" colspan="2">
initialising...
</td>
</tr>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#77239e">
<div id="hometab" class="tabcontent">
<table>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#239f26" id="dashboardStatusIcon">
check_circle
</td>
<td id="dashboardStatus" style="color:#239f26" colspan="2">
initialising...
</td>
</tr>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#77239e">
sell
</td>
<td>
tags
</td>
<td id="dashboardTagCount" style="color:#77239e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('pending')">
<td class="material-symbols-outlined" style="color:#235f9e">
hourglass_empty
</td>
<td>
pending data
</td>
<td id="dashboardPending" style="color:#235f9e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('lowbatt')">
<td class="material-symbols-outlined" style="color:#9e9223">
battery_low
</td>
<td>
low battery
</td>
<td id="dashboardLowBatt" style="color:#9e9223">
0
</td>
</tr>
<tr onclick="setFilterAndShow('inactive')">
<td class="material-symbols-outlined" style="color:#9e2323">
signal_disconnected
</td>
<td>
timeout
</td>
<td id="dashboardTimeout" style="color:#9e2323">
0
</td>
</tr>
<!--
<tr onclick="$(`[data-target='aptab']`).click()">
<td class="material-symbols-outlined" style="color:#239f26">
cell_tower
</td>
<td>
access points
</td>
<td id="dashboardApCount" style="color:#239f26">
</td>
</tr>
-->
</table>
</div>
<div id="tagtab" class="tabcontent">
<div class="tagheader">
<h3>Currently active tags</h3>
<div id="activefilter"></div><button class="material-symbols-outlined"
id="toggleFilters">filter_alt</button>
</div>
<div id="filterOptions">
<div>
<div>group by</div>
<div>
<input type="radio" name="group" value="" id="rnone" checked><label
for="rnone">None</label>
</div>
<div>
<input type="radio" name="group" value="model" id="rtagtype"><label for="rtagtype">Tag
model</label>
</div>
<div>
<input type="radio" name="group" value="contentmode" id="rcontent"><label
for="rcontent">Content</label>
</div>
<div>
<input type="radio" name="group" value="data-channel" id="rchannel"><label
for="rchannel">Channel</label>
</div>
</div>
<div>
<div>sort by</div>
<div>
<input type="radio" name="sort" value="alias" id="ralias" checked><label
for="ralias">Alias</label>
</div>
<div>
<input type="radio" name="sort" value="mac" id="rmac"><label for="rmac">Mac</label>
</div>
<div>
<input type="radio" name="sort" value="data-lastseen" id="rlastseen"><label
for="rlastseen">Last seen</label>
</div>
<div>
<input type="radio" name="sort" value="data-nextupdate" id="rnext"><label
for="rnext">Next update</label>
</div>
</div>
<div>
<div>filter</div>
<div>
<input type="checkbox" name="filter" value="local" id="rlocal"><label
for="rlocal">local</label>
</div>
<div>
<input type="checkbox" name="filter" value="remote" id="rremote"><label
for="rremote">remote</label>
</div>
<div>
<input type="checkbox" name="filter" value="inactive" id="rinactive"><label
for="rinactive">timed out</label>
</div>
<div>
<input type="checkbox" name="filter" value="pending" id="rpending"><label
for="rpending">pending</label>
</div>
<div>
<input type="checkbox" name="filter" value="lowbatt" id="rlowbatt"><label
for="rlowbatt">low battery</label>
</div>
</div>
</div>
<div id="taglist" class="taglist">
<div class="tagcard" id="tagtemplate">
<div class="currimg"><canvas class="tagimg"></div>
<div class="mac"></div>
<div class="alias"></div>
<div class="model"></div>
<div class="received"></div>
<div class="contentmode"></div>
<div class="lastseen"></div>
<div class="nextcheckin"></div>
<div class="nextupdate"></div>
<div class="corner">
<div class="pendingicon" title="A new message is waiting for the tag to pick up">
&circlearrowright;</div>
<div class="warningicon" title="This tag has not been seen for a long time">&#9888;
</div>
</div>
</div>
</div>
</div>
<div id="logtab" class="tabcontent">
<div class="tabheader">
<div><img id="clearlog" src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw==
"></div>
<div><input type="checkbox" id="showdebug" value="1"><label for="showdebug">Show all websocket
traffic</label></div>
</div>
<ul id="messages" class="messages">
</ul>
</div>
<div id="aptab" class="tabcontent">
<h3>Active access points</h3>
<div id="aplist">
<div id="apcard" class="apcard">
<p class="apip">194.109.6.66</p>
<p class="apalias">AP kitchen</p>
<div>
<span class="material-symbols-outlined">
sell
</td>
<td>
tags
</td>
<td id="dashboardTagCount" style="color:#77239e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('pending')">
<td class="material-symbols-outlined" style="color:#235f9e">
hourglass_empty
</td>
<td>
pending data
</td>
<td id="dashboardPending" style="color:#235f9e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('lowbatt')">
<td class="material-symbols-outlined" style="color:#9e9223">
battery_low
</td>
<td>
low battery
</td>
<td id="dashboardLowBatt" style="color:#9e9223">
0
</td>
</tr>
<tr onclick="setFilterAndShow('inactive')">
<td class="material-symbols-outlined" style="color:#9e2323">
signal_disconnected
</td>
<td>
timeout
</td>
<td id="dashboardTimeout" style="color:#9e2323">
0
</td>
</tr>
<!--
<tr onclick="$(`[data-target='aptab']`).click()">
<td class="material-symbols-outlined" style="color:#239f26">
</span>
<span class="aptagcount">13</span>
<span class="material-symbols-outlined space">
cell_tower
</td>
<td>
access points
</td>
<td id="dashboardApCount" style="color:#239f26">
</td>
</tr>
-->
</table>
</div>
<div id="tagtab" class="tabcontent">
<div class="tagheader">
<h3>Currently active tags</h3>
<div id="activefilter"></div><button class="material-symbols-outlined"
id="toggleFilters">filter_alt</button>
</div>
<div id="filterOptions">
<div>
<div>group by</div>
<div>
<input type="radio" name="group" value="" id="rnone" checked><label
for="rnone">None</label>
</div>
<div>
<input type="radio" name="group" value="model" id="rtagtype"><label for="rtagtype">Tag
model</label>
</div>
<div>
<input type="radio" name="group" value="contentmode" id="rcontent"><label
for="rcontent">Content</label>
</div>
<div>
<input type="radio" name="group" value="data-channel" id="rchannel"><label
for="rchannel">Channel</label>
</div>
</div>
<div>
<div>sort by</div>
<div>
<input type="radio" name="sort" value="alias" id="ralias" checked><label
for="ralias">Alias</label>
</div>
<div>
<input type="radio" name="sort" value="mac" id="rmac"><label for="rmac">Mac</label>
</div>
<div>
<input type="radio" name="sort" value="data-lastseen" id="rlastseen"><label
for="rlastseen">Last seen</label>
</div>
<div>
<input type="radio" name="sort" value="data-nextupdate" id="rnext"><label
for="rnext">Next update</label>
</div>
</div>
<div>
<div>filter</div>
<div>
<input type="checkbox" name="filter" value="local" id="rlocal"><label
for="rlocal">local</label>
</div>
<div>
<input type="checkbox" name="filter" value="remote" id="rremote"><label
for="rremote">remote</label>
</div>
<div>
<input type="checkbox" name="filter" value="inactive" id="rinactive"><label
for="rinactive">timed out</label>
</div>
<div>
<input type="checkbox" name="filter" value="pending" id="rpending"><label
for="rpending">pending</label>
</div>
<div>
<input type="checkbox" name="filter" value="lowbatt" id="rlowbatt"><label
for="rlowbatt">low battery</label>
</div>
</div>
</div>
<div id="taglist" class="taglist">
<div class="tagcard" id="tagtemplate">
<div class="currimg"><canvas class="tagimg"></div>
<div class="mac"></div>
<div class="alias"></div>
<div class="model"></div>
<div class="received"></div>
<div class="contentmode"></div>
<div class="lastseen"></div>
<div class="nextcheckin"></div>
<div class="nextupdate"></div>
<div class="corner">
<div class="pendingicon" title="A new message is waiting for the tag to pick up">
&circlearrowright;</div>
<div class="warningicon" title="This tag has not been seen for a long time">&#9888;
</div>
</div>
</span>
<span class="apchannel">25</span>
</div>
<p class="apswversion">
fetching software version...
</p>
</div>
</div>
</div>
<div id="logtab" class="tabcontent">
<div class="tabheader">
<div><img id="clearlog" src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw==
"></div>
<div><input type="checkbox" id="showdebug" value="1"><label for="showdebug">Show all websocket
traffic</label></div>
</div>
<ul id="messages" class="messages">
</ul>
</div>
<div id="templatetab" class="tabcontent">
Work in progress...
</div>
<div id="aptab" class="tabcontent">
<h3>Active access points</h3>
<div id="configtab" class="tabcontent">
<h3>Access Point config</h3>
<p>
<label for="apcfgalias">Alias</label>
<input id="apcfgalias" type="text">
</p>
<p>
<label for="apcfgchid">IEEE 802.15.4 channel</label>
<select id="apcfgchid">
<option value="0" selected>auto</option>
<option value="11">11</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
</select>
</p>
<p>
<label for="apcfgledbrightness">LED brightness</label>
<select id="apcfgledbrightness">
<option value="-1">off</option>
<option value="20">10%</option>
<option value="64">25%</option>
<option value="128" selected>50%</option>
<option value="192">75%</option>
<option value="255">100%</option>
</select>
</p>
<p>
<label for="apcfglanguage">Content language</label>
<select id="apcfglanguage">
<option value="0" selected>EN English</option>
<option value="1">NL Nederlands</option>
<option value="2">DE Deutsch</option>
</select>
</p>
<p title="Depending on the content, a tag can sleep for
longer periods when no updates are expected
(like a date display). This setting specifies
the maximum sleep time.">
<label for="apclatency">Maximum sleep</label>
<select id="apclatency">
<option value="0" selected>shortest (40 sec)</option>
<option value="5">5 minutes</option>
<option value="10">10 minute</option>
<option value="30">30 minutes</option>
<option value="60">1 hour</option>
</select>
</p>
<p title="If connected to the website, don't sleep extra.
Latency will be around 40 seconds.">
<label for="apcpreventsleep">Shorten latency during config</label>
<select id="apcpreventsleep">
<option value="0">no</option>
<option value="1" selected>yes</option>
</select>
</p>
<p
title="Stops updates at night, and put the tags to sleep. During the configured night time, this overrides the maximum sleep time.">
<label for="apcnight1">No updates between</label>
<select id="apcnight1"></select>
<span style="align-self:center;">and</span>
<select id="apcnight2"></select>
</p>
<p title="Turn off preview images on the webpage if you want to manage many tags,
to save file system space">
<label for="apcpreview">Preview images</label>
<select id="apcpreview">
<option value="1" selected>yes</option>
<option value="0">no</option>
</select>
</p>
<p title="Wifi transmit power">
<label for="apcwifipower">Wifi power</label>
<select id="apcwifipower">
<option value="78">19.5 dBm</option>
<option value="76">19.0 dBm</option>
<option value="74">18.5 dBm</option>
<option value="68">17.0 dBm</option>
<option value="60">15.0 dBm</option>
<option value="52">13.0 dBm</option>
<option value="44">11.0 dBm</option>
<option value="34" selected>8.5 dBm</option>
<option value="28">7.0 dBm</option>
<option value="20">5.0 dBm</option>
<option value="8">2.0 dBm</option>
</select>
</p>
<p title="Your local time zone">
<label for="apctimezone">Local time zone</label>
<select id="apctimezone">
<optgroup label="Europe">
<option value="CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" selected>Central European
Time</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Athens, Greece</option>
<option value="GMT+0IST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Dublin, Ireland</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Helsinki, Finland</option>
<option value="WET-0WEST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Lisbon, Portugal</option>
<option value="GMT+0BST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">London, Great Britain
</option>
<option value="EET-2EEST,M3.5.0/3,M10.5.0/4">Kyiv, Ukraine</option>
</optgroup>
<optgroup label="USA / Canada">
<option value="HAW10">Hawaii Time</option>
<option value="AKST9AKDT">Alaska Time</option>
<option value="PST8PDT">Pacific Time</option>
<option value="MST7MDT">Mountain Time</option>
<option value="MST7">Arizona, no DST</option>
<option value="CST6CDT">Central Time</option>
<option value="EST5EDT">Eastern Time</option>
</optgroup>
<optgroup label="Australia / New Zealand">
<option value="EST-10EDT-11,M10.5.0/02:00:00,M3.5.0/03:00:00">Melbourne, Sydney</option>
<option value="WST-8">Perth</option>
<option value="EST-10">Brisbane</option>
<option value="CST-9:30CDT-10:30,M10.5.0/02:00:00,M3.5.0/03:00:00">Adelaide</option>
<option value="CST-9:30">Darwin</option>
<option value="EST-10EDT-11,M10.1.0/02:00:00,M3.5.0/03:00:00">Hobart</option>
<option value="NZST-12NZDT-13,M9.4.0/02:00:00,M4.1.0/03:00:00">New Zealand</option>
</optgroup>
<optgroup label="Asia">
<option value="JST-9">Tokyo</option>
<option value="WIB-7">Jakarta</option>
<option value="GMT+2">Jerusalem</option>
<option value="SGT-8">Singapore</option>
<option value="ULAT-8ULAST,M3.5.0/2,M9.5.0/2">Ulaanbaatar, Mongolia</option>
</optgroup>
<optgroup label="Central and South America">
<option value="BRST+3BRDT+2,M10.3.0,M2.3.0">Brazil, Sao Paulo</option>
<option value="UTC+3">Argentina</option>
<option value="CST+6">Central America</option>
</optgroup>
</select>
</p>
<p>
<input type="button" value="Save" id="apcfgsave"><span id="apcfgmsg"></span>
</p>
<h3>Manage</h3>
<p>
<button type="button" id="rebootbutton">Reboot AP</button> Saves the tagDB and instantly reboots the Access
Point
</p>
<p>
<a href="/backup_db" id="downloadDBbutton">Download tagDB</a>
</p>
<p>
<button type="button" id="updatebutton" class="tablinks" data-target="updatetab" title="Update">Update</button> Manage firmware of the ESP32
</p>
<p>
<a href="/setup" target="setup" class="wifibutton">WiFi config</a> Opens a new window with WiFi
config options
</p>
<p>
<br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink" target="_new">Github
OpenEPaperLink</a><br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink/wiki" target="_new">OpenEPaperLink
Wiki</a><br>
</p>
</div>
<div id="aplist">
<div id="apcard" class="apcard">
<p class="apip">194.109.6.66</p>
<p class="apalias">AP kitchen</p>
<div>
<span class="material-symbols-outlined">
sell
<div id="updatetab" class="tabcontent">
<h3>Firmware Updates</h3>
<div>
<div class="updateCol1">
<div id="easyupdate"></div>
<h4>Repository</h4>
<div>
<label for="repo">Repo</label><input type="text" id="repo" value="">
<button id="selectRepo">Change</button><br>
<p id="repoWarning" style="display:none" class="warning">
To change to this repository, select and confirm the build environment.
</p>
<label for="environment">Environment</label><input type="text" id="environment" readonly value="">
<button id="confirmSelectRepo">Confirm</button><button id="cancelSelectRepo">Cancel</button>
</div>
<h4>Releases</h4>
<div id="releasetable"></div>
<h4>Other actions</h4>
<div>
<p>
<div id="rollbackOption" style="display:none"><button type="button" id="rollbackBtn">Rollback to previous
firmware</button></div>
</p>
<p>
<span id="c6Option">
<div id="updateC6Option"><button type="button" id="updateC6Btn">Update ESP32-C6</button> <input type="checkbox"
value="1" checked id="c6download"> download latest version</div>
</span>
<span class="aptagcount">13</span>
<span class="material-symbols-outlined space">
cell_tower
</span>
<span class="apchannel">25</span>
</div>
<p class="apswversion">
fetching software version...
</p>
</div>
</div>
<div class="console" id="updateconsole"></div>
</div>
<div id="templatetab" class="tabcontent">
Work in progress...
</div>
<div id="configtab" class="tabcontent">
<h3>Access Point config</h3>
<p>
<label for="apcfgalias">Alias</label>
<input id="apcfgalias" type="text">
</p>
<p>
<label for="apcfgchid">Channel</label>
<select id="apcfgchid">
<option value="0" selected>auto</option>
<option value="11">11</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
</select>
</p>
<p>
<label for="apcfgledbrightness">LED brightness</label>
<select id="apcfgledbrightness">
<option value="-1">off</option>
<option value="20">10%</option>
<option value="64">25%</option>
<option value="128" selected>50%</option>
<option value="192">75%</option>
<option value="255">100%</option>
</select>
</p>
<p>
<label for="apcfglanguage">Content language</label>
<select id="apcfglanguage">
<option value="0" selected>EN English</option>
<option value="1">NL Nederlands</option>
<option value="2">DE Deutsch</option>
</select>
</p>
<p title="Depending on the content, a tag can sleep for
longer periods when no updates are expected
(like a date display). This setting specifies
the maximum sleep time.">
<label for="apclatency">Maximum sleep</label>
<select id="apclatency">
<option value="0" selected>shortest (40 sec)</option>
<option value="5">5 minutes</option>
<option value="10">10 minute</option>
<option value="30">30 minutes</option>
<option value="60">1 hour</option>
</select>
</p>
<p title="If connected to the website, don't sleep extra.
Latency will be around 40 seconds.">
<label for="apcpreventsleep">Shorten latency during config</label>
<select id="apcpreventsleep">
<option value="0">no</option>
<option value="1" selected>yes</option>
</select>
</p>
<p
title="Stops updates at night, and put the tags to sleep. During the configured night time, this overrides the maximum sleep time.">
<label for="apcnight1">No updates between</label>
<select id="apcnight1"></select>
<span style="align-self:center;">and</span>
<select id="apcnight2"></select>
</p>
<p title="Turn off preview images on the webpage if you want to manage many tags,
to save file system space">
<label for="apcpreview">Preview images</label>
<select id="apcpreview">
<option value="1" selected>yes</option>
<option value="0">no</option>
</select>
</p>
<p title="Wifi transmit power">
<label for="apcwifipower">Wifi power</label>
<select id="apcwifipower">
<option value="78">19.5 dBm</option>
<option value="76">19.0 dBm</option>
<option value="74">18.5 dBm</option>
<option value="68">17.0 dBm</option>
<option value="60">15.0 dBm</option>
<option value="52">13.0 dBm</option>
<option value="44">11.0 dBm</option>
<option value="34" selected>8.5 dBm</option>
<option value="28">7.0 dBm</option>
<option value="20">5.0 dBm</option>
<option value="8">2.0 dBm</option>
</select>
</p>
<p title="Your local time zone">
<label for="apctimezone">Local time zone</label>
<select id="apctimezone">
<optgroup label="Europe">
<option value="CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" selected>Central European
Time</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Athens, Greece</option>
<option value="GMT+0IST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Dublin, Ireland</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Helsinki, Finland</option>
<option value="WET-0WEST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Lisbon, Portugal</option>
<option value="GMT+0BST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">London, Great Britain
</option>
<option value="EET-2EEST,M3.5.0/3,M10.5.0/4">Kyiv, Ukraine</option>
</optgroup>
<optgroup label="USA / Canada">
<option value="HAW10">Hawaii Time</option>
<option value="AKST9AKDT">Alaska Time</option>
<option value="PST8PDT">Pacific Time</option>
<option value="MST7MDT">Mountain Time</option>
<option value="MST7">Arizona, no DST</option>
<option value="CST6CDT">Central Time</option>
<option value="EST5EDT">Eastern Time</option>
</optgroup>
<optgroup label="Australia / New Zealand">
<option value="EST-10EDT-11,M10.5.0/02:00:00,M3.5.0/03:00:00">Melbourne, Sydney</option>
<option value="WST-8">Perth</option>
<option value="EST-10">Brisbane</option>
<option value="CST-9:30CDT-10:30,M10.5.0/02:00:00,M3.5.0/03:00:00">Adelaide</option>
<option value="CST-9:30">Darwin</option>
<option value="EST-10EDT-11,M10.1.0/02:00:00,M3.5.0/03:00:00">Hobart</option>
<option value="NZST-12NZDT-13,M9.4.0/02:00:00,M4.1.0/03:00:00">New Zealand</option>
</optgroup>
<optgroup label="Asia">
<option value="JST-9">Tokyo</option>
<option value="WIB-7">Jakarta</option>
<option value="GMT+2">Jerusalem</option>
<option value="SGT-8">Singapore</option>
<option value="ULAT-8ULAST,M3.5.0/2,M9.5.0/2">Ulaanbaatar, Mongolia</option>
</optgroup>
<optgroup label="Central and South America">
<option value="BRST+3BRDT+2,M10.3.0,M2.3.0">Brazil, Sao Paulo</option>
<option value="UTC+3">Argentina</option>
<option value="CST+6">Central America</option>
</optgroup>
</select>
</p>
<p>
<input type="button" value="Save" id="apcfgsave"><span id="apcfgmsg"></span>
</p>
<h3>Manage</h3>
<p>
<button id="rebootbutton">Reboot AP</button> Saves the tagDB and instantly reboots the Access
Point
</p>
<p>
<a href="/backup_db" id="downloadDBbutton">Download tagDB</a>
</p>
<p>
<button id="updatebutton">Update</button> Manage firmware of the ESP32
</p>
<p>
<a href="/setup" target="setup" class="wifibutton">WiFi config</a> Opens a new window with WiFi
config options
</p>
<p>
<br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink" target="_new">Github
OpenEPaperLink</a><br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink/wiki" target="_new">OpenEPaperLink
Wiki</a><br>
</p>
</div>
</div>
</div>
</form>
</div>
<footer class="logbox">
<p>
@@ -464,27 +497,6 @@
</p>
</dialog>
<div id="apupdatebox">
<div class="closebtn">&#10006;</div>
<h3>Update dashboard</h3>
<div id="easyupdate"></div>
<div id="advanceddiv">
<!--<div>
repo: <input type="text" name="repo"> <button id="switchRepo">Switch</button><br>
environment: <span id="environment">environment</span>
</div>-->
<div id="releasetable"></div>
<div>
<div id="rollbackOption" style="display:none"><button id="rollbackBtn">Rollback to previous
firmware</button></div>
<span id="c6Option">
<div id="updateC6Option"><button id="updateC6Btn">Update ESP32-C6</button> <input type="checkbox"
value="1" checked id="c6download"> download latest version</div>
</span>
</div>
</div>
</div>
<ul id="context-menu"
style="display: none; position: absolute; background: white; border: 1px solid gray; padding: 0; list-style: none;">
</ul>

View File

@@ -65,7 +65,7 @@ footer {
.logo {
margin: 0 auto;
height: 50px;
text-indent: 50px;
text-indent: 12px;
overflow: hidden;
font-size: 2.5em;
color: white;
@@ -202,7 +202,8 @@ label {
}
#aptab,
#configtab {
#configtab,
#updatetab {
padding: 10px;
& p {
@@ -210,6 +211,35 @@ label {
}
}
#updatetab {
&>div {
display: flex;
gap: 2em;
flex-flow: wrap;
}
& label {
width: 100px;
display: inline-block;
vertical-align: baseline;
padding: 7px 0px;
}
& input[type="text"] {
width: 200px;
}
& button {
margin: 0px 5px;
}
& input:read-only {
background-color: #ccc;
}
& .warning {
padding: 5px;
color: #f02000;
background-color: #ffffc0;
font-weight: bold;
}
}
#aplist {
display: flex;
gap: 1em;
@@ -719,7 +749,10 @@ ul.messages li.new {
/* updatescreens */
#easyupdate {
margin-top: 10px;
padding: 10px;
background-color: white;
width: 400px;
margin-bottom: 20px;
}
#easyupdate button {
@@ -733,13 +766,10 @@ ul.messages li.new {
text-decoration: underline;
}
#advanceddiv {
display: none;
}
#advanceddiv div:nth-child(2) {
display: flex;
gap: 10px;
h4 {
font-size: 1.25em;
margin: 10px 0px;
font-weight: bold;
}
#releasetable {
@@ -764,7 +794,7 @@ ul.messages li.new {
}
#releasetable td:nth-child(2) {
word-wrap: nowrap;
white-space: nowrap;
}
#releasetable button {
@@ -780,17 +810,21 @@ ul.messages li.new {
display: none;
}
.updateCol1 {
flex-grow: 1;
}
.console {
width: 100%;
width: 450px;
background-color: black;
font-family: 'lucida console', 'ui-monospace';
color: white;
padding: 5px 10px;
margin: 20px 0px;
padding-bottom: 25px;
height: 400px;
height: calc(100vh - 200px);
overflow-y: scroll;
white-space: break-spaces;
flex-grow: 1;
}
.console div {

View File

@@ -83,7 +83,8 @@ function initTabs() {
const tabContents = document.querySelectorAll(".tabcontent");
tabLinks.forEach(tabLink => {
tabLink.addEventListener("click", function () {
tabLink.addEventListener("click", function (event) {
event.preventDefault();
const targetId = this.getAttribute("data-target");
const loadTabEvent = new CustomEvent('loadTab', { detail: targetId });
document.dispatchEvent(loadTabEvent);
@@ -575,6 +576,10 @@ document.addEventListener("loadTab", function (event) {
})
$('#apcfgmsg').innerHTML = '';
break;
case 'updatetab':
$('#updateconsole').innerHTML = '';
loadOTA();
break;
}
});
@@ -605,12 +610,6 @@ $('#apcfgsave').onclick = function () {
.catch(error => showMessage('Error: ' + error));
}
$('#updatebutton').onclick = function (event) {
event.preventDefault();
$('#apupdatebox').style.display = 'block';
loadOTA();
}
async function loadOTA() {
otamodule = await import('./ota.js?v=' + Date.now());
otamodule.initUpdate();

View File

@@ -1,4 +1,5 @@
const repoUrl = 'https://api.github.com/repos/jjwbruijn/OpenEPaperLink/releases';
var repo = apConfig.repo || 'jjwbruijn/OpenEPaperLink';
var repoUrl = 'https://api.github.com/repos/' + repo + '/releases';
const $ = document.querySelector.bind(document);
@@ -8,17 +9,26 @@ let env = '', currentVer = '', currentBuildtime = 0;
let buttonState = false;
export async function initUpdate() {
if (!$("#updateconsole")) {
const consoleDiv = document.createElement('div');
consoleDiv.classList.add('console');
consoleDiv.id = "updateconsole";
$('#apupdatebox').appendChild(consoleDiv);
}
$("#updateconsole").innerHTML = "";
const response = await fetch("/version.txt");
let filesystemversion = await response.text();
if (!filesystemversion) filesystemversion = "unknown";
$('#repo').value = repo;
const envBox = $('#environment');
if (envBox?.tagName === 'SELECT') {
const inputElement = document.createElement('input');
inputElement.type = 'text';
inputElement.id = 'environment';
envBox.parentNode.replaceChild(inputElement, envBox);
}
$('#environment').value = '';
$('#environment').setAttribute('readonly', true);
$('#repo').removeAttribute('readonly');
$('#confirmSelectRepo').style.display = 'none';
$('#cancelSelectRepo').style.display = 'none';
$('#selectRepo').style.display = 'inline-block';
$('#repoWarning').style.display = 'none';
fetch("/sysinfo")
.then(response => {
@@ -42,7 +52,6 @@ export async function initUpdate() {
print(`build date: ${formatEpoch(data.buildtime)}`);
print(`esp32 version: ${data.buildversion}`);
print(`filesystem version: ${filesystemversion}` + matchtest);
print(`sha: ${data.sha}`);
print(`psram size: ${data.psramsize}`);
print(`flash size: ${data.flashsize}`);
print("--------------------------", "gray");
@@ -51,6 +60,7 @@ export async function initUpdate() {
currentBuildtime = data.buildtime;
if (data.rollback) $("#rollbackOption").style.display = 'block';
if (data.env == 'ESP32_S3_16_8_YELLOW_AP') $("#c6Option").style.display = 'block';
$('#environment').value = env;
}
})
.catch(error => {
@@ -92,7 +102,6 @@ export async function initUpdate() {
}
}
}
easyupdate.innerHTML += "<br><a onclick=\"$('#advanceddiv').style.display='block'\">advanced options</a>"
const table = document.createElement('table');
const tableHeader = document.createElement('tr');
@@ -101,9 +110,9 @@ export async function initUpdate() {
let rowCounter = 0;
releaseDetails.forEach(release => {
if (rowCounter < 3 && release?.html_url) {
if (rowCounter < 4 && release?.html_url) {
const tableRow = document.createElement('tr');
let tablerow = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td><td><button onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</button></td><td><button onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td>`;
let tablerow = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td><td><button type="button" onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td><td><button type="button" onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</button></td>`;
if (release.tag_name == currentVer) {
tablerow += "<td>current version</td>";
} else if (release.date < formatEpoch(currentBuildtime)) {
@@ -362,6 +371,90 @@ $('#updateC6Btn').onclick = function () {
disableButtons(false);
}
$('#selectRepo').onclick = function (event) {
event.preventDefault();
$('#updateconsole').innerHTML = '';
let repoUrl = 'https://api.github.com/repos/' + $('#repo').value + '/releases';
fetch(repoUrl)
.then(response => response.json())
.then(data => {
if (Array.isArray(data) && data.length > 0) {
const release = data[0];
print("Repo found! Latest release: " + release.name + " created " + release.created_at);
const assets = release.assets;
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
if (filesJsonAsset && binariesJsonAsset) {
const updateUrl = "http://openepaperlink.eu/getupdate/?url=" + binariesJsonAsset.browser_download_url + "&env=" + $('#repo').value;
return fetch(updateUrl);
} else {
throw new Error("Json file binaries.json and/or filesystem.json not found in the release assets");
}
};
})
.then(updateResponse => {
if (!updateResponse.ok) {
throw new Error("Network response was not OK");
}
return updateResponse.text();
})
.then(responseBody => {
if (!responseBody.trim().startsWith("[")) {
throw new Error("Failed to fetch the release info file");
}
const updateData = JSON.parse(responseBody).filter(item => !item.name.endsWith('_full.bin'));
const inputParent = $('#environment').parentNode;
const selectElement = document.createElement('select');
selectElement.id = 'environment';
updateData.forEach(item => {
const option = document.createElement('option');
option.value = item.name.replace('.bin', '');
option.text = item.name.replace('.bin', '');
selectElement.appendChild(option);
});
inputParent.replaceChild(selectElement, $('#environment'));
$('#environment').value = env;
$('#confirmSelectRepo').style.display = 'inline-block';
$('#cancelSelectRepo').style.display = 'inline-block';
$('#selectRepo').style.display = 'none';
$('#repo').setAttribute('readonly', true);
$('#repoWarning').style.display = 'block';
})
.catch(error => {
print('Error fetching releases:' + error, "red");
});
}
$('#cancelSelectRepo').onclick = function (event) {
event.preventDefault();
$('#updateconsole').innerHTML = '';
initUpdate();
}
$('#confirmSelectRepo').onclick = function (event) {
event.preventDefault();
repo = $('#repo').value;
let formData = new FormData();
formData.append("repo", repo);
formData.append("env", $('#environment').value);
fetch("/save_apcfg", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => {
window.dispatchEvent(loadConfig);
print('OK, Saved');
})
.catch(error => print('Error: ' + error));
$('#updateconsole').innerHTML = '';
repoUrl = 'https://api.github.com/repos/' + repo + '/releases';
initUpdate();
}
export function print(line, color = "white") {
const consoleDiv = document.getElementById('updateconsole');
if (consoleDiv) {
@@ -464,7 +557,7 @@ const writeVersion = async (content, name, path) => {
};
function disableButtons(active) {
$("#apupdatebox").querySelectorAll('button').forEach(button => {
$("#configtab").querySelectorAll('button').forEach(button => {
button.disabled = active;
});
buttonState = active;