Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Import and Export of system config #52

Merged
merged 4 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"clocks.h": "c",
"pwm.h": "c",
"servo_gate.h": "c",
"semphr.h": "c"
"semphr.h": "c",
"version.h": "c"
}
}
7 changes: 0 additions & 7 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,6 @@
#include "servo_gate.h"


uint8_t software_reboot() {
watchdog_reboot(0, 0, 0);

return 0;
}


int main()
{
// stdio_init_all();
Expand Down
1 change: 0 additions & 1 deletion src/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ extern "C" {
#endif

bool app_init();
uint8_t software_reboot();
bool http_app_config();

#ifdef __cplusplus
Expand Down
2 changes: 1 addition & 1 deletion src/charge_mode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa
sizeof(charge_mode_json_buffer),
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"
"{\"c1\":\"#%06lx\",\"c2\":\"#%06lx\",\"c3\":\"#%06lx\",\"c4\":\"#%06lx\","
"\"c5\":%.3f,\"c6\":%.3f,\"c7\":%.3f,\"c8\":%.3f,\"c9\":%d,\"c10\":\"%s\",\"c11\":%ld,\"c12\":%0.3f}",
"\"c5\":%.3f,\"c6\":%.3f,\"c7\":%.3f,\"c8\":%.3f,\"c9\":%d,\"c10\":%s,\"c11\":%ld,\"c12\":%0.3f}",
charge_mode_config.eeprom_charge_mode_data.neopixel_normal_charge_colour,
charge_mode_config.eeprom_charge_mode_data.neopixel_under_charge_colour,
charge_mode_config.eeprom_charge_mode_data.neopixel_over_charge_colour,
Expand Down
65 changes: 2 additions & 63 deletions src/eeprom.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <stdlib.h>
#include <string.h>

#include "hardware/watchdog.h"
#include "hardware/regs/rosc.h"
#include "hardware/regs/addressmap.h"

Expand All @@ -19,8 +18,8 @@
#include "app.h"
#include "neopixel_led.h"
#include "rotary_button.h"
#include "version.h"
#include "profile.h"
#include "system_control.h"


extern bool cat24c256_eeprom_erase();
Expand Down Expand Up @@ -78,7 +77,7 @@ uint8_t eeprom_erase(bool reboot) {
cat24c256_eeprom_erase();

if (reboot) {
watchdog_reboot(0, 0, 0);
software_reboot();
}

return 37; // Configuration Menu ID
Expand Down Expand Up @@ -186,63 +185,3 @@ bool eeprom_get_board_id(char ** board_id_buffer, size_t bytes_to_copy) {
return true;
}


bool http_rest_system_control(struct fs_file *file, int num_params, char *params[], char *values[]) {
// Mappings
// s0 (str): unique_id
// s1 (str): version_string
// s2 (str): vcs_hash
// s3 (str): build_type
// s4 (bool): save_to_eeprom
// s5 (bool): software_reset
// s6 (bool): erase_eeprom
static char eeprom_config_json_buffer[256];

bool save_to_eeprom_flag = false;
bool software_reset_flag = false;
bool erase_eeprom_flag = false;

// Control
for (int idx = 0; idx < num_params; idx += 1) {
if (strcmp(params[idx], "s4") == 0) {
save_to_eeprom_flag = string_to_boolean(values[idx]);
}
else if (strcmp(params[idx], "s5") == 0) {
software_reset_flag = string_to_boolean(values[idx]);
}
else if (strcmp(params[idx], "s6") == 0) {
erase_eeprom_flag = string_to_boolean(values[idx]);
}
}

if (save_to_eeprom_flag) {
eeprom_save_all();
}

if (erase_eeprom_flag) {
eeprom_erase(software_reset_flag);
}

if (software_reset_flag) {
software_reboot();
}

// Response
snprintf(eeprom_config_json_buffer,
sizeof(eeprom_config_json_buffer),
"%s"
"{\"s0\":\"%s\",\"s1\":\"%s\",\"s2\":\"%s\",\"s3\":\"%s\",\"s4\":%s,\"s5\":%s,\"s6\":%s}",
http_json_header,
metadata.unique_id, version_string, vcs_hash, build_type,
boolean_to_string(save_to_eeprom_flag),
boolean_to_string(erase_eeprom_flag),
boolean_to_string(software_reset_flag));

size_t data_length = strlen(eeprom_config_json_buffer);
file->data = eeprom_config_json_buffer;
file->len = data_length;
file->index = data_length;
file->flags = FS_FILE_FLAGS_HEADER_INCLUDED;

return true;
}
2 changes: 0 additions & 2 deletions src/eeprom.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ uint8_t eeprom_erase(bool);
uint8_t eeprom_save_all(void);
void eeprom_register_handler(eeprom_save_handler_t handler);

bool http_rest_system_control(struct fs_file *file, int num_params, char *params[], char *values[]);


#ifdef __cplusplus
}
Expand Down
132 changes: 130 additions & 2 deletions src/html/web_portal.html
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,17 @@
</select>
</div>

<button class="btn btn-neutral system-control-apply-btn">Apply</button>
<button class="btn btn-neutral system-control-apply-btn" id="system_control_apply_btn">Apply</button>
</form>

<div class="divider"></div>

<!-- Export and Import config -->
<div class="grid grid-cols-2 gap-1">
<button class="btn btn-neutral system-control-export-btn" onclick="onExportConfigClicked()">Export Config</button>
<button class="btn btn-neutral system-control-import-btn" onclick="onImportConfigClicked()">Import Config</button>
</div>

</section>

</div>
Expand Down Expand Up @@ -747,7 +756,7 @@ <h3 class="font-bold text-lg">Warning</h3>
</dialog>

<dialog id="settingsAppliedSuccessDialog" class="modal">
<div class="modal-box">
<div class="modal-box" id="settingsAppliedSuccessText">
<h3 class="font-bold text-lg">Info</h3>
<p class="py-4">Settings Applied</p>
</div>
Expand All @@ -756,6 +765,14 @@ <h3 class="font-bold text-lg">Info</h3>
</form>
</dialog>

<dialog id="settingsRestoreFailedDialog" class="modal">
<div class="modal-box" id="settingsRestoreFailedText">
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>

<dialog id="scaleZeroedSuccessDialog" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Info</h3>
Expand Down Expand Up @@ -1387,6 +1404,117 @@ <h3 class="font-bold text-lg">Error</h3>
fetch(uri);
}

async function onExportConfigClicked() {
const config = {};

const endpoints = [
"/rest/scale_config",
"/rest/charge_mode_config",
"/rest/coarse_motor_config",
"/rest/fine_motor_config",
"/rest/button_config",
"/rest/wireless_config",
"/rest/neopixel_led_config",
"/rest/profile_config?pf=0",
"/rest/profile_config?pf=1",
"/rest/profile_config?pf=2",
"/rest/profile_config?pf=3",
"/rest/profile_config?pf=4",
"/rest/profile_config?pf=5",
"/rest/profile_config?pf=6",
"/rest/profile_config?pf=7",
"/rest/servo_gate_config",
]

// Read metadata
const response = await fetch("/rest/system_control");
const system_control_data = await response.json();
config["unique_id"] = system_control_data["s0"];
config["firmware_version"] = system_control_data["s1"];
config["vcs_hash"] = system_control_data["s2"];
config["config"] = {};

// Fetch data from endpoints
for (const endpoint of endpoints) {
const response = await fetch(endpoint);
const data = await response.json();
config["config"][endpoint] = data;
}

// download as a file
const blob = new Blob([JSON.stringify(config, null, "\t")], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = "opentrickler_"+config["unique_id"]+"_config.json";
a.click();
URL.revokeObjectURL(url);
}

async function onImportConfigClicked() {
const input = document.createElement('input');
input.setAttribute('type', 'file')
input.setAttribute('multiple', 'false')
input.setAttribute('accept', '.json')

input.onchange = (event) => {
const file = event.target.files[0];
if (!file) {
return;
}

// Setup the callbacks
const reader = new FileReader();
reader.onload = (event2) => {
try {
const data = JSON.parse(reader.result);

// Process data
const source_firmware_version = data["firmware_version"];
const source_unique_id = data["unique_id"];
const source_vcs_hash = data["vcs_hash"];
const config = data["config"];

// Restore settings
for (const [endpoint, config_data] of Object.entries(config)) {
const paramData = new FormData();
for (const [key, value] of Object.entries(config_data)) {
paramData.append(key, value);
}

// Create URI
const queryString = new URLSearchParams(paramData).toString();
const uri = new URL(endpoint + "?", window.location.origin);
uri.search = queryString;
fetch(uri);
}

// Update form
const systemControlForm = document.getElementById("systemControlForm");
systemControlForm.elements["s4"].value = true; // Save to EEPROM
systemControlForm.elements["s5"].value = true; // Reboot

// Prompt user to save and reboot
const applyBtn = document.getElementById("system_control_apply_btn");
applyBtn.click();
}
catch (exception) {
console.error(exception);

// Show warning
const settingsRestoreFailedDialog = document.getElementById("settingsRestoreFailedDialog");
const settingsRestoreFailedText = document.getElementById("settingsRestoreFailedText");

settingsRestoreFailedText.innerHTML = `<h3 class="font-bold text-lg">Failed to Restore Settings</h3><p class="py-4">${exception}</p>`
settingsRestoreFailedDialog.showModal();
}
}

reader.readAsText(file);
}
input.click();
}

// Start the long polling process when the page loads
if (document.readyState != "loading") {
onNavButtonClicked('trickler');
Expand Down
2 changes: 1 addition & 1 deletion src/menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include "cleanup_mode.h"
#include "eeprom.h"
#include "wireless.h"

#include "system_control.h"

// External variables
extern muif_t muif_list[];
Expand Down
1 change: 1 addition & 0 deletions src/rest_endpoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "profile.h"
#include "cleanup_mode.h"
#include "servo_gate.h"
#include "system_control.h"

// Generated headers by html2header.py under scripts
#include "display_mirror.html.h"
Expand Down
79 changes: 79 additions & 0 deletions src/system_control.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <string.h>

#include "hardware/watchdog.h"

#include "system_control.h"
#include "common.h"
#include "eeprom.h"
#include "version.h"

extern eeprom_metadata_t metadata;


int software_reboot() {
watchdog_reboot(0, 0, 0);

return 0;
}



bool http_rest_system_control(struct fs_file *file, int num_params, char *params[], char *values[]) {
// Mappings
// s0 (str): unique_id
// s1 (str): version_string
// s2 (str): vcs_hash
// s3 (str): build_type
// s4 (bool): save_to_eeprom
// s5 (bool): software_reset
// s6 (bool): erase_eeprom
static char eeprom_config_json_buffer[256];

bool save_to_eeprom_flag = false;
bool software_reset_flag = false;
bool erase_eeprom_flag = false;

// Control
for (int idx = 0; idx < num_params; idx += 1) {
if (strcmp(params[idx], "s4") == 0) {
save_to_eeprom_flag = string_to_boolean(values[idx]);
}
else if (strcmp(params[idx], "s5") == 0) {
software_reset_flag = string_to_boolean(values[idx]);
}
else if (strcmp(params[idx], "s6") == 0) {
erase_eeprom_flag = string_to_boolean(values[idx]);
}
}

if (save_to_eeprom_flag) {
eeprom_save_all();
}

if (erase_eeprom_flag) {
eeprom_erase(software_reset_flag);
}

if (software_reset_flag) {
software_reboot();
}

// Response
snprintf(eeprom_config_json_buffer,
sizeof(eeprom_config_json_buffer),
"%s"
"{\"s0\":\"%s\",\"s1\":\"%s\",\"s2\":\"%s\",\"s3\":\"%s\",\"s4\":%s,\"s5\":%s,\"s6\":%s}",
http_json_header,
metadata.unique_id, version_string, vcs_hash, build_type,
boolean_to_string(save_to_eeprom_flag),
boolean_to_string(erase_eeprom_flag),
boolean_to_string(software_reset_flag));

size_t data_length = strlen(eeprom_config_json_buffer);
file->data = eeprom_config_json_buffer;
file->len = data_length;
file->index = data_length;
file->flags = FS_FILE_FLAGS_HEADER_INCLUDED;

return true;
}
Loading