Browse Source

Backup and restore settings from file

fastled
Xose Pérez 7 years ago
parent
commit
39a81ea529
6 changed files with 154 additions and 17 deletions
  1. +1
    -0
      code/espurna/config/general.h
  2. +46
    -10
      code/espurna/settings.ino
  3. +40
    -0
      code/espurna/web.ino
  4. +4
    -0
      code/html/custom.css
  5. +52
    -5
      code/html/custom.js
  6. +11
    -2
      code/html/index.html

+ 1
- 0
code/espurna/config/general.h View File

@ -13,6 +13,7 @@
#define EEPROM_RELAY_STATUS 0
#define EEPROM_ENERGY_COUNT 1
#define EEPROM_DATA_END 5
//--------------------------------------------------------------------------------
// BUTTON


+ 46
- 10
code/espurna/settings.ino View File

@ -20,18 +20,44 @@ Embedis embedis(Serial);
// -----------------------------------------------------------------------------
unsigned long settingsSize() {
bool zero = false;
unsigned long size = 0;
for (unsigned int i = SPI_FLASH_SEC_SIZE; i>0; i--) {
size++;
if (EEPROM.read(i) == 0) {
if (zero) break;
zero = true;
} else {
zero = false;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
pos = pos - len - 2;
}
return SPI_FLASH_SEC_SIZE - pos;
}
unsigned int settingsKeyCount() {
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
pos = pos - len - 2;
count ++;
}
return count / 2;
}
String settingsKeyName(unsigned int index) {
String s;
unsigned count = 0;
unsigned stop = index * 2 + 1;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
pos = pos - len - 2;
count++;
if (count == stop) {
s.reserve(len);
for (unsigned char i = 0 ; i < len; i++) {
s += (char) EEPROM.read(pos + i + 1);
}
break;
}
}
return size-2;
return s;
}
void settingsSetup() {
@ -92,6 +118,16 @@ void settingsSetup() {
e->response(String(settingsSize()));
});
Embedis::command( F("DUMP"), [](Embedis* e) {
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
e->stream->printf("+%s => %s\n", key.c_str(), value.c_str());
}
e->response(Embedis::OK);
});
DEBUG_MSG("[SETTINGS] EEPROM size: %d bytes\n", SPI_FLASH_SEC_SIZE);
DEBUG_MSG("[SETTINGS] Settings size: %d bytes\n", settingsSize());


+ 40
- 0
code/espurna/web.ino View File

@ -89,6 +89,22 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
DEBUG_MSG("[WEBSOCKET] Requested action: %s\n", action.c_str());
if (action.equals("reset")) ESP.reset();
if (action.equals("restore") && root.containsKey("data")) {
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
JsonObject& data = root["data"];
for (auto element : data){
setSetting(element.key, element.value.as<char*>());
}
saveSettings();
ws.text(client_id, "{\"message\": \"Changes saved. You should reboot your board now.\"}");
}
if (action.equals("reconnect")) {
// Let the HTTP request return and disconnect after 100ms
@ -707,6 +723,29 @@ void _onAuth(AsyncWebServerRequest *request) {
}
void _onGetConfig(AsyncWebServerRequest *request) {
webLogRequest(request);
if (!_authenticate(request)) return request->requestAuthentication();
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
root[key] = value;
}
char buffer[100];
sprintf(buffer, "attachment; filename=\"%s-backup.json\"", (char *) getSetting("hostname").c_str());
response->addHeader("Content-Disposition", buffer);
response->setLength();
request->send(response);
}
#if EMBEDDED_WEB
void _onHome(AsyncWebServerRequest *request) {
@ -751,6 +790,7 @@ void webSetup() {
#if EMBEDDED_WEB
_server->on("/index.html", HTTP_GET, _onHome);
#endif
_server->on("/config", HTTP_GET, _onGetConfig);
_server->on("/auth", HTTP_GET, _onAuth);
_server->on("/apis", HTTP_GET, _onAPIs);
_server->on("/rpc", HTTP_GET, _onRPC);


+ 4
- 0
code/html/custom.css View File

@ -51,6 +51,10 @@
.button-more-network {
background: rgb(223, 117, 20);
}
.button-settings-backup,
.button-settings-restore {
background: rgb(0, 202, 0);
}
.pure-g {
margin-bottom: 20px;
}


+ 52
- 5
code/html/custom.js View File

@ -1,6 +1,8 @@
var websock;
var password = false;
var maxNetworks;
var host;
var port;
// http://www.the-art-of-web.com/javascript/validate-password/
function checkPassword(str) {
@ -71,6 +73,44 @@ function doToggle(element, value) {
return false;
}
function backupSettings() {
document.getElementById('downloader').src = 'http://' + host + ':' + port + '/config';
return false;
}
function onFileUpload(event) {
var inputFiles = this.files;
if (inputFiles == undefined || inputFiles.length == 0) return false;
var inputFile = inputFiles[0];
var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
if (response == false) return false;
var reader = new FileReader();
reader.onload = function(e) {
var data = getJson(e.target.result);
if (data) {
websock.send(JSON.stringify({'action': 'restore', 'data': data}));
} else {
alert("The file is not a configuration backup or is corrupted");
}
};
reader.readAsText(inputFile);
return false;
}
function restoreSettings() {
if (typeof window.FileReader !== 'function') {
alert("The file API isn't supported on this browser yet.");
} else {
$("#uploader").click();
}
return false;
}
function randomString(length, chars) {
var mask = '';
if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz';
@ -357,13 +397,17 @@ function getJson(str) {
}
}
function connect(host, port) {
if (typeof host === 'undefined') {
host = window.location.hostname;
function connect(h, p) {
if (typeof h === 'undefined') {
h = window.location.hostname;
}
if (typeof port === 'undefined') {
port = location.port;
if (typeof p === 'undefined') {
p = location.port;
}
host = h;
port = p;
if (websock) websock.close();
websock = new WebSocket('ws://' + host + ':' + port + '/ws');
websock.onopen = function(evt) {
@ -388,6 +432,9 @@ function init() {
$(".button-update-password").on('click', doUpdatePassword);
$(".button-reset").on('click', doReset);
$(".button-reconnect").on('click', doReconnect);
$(".button-settings-backup").on('click', backupSettings);
$(".button-settings-restore").on('click', restoreSettings);
$('#uploader').on('change', onFileUpload);
$(".button-apikey").on('click', doGenerateAPIKey);
$(".pure-menu-link").on('click', showPanel);
$(".button-add-network").on('click', function() {


+ 11
- 2
code/html/index.html View File

@ -294,16 +294,22 @@
</div>
<div class="pure-g module module-fauxmo">
<div class="pure-u-1 pure-u-sm-1-4"><label for="fauxmoEnabled">Alexa integration</label></div>
<label class="pure-u-1 pure-u-sm-1-4" for="fauxmoEnabled">Alexa integration</label>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="fauxmoEnabled" tabindex="6" /></div>
</div>
<div class="pure-g module module-ds module-dht">
<div class="pure-u-1 pure-u-sm-1-4"><label for="tmpUnits">Temperature units</label></div>
<label class="pure-u-1 pure-u-sm-1-4" for="tmpUnits">Temperature units</label>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="7" value="0"> Celsius (ºC)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="8" value="1"> Fahrenheit (ºF)</input></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Settings</label>
<div class="pure-u-1 pure-u-sm-1-8"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
<div class="pure-u-1 pure-u-sm-1-8"><button class="pure-button button-settings-restore pure-u-23-24">Restore</button></div>
</div>
</fieldset>
</div>
</div>
@ -616,6 +622,9 @@
</div>
</div>
<iframe id="downloader" style="display:none;"></iframe>
<input id="uploader" type="file" style="display:none;" />
</body>
<!-- build:js script.js -->


Loading…
Cancel
Save