diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md
index b128f1fab..0ec10fa91 100644
--- a/CHANGELOG_LATEST.md
+++ b/CHANGELOG_LATEST.md
@@ -40,6 +40,7 @@
- API fetch individual attributes from an entity [#462](https://github.com/emsesp/EMS-ESP32/issues/462)
- Option to disable mDNS
- Option for rendering booleans on dashboard [#456](https://github.com/emsesp/EMS-ESP32/issues/456)
+- Upload customization settings from a file [#256](https://github.com/emsesp/EMS-ESP32/issues/256)
### Fixed
diff --git a/interface/package.json b/interface/package.json
index 413709d0b..f278f7a4d 100644
--- a/interface/package.json
+++ b/interface/package.json
@@ -78,7 +78,7 @@
"max-len": [
1,
{
- "code": 150
+ "code": 200
}
],
"arrow-parens": 1
diff --git a/interface/src/project/HelpInformation.tsx b/interface/src/project/HelpInformation.tsx
index e081facca..16a27dbe4 100644
--- a/interface/src/project/HelpInformation.tsx
+++ b/interface/src/project/HelpInformation.tsx
@@ -2,7 +2,7 @@ import { FC, useContext } from 'react';
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
-import { SectionContent, ButtonRow } from '../components';
+import { SectionContent, ButtonRow, MessageBox } from '../components';
import { AuthenticatedContext } from '../contexts/authentication';
@@ -102,9 +102,13 @@ const HelpInformation: FC = () => {
- To report an issue or request a feature, please do via
+ To report an issue or request a feature, please
+ onDownload('info')}>
+ download
+
+ the debug information and include in a new
- {'GitHub'}
+ GitHub issue
@@ -112,28 +116,15 @@ const HelpInformation: FC = () => {
{me.admin && (
<>
-
- Export Data
+
+ Download Settings
-
- Download the current system information to show EMS statistics and connected devices
-
-
- } variant="outlined" color="secondary" onClick={() => onDownload('info')}>
- system info
-
-
-
+
Export the application settings and any customizations to a JSON file. These files can later be uploaded
- via the System menu.
-
-
- Be careful when sharing the settings as the file contains passwords and other sensitive system
- information.
+ via System→Upload.
-
+
>
)}
diff --git a/src/command.cpp b/src/command.cpp
index 15f21f08f..a169f87db 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -193,10 +193,11 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
return nullptr;
}
+ // check prefix and valid number range, also check 'id'
if (!strncmp(command, "hc", 2) && command[2] >= '1' && command[2] <= '8') {
id = command[2] - '0';
command += 3;
- } else if (!strncmp(command, "wwc", 3) && command[3] >= '1' && command[4] == '0') {
+ } else if (!strncmp(command, "wwc", 3) && command[3] == '1' && command[4] == '0') {
id = 19;
command += 5;
} else if (!strncmp(command, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
diff --git a/src/system.cpp b/src/system.cpp
index 47fbabb45..3396cf6fe 100644
--- a/src/system.cpp
+++ b/src/system.cpp
@@ -689,7 +689,7 @@ void System::commands_init() {
// these commands will return data in JSON format
Command::add(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, F("show system status"));
- Command::add(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("fetch system settings"));
+ Command::add(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("fetch system settings"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(customizations), System::command_customizations, F("fetch system customizations"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("fetch system commands"));
@@ -915,7 +915,7 @@ bool System::check_upgrade() {
reboot_required |= saveSettings(EMSESP_SETTINGS_FILE, "Settings", input);
} else if (settings_type == "customizations") {
// it's a customization file, just replace it and there's no need to reboot
- saveSettings(EMSESP_CUSTOMIZATION_FILE, "Customization", input);
+ saveSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", input);
} else {
LOG_ERROR(F("Unrecognized file uploaded"));
}
@@ -923,7 +923,7 @@ bool System::check_upgrade() {
LOG_ERROR(F("Unrecognized file uploaded, not json"));
}
- // close (just in case) and remove the file
+ // close (just in case) and remove the temp file
new_file.close();
LittleFS.remove(TEMP_FILENAME_PATH);
}
@@ -1007,36 +1007,25 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
JsonObject node;
// System
- node = output.createNestedObject("System");
-
- node["version"] = EMSESP_APP_VERSION;
- node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
- node["uptime (seconds)"] = uuid::get_uptime_sec();
- node["network time"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
-
+ node = output.createNestedObject("System Status");
+ node["version"] = EMSESP_APP_VERSION;
+ node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
+ // node["uptime (seconds)"] = uuid::get_uptime_sec();
#ifndef EMSESP_STANDALONE
node["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes
#endif
node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
- if (EMSESP::dallas_enabled()) {
- node["temperature sensors"] = EMSESP::dallassensor_.no_sensors();
- }
-
- if (EMSESP::analog_enabled()) {
- node["analog sensors"] = EMSESP::analogsensor_.no_sensors();
- }
-
#ifndef EMSESP_STANDALONE
- // Network
- node = output.createNestedObject("Network");
+ // Network Status
+ node = output.createNestedObject("Network Status");
if (WiFi.status() == WL_CONNECTED) {
- node["connection"] = F("WiFi");
- node["hostname"] = WiFi.getHostname();
- node["SSID"] = WiFi.SSID();
- node["BSSID"] = WiFi.BSSIDstr();
- node["RSSI"] = WiFi.RSSI();
- node["MAC"] = WiFi.macAddress();
+ node["connection"] = F("WiFi");
+ node["hostname"] = WiFi.getHostname();
+ // node["SSID"] = WiFi.SSID();
+ // node["BSSID"] = WiFi.BSSIDstr();
+ node["RSSI"] = WiFi.RSSI();
+ // node["MAC"] = WiFi.macAddress();
node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask());
node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
node["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP());
@@ -1053,18 +1042,115 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") {
node["IPv6 address"] = uuid::printable_to_string(ETH.localIPv6());
}
+ EMSESP::webSettingsService.read([&](WebSettings & settings) {
+ node["phy type"] = settings.phy_type;
+ node["eth power"] = settings.eth_power;
+ node["eth phy addr"] = settings.eth_phy_addr;
+ node["eth clock mode"] = settings.eth_clock_mode;
+ });
+ }
+#endif
+ EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
+ node["static ip config"] = settings.staticIPConfig;
+ node["enable IPv6"] = settings.enableIPv6;
+ node["low bandwidth"] = settings.bandwidth20;
+ node["disable sleep"] = settings.nosleep;
+ });
+#ifndef EMSESP_STANDALONE
+ EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) {
+ const char * pM[] = {"always", "disconnected", "never"};
+ node["AP provision mode"] = pM[settings.provisionMode];
+ node["AP security"] = settings.password.length() ? "wpa2" : "open";
+ node["AP ssid"] = settings.ssid;
+ });
+#endif
+
+ // NTP status
+ node = output.createNestedObject("NTP Status");
+#ifndef EMSESP_STANDALONE
+ node["network time"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
+ EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
+ node["enabled"] = settings.enabled;
+ node["server"] = settings.server;
+ node["tz label"] = settings.tzLabel;
+ // node["tz format"] = settings.tzFormat;
+ });
+
+ // OTA status
+ node = output.createNestedObject("OTA Status");
+ EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) {
+ node["enabled"] = settings.enabled;
+ node["port"] = settings.port;
+ });
+#endif
+
+ // MQTT Status
+ node = output.createNestedObject("MQTT Status");
+ node["MQTT status"] = Mqtt::connected() ? F_(connected) : F_(disconnected);
+ if (Mqtt::enabled()) {
+ node["MQTT publishes"] = Mqtt::publish_count();
+ node["MQTT publish fails"] = Mqtt::publish_fails();
+ }
+ EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
+ node["enabled"] = settings.enabled;
+ node["client_id"] = settings.clientId;
+ node["keep alive"] = settings.keepAlive;
+ node["clean session"] = settings.cleanSession;
+ node["base"] = settings.base;
+ node["discovery prefix"] = settings.discovery_prefix;
+ node["nested format"] = settings.nested_format;
+ node["ha enabled"] = settings.ha_enabled;
+ node["mqtt qos"] = settings.mqtt_qos;
+ node["mqtt retain"] = settings.mqtt_retain;
+ node["publish time boiler"] = settings.publish_time_boiler;
+ node["publish time thermostat"] = settings.publish_time_thermostat;
+ node["publish time solar"] = settings.publish_time_solar;
+ node["publish time mixer"] = settings.publish_time_mixer;
+ node["publish time other"] = settings.publish_time_other;
+ node["publish time sensor"] = settings.publish_time_sensor;
+ node["publish single"] = settings.publish_single;
+ node["publish2command"] = settings.publish_single2cmd;
+ node["send response"] = settings.send_response;
+ });
+
+ // Syslog Status
+ node = output.createNestedObject("Syslog Status");
+ node["enabled"] = EMSESP::system_.syslog_enabled_;
+#ifndef EMSESP_STANDALONE
+ if (EMSESP::system_.syslog_enabled_) {
+ node["syslog started"] = syslog_.started();
+ node["syslog level"] = FL_(enum_syslog_level)[syslog_.log_level() + 1];
+ node["syslog ip"] = syslog_.ip();
+ node["syslog queue"] = syslog_.queued();
}
#endif
- // Status
- node = output.createNestedObject("Status");
+ // Sensor Status
+ node = output.createNestedObject("Sensor Status");
+ if (EMSESP::dallas_enabled()) {
+ node["temperature sensors"] = EMSESP::dallassensor_.no_sensors();
+ node["temperature sensor reads"] = EMSESP::dallassensor_.reads();
+ node["temperature sensor fails"] = EMSESP::dallassensor_.fails();
+ }
+ if (EMSESP::analog_enabled()) {
+ node["analog sensors"] = EMSESP::analogsensor_.no_sensors();
+ node["analog sensor reads"] = EMSESP::analogsensor_.reads();
+ node["analog sensor fails"] = EMSESP::analogsensor_.fails();
+ }
+ // API Status
+ node = output.createNestedObject("API Status");
+ node["API calls"] = WebAPIService::api_count();
+ node["API fails"] = WebAPIService::api_fails();
+
+ // EMS Bus Status
+ node = output.createNestedObject("Bus Status");
switch (EMSESP::bus_status()) {
case EMSESP::BUS_STATUS_OFFLINE:
node["bus status"] = (F("disconnected"));
break;
case EMSESP::BUS_STATUS_TX_ERRORS:
- node["bus status"] = (F("connected, tx issues - try a different tx-mode"));
+ node["bus status"] = (F("connected, tx issues - try a different Tx Mode"));
break;
case EMSESP::BUS_STATUS_CONNECTED:
node["bus status"] = (F("connected"));
@@ -1073,7 +1159,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["bus status"] = (F("unknown"));
break;
}
-
if (EMSESP::bus_status() != EMSESP::BUS_STATUS_OFFLINE) {
node["bus protocol"] = EMSbus::is_ht3() ? F("HT3") : F("Buderus");
node["bus telegrams received (rx)"] = EMSESP::rxservice_.telegram_count();
@@ -1084,34 +1169,37 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["bus writes failed"] = EMSESP::txservice_.telegram_write_fail_count();
node["bus rx line quality"] = EMSESP::rxservice_.quality();
node["bus tx line quality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.read_quality()) / 2;
- if (Mqtt::enabled()) {
- node["MQTT status"] = Mqtt::connected() ? F_(connected) : F_(disconnected);
- node["MQTT publishes"] = Mqtt::publish_count();
- node["MQTT publish fails"] = Mqtt::publish_fails();
- }
- node["temperature sensors"] = EMSESP::dallassensor_.no_sensors();
- if (EMSESP::dallas_enabled()) {
- node["temperature sensor reads"] = EMSESP::dallassensor_.reads();
- node["temperature sensor fails"] = EMSESP::dallassensor_.fails();
- }
- node["analog sensors"] = EMSESP::analogsensor_.no_sensors();
- if (EMSESP::analog_enabled()) {
- node["analog sensor reads"] = EMSESP::analogsensor_.reads();
- node["analog sensor fails"] = EMSESP::analogsensor_.fails();
- }
- node["API calls"] = WebAPIService::api_count();
- node["API fails"] = WebAPIService::api_fails();
-
-#ifndef EMSESP_STANDALONE
- if (EMSESP::system_.syslog_enabled_) {
- node["syslog started"] = syslog_.started();
- node["syslog level"] = FL_(enum_syslog_level)[syslog_.log_level() + 1];
- node["syslog ip"] = syslog_.ip();
- node["syslog queue"] = syslog_.queued();
- }
-#endif
}
+ // Settings
+ node = output.createNestedObject("Settings");
+ EMSESP::webSettingsService.read([&](WebSettings & settings) {
+ node["board profile"] = settings.board_profile;
+ node["tx mode"] = settings.tx_mode;
+ node["ems bus id"] = settings.ems_bus_id;
+ node["shower timer"] = settings.shower_timer;
+ node["shower alert"] = settings.shower_alert;
+ if (settings.shower_alert) {
+ node["shower alert coldshot"] = settings.shower_alert_coldshot; // seconds
+ node["shower alert trigger"] = settings.shower_alert_trigger; // minutes
+ }
+ node["rx gpio"] = settings.rx_gpio;
+ node["tx gpio"] = settings.tx_gpio;
+ node["dallas gpio"] = settings.dallas_gpio;
+ node["pbutton gpio"] = settings.pbutton_gpio;
+ node["led gpio"] = settings.led_gpio;
+ node["hide led"] = settings.hide_led;
+ node["notoken api"] = settings.notoken_api;
+ node["readonly mode"] = settings.readonly_mode;
+ node["fahrenheit"] = settings.fahrenheit;
+ node["dallas parasite"] = settings.dallas_parasite;
+ node["bool format"] = settings.bool_format;
+ node["bool dashboard"] = settings.bool_dashboard;
+ node["enum format"] = settings.enum_format;
+ node["analog enabled"] = settings.analog_enabled;
+ node["telnet enabled"] = settings.telnet_enabled;
+ });
+
// Devices - show EMS devices
JsonArray devices = output.createNestedArray("Devices");
for (const auto & device_class : EMSFactory::device_handlers()) {