From a59f32c420141c217ed0c39dcae3a563f10cd310 Mon Sep 17 00:00:00 2001 From: rjwats Date: Wed, 20 May 2020 00:32:49 +0100 Subject: [PATCH] Factory reset feature (#114) Implemented factory-reset feature Extract factory settings into separate ini file Hide reset/factory reset from guest user Co-authored-by: kasedy --- README.md | 42 +- data/config/apSettings.json | 5 - data/config/mqttSettings.json | 11 - data/config/ntpSettings.json | 6 - data/config/otaSettings.json | 5 - data/config/securitySettings.json | 15 - data/config/wifiSettings.json | 6 - factory_settings.ini | 44 + interface/package-lock.json | 1452 +-------------------- interface/src/api/Endpoints.ts | 1 + interface/src/components/ErrorButton.tsx | 11 + interface/src/components/index.ts | 1 + interface/src/system/SystemStatusForm.tsx | 93 +- lib/framework/APSettingsService.h | 19 +- lib/framework/ESP8266React.cpp | 1 + lib/framework/ESP8266React.h | 6 + lib/framework/ESPUtils.h | 17 + lib/framework/FactoryResetService.cpp | 34 + lib/framework/FactoryResetService.h | 31 + lib/framework/JsonUtils.h | 5 + lib/framework/MqttPubSub.h | 6 +- lib/framework/MqttSettingsService.h | 67 +- lib/framework/NTPSettingsService.h | 28 +- lib/framework/OTASettingsService.h | 21 +- lib/framework/RestartService.cpp | 8 +- lib/framework/SecurityManager.h | 5 +- lib/framework/SecuritySettingsService.h | 25 +- lib/framework/WiFiSettingsService.h | 18 +- platformio.ini | 2 + src/LightMqttSettingsService.h | 15 +- src/LightStateService.cpp | 2 +- 31 files changed, 410 insertions(+), 1592 deletions(-) delete mode 100644 data/config/apSettings.json delete mode 100644 data/config/mqttSettings.json delete mode 100644 data/config/ntpSettings.json delete mode 100644 data/config/otaSettings.json delete mode 100644 data/config/securitySettings.json delete mode 100644 data/config/wifiSettings.json create mode 100644 factory_settings.ini create mode 100644 interface/src/components/ErrorButton.tsx create mode 100644 lib/framework/ESPUtils.h create mode 100644 lib/framework/FactoryResetService.cpp create mode 100644 lib/framework/FactoryResetService.h diff --git a/README.md b/README.md index 0b45225..d32849f 100644 --- a/README.md +++ b/README.md @@ -149,40 +149,50 @@ You can enable CORS on the back end by uncommenting the -D ENABLE_CORS build fla -D CORS_ORIGIN=\"http://localhost:3000\" ``` -## Device configuration & default settings +## Factory settings -The SPIFFS image (in the ['data'](data) folder) contains a JSON settings file for each of the configurable features. +The firmware has built-in factory settings which act as default values for the various configurable services where settings are not saved on the file system. These settings can be overridden using the build flags defined in [factory_settings.ini](factory_settings.ini). -The config files can be found in the ['data/config'](data/config) directory: +Customize the settings as you see fit, for example you might configure your home WiFi network as the factory default: -File | Description ----- | ----------- -[apSettings.json](data/config/apSettings.json) | Access point settings -[mqttSettings.json](data/config/mqttSettings.json) | MQTT connection settings -[ntpSettings.json](data/config/ntpSettings.json) | NTP synchronization settings -[otaSettings.json](data/config/otaSettings.json) | OTA update configuration -[securitySettings.json](data/config/securitySettings.json) | Security settings and user credentials -[wifiSettings.json](data/config/wifiSettings.json) | WiFi connection settings - -These files can be pre-loaded with default configuration and [uploaded to the device](#uploading-the-file-system-image) if required. There are sensible defaults provided by the firmware, so this is optional. +```ini + -D FACTORY_WIFI_SSID=\"My Awesome WiFi Network\" + -D FACTORY_WIFI_PASSWORD=\"secret\" + -D FACTORY_WIFI_HOSTNAME=\"awesome_light_controller\" +``` ### Default access point settings -The default settings configure the device to bring up an access point on start up which can be used to configure the device: +By default, the factory settings configure the device to bring up an access point on start up which can be used to configure the device: * SSID: ESP8266-React * Password: esp-react ### Security settings and user credentials -The security settings and user credentials provide the following users by default: +By default, the factory settings configure two user accounts with the following credentials: Username | Password -------- | -------- admin | admin guest | guest -It is recommended that you change the JWT secret and user credentials from their defaults protect your device. You can do this in the user interface, or by modifying [securitySettings.json](data/config/securitySettings.json) before [uploading the file system image](#uploading-the-file-system-image). +It is recommended that you change the user credentials from their defaults better protect your device. You can do this in the user interface, or by modifying [factory_settings.ini](factory_settings.ini) as mentioned above. + +### Customizing the factory time zone setting + +Changing factory time zone setting is a common requirement. This requires a little effort because the time zone name and POSIX format are stored as separate values for the moment. The time zone names and POSIX formats are contained in the UI code in [TZ.ts](interface/src/ntp/TZ.ts). Take the appropriate pair of values from there, for example, for Los Angeles you would use: + +```ini + -D FACTORY_NTP_TIME_ZONE_LABEL=\"America/Los_Angeles\" + -D FACTORY_NTP_TIME_ZONE_FORMAT=\"PST8PDT,M3.2.0,M11.1.0\" +``` + +### Device ID factory defaults + +If not overridden with a build flag, the firmware will use the device ID to generate factory defaults for settings such as the JWT secret and MQTT client ID. + +> **Tip**: Random values are generally better defaults for these settings, so it is recommended you leave these flags undefined. ## Building for different devices diff --git a/data/config/apSettings.json b/data/config/apSettings.json deleted file mode 100644 index 4a6b530..0000000 --- a/data/config/apSettings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "provision_mode": 0, - "ssid": "ESP8266-React", - "password": "esp-react" -} \ No newline at end of file diff --git a/data/config/mqttSettings.json b/data/config/mqttSettings.json deleted file mode 100644 index 0f83c4c..0000000 --- a/data/config/mqttSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "enabled": false, - "host": "test.mosquitto.org", - "port": 1883, - "authenticated": false, - "username": "mqttuser", - "password": "mqttpassword", - "keepAlive": 16, - "cleanSession": true, - "maxTopicLength": 128 -} diff --git a/data/config/ntpSettings.json b/data/config/ntpSettings.json deleted file mode 100644 index 42fc0c7..0000000 --- a/data/config/ntpSettings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "enabled": true, - "server": "time.google.com", - "tz_label": "Europe/London", - "tz_format": "GMT0BST,M3.5.0/1,M10.5.0" -} \ No newline at end of file diff --git a/data/config/otaSettings.json b/data/config/otaSettings.json deleted file mode 100644 index 5fe8e45..0000000 --- a/data/config/otaSettings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "enabled": true, - "port": 8266, - "password": "esp-react" -} \ No newline at end of file diff --git a/data/config/securitySettings.json b/data/config/securitySettings.json deleted file mode 100644 index 8f9f0a7..0000000 --- a/data/config/securitySettings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "jwt_secret": "esp8266-react", - "users": [ - { - "username": "admin", - "password": "admin", - "admin": true - }, - { - "username": "guest", - "password": "guest", - "admin": false - } - ] -} \ No newline at end of file diff --git a/data/config/wifiSettings.json b/data/config/wifiSettings.json deleted file mode 100644 index 919bb55..0000000 --- a/data/config/wifiSettings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ssid": "", - "password": "password", - "hostname": "esp8266-react", - "static_ip_config": false -} \ No newline at end of file diff --git a/factory_settings.ini b/factory_settings.ini new file mode 100644 index 0000000..6ef5ae7 --- /dev/null +++ b/factory_settings.ini @@ -0,0 +1,44 @@ +[factory_settings] +build_flags = + ; WiFi settings + -D FACTORY_WIFI_SSID=\"\" + -D FACTORY_WIFI_PASSWORD=\"\" + -D FACTORY_WIFI_HOSTNAME=\"esp-react\" + + ; Access point settings + -D FACTORY_AP_SSID=\"ESP8266-React\" + -D FACTORY_AP_PASSWORD=\"esp-react\" + -D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED + + ; User credentials for admin and guest user + -D FACTORY_ADMIN_USERNAME=\"admin\" + -D FACTORY_ADMIN_PASSWORD=\"admin\" + -D FACTORY_GUEST_USERNAME=\"guest\" + -D FACTORY_GUEST_PASSWORD=\"guest\" + + ; NTP settings + -D FACTORY_NTP_ENABLED=true + -D FACTORY_NTP_TIME_ZONE_LABEL=\"Europe/London\" + -D FACTORY_NTP_TIME_ZONE_FORMAT=\"GMT0BST,M3.5.0/1,M10.5.0\" + -D FACTORY_NTP_SERVER=\"time.google.com\" + + ; OTA settings + -D FACTORY_OTA_PORT=8266 + -D FACTORY_OTA_PASSWORD=\"esp-react\" + -D FACTORY_OTA_ENABLED=true + + ; MQTT settings + -D FACTORY_MQTT_ENABLED=false + -D FACTORY_MQTT_HOST=\"test.mosquitto.org\" + -D FACTORY_MQTT_PORT=1883 + -D FACTORY_MQTT_USERNAME=\"\" + -D FACTORY_MQTT_PASSWORD=\"\" + ; if unspecified the devices hardware ID will be used + ;-D FACTORY_MQTT_CLIENT_ID=\"esp-react\" + -D FACTORY_MQTT_KEEP_ALIVE=16 + -D FACTORY_MQTT_CLEAN_SESSION=true + -D FACTORY_MQTT_MAX_TOPIC_LENGTH=128 + + ; JWT Secret + ; if unspecified the devices hardware ID will be used + ; -D FACTORY_JWT_SECRET=\"esp8266-react\" diff --git a/interface/package-lock.json b/interface/package-lock.json index 8d10251..01ebe7c 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -7087,486 +7087,10 @@ }, "dependencies": { "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "optional": true, - "requires": { - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } - } + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true } } }, @@ -13155,486 +12679,10 @@ } }, "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "optional": true, - "requires": { - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } - } + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true }, "glob-parent": { "version": "3.1.0", @@ -13977,486 +13025,10 @@ } }, "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "optional": true, - "requires": { - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } - } + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true }, "get-caller-file": { "version": "1.0.3", diff --git a/interface/src/api/Endpoints.ts b/interface/src/api/Endpoints.ts index 40a61ba..b16d564 100644 --- a/interface/src/api/Endpoints.ts +++ b/interface/src/api/Endpoints.ts @@ -16,3 +16,4 @@ export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn"; export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization"; export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings"; export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart"; +export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset"; diff --git a/interface/src/components/ErrorButton.tsx b/interface/src/components/ErrorButton.tsx new file mode 100644 index 0000000..c93cddd --- /dev/null +++ b/interface/src/components/ErrorButton.tsx @@ -0,0 +1,11 @@ +import { Button, styled } from "@material-ui/core"; + +const ErrorButton = styled(Button)(({ theme }) => ({ + color: theme.palette.getContrastText(theme.palette.error.main), + backgroundColor: theme.palette.error.main, + '&:hover': { + backgroundColor: theme.palette.error.dark, + } +})); + +export default ErrorButton; diff --git a/interface/src/components/index.ts b/interface/src/components/index.ts index e4e490e..8d712d8 100644 --- a/interface/src/components/index.ts +++ b/interface/src/components/index.ts @@ -7,6 +7,7 @@ export { default as PasswordValidator } from './PasswordValidator'; export { default as RestFormLoader } from './RestFormLoader'; export { default as SectionContent } from './SectionContent'; export { default as WebSocketFormLoader } from './WebSocketFormLoader'; +export { default as ErrorButton } from './ErrorButton'; export * from './RestFormLoader'; export * from './RestController'; diff --git a/interface/src/system/SystemStatusForm.tsx b/interface/src/system/SystemStatusForm.tsx index c9fe315..b2ccd9c 100644 --- a/interface/src/system/SystemStatusForm.tsx +++ b/interface/src/system/SystemStatusForm.tsx @@ -1,6 +1,6 @@ import React, { Component, Fragment } from 'react'; -import { Avatar, Button, Divider, Dialog, DialogTitle, DialogContent, DialogActions } from '@material-ui/core'; +import { Avatar, Button, Divider, Dialog, DialogTitle, DialogContent, DialogActions, Box } from '@material-ui/core'; import { List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'; import DevicesIcon from '@material-ui/icons/Devices'; @@ -8,26 +8,29 @@ import MemoryIcon from '@material-ui/icons/Memory'; import ShowChartIcon from '@material-ui/icons/ShowChart'; import SdStorageIcon from '@material-ui/icons/SdStorage'; import DataUsageIcon from '@material-ui/icons/DataUsage'; -import AutorenewIcon from '@material-ui/icons/Autorenew'; +import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew'; import RefreshIcon from '@material-ui/icons/Refresh'; +import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore'; -import { redirectingAuthorizedFetch } from '../authentication'; -import { RestFormProps, FormButton, FormActions } from '../components'; -import { RESTART_ENDPOINT } from '../api'; +import { redirectingAuthorizedFetch, AuthenticatedContextProps, withAuthenticatedContext } from '../authentication'; +import { RestFormProps, FormButton, ErrorButton } from '../components'; +import { FACTORY_RESET_ENDPOINT, RESTART_ENDPOINT } from '../api'; import { SystemStatus } from './types'; interface SystemStatusFormState { confirmRestart: boolean; + confirmFactoryReset: boolean; processing: boolean; } -type SystemStatusFormProps = RestFormProps; +type SystemStatusFormProps = AuthenticatedContextProps & RestFormProps; class SystemStatusForm extends Component { state: SystemStatusFormState = { confirmRestart: false, + confirmFactoryReset: false, processing: false } @@ -95,7 +98,7 @@ class SystemStatusForm extends Component - + + + ) + } + + onFactoryReset = () => { + this.setState({ confirmFactoryReset: true }); + } + + onFactoryResetRejected = () => { + this.setState({ confirmFactoryReset: false }); + } + + onFactoryResetConfirmed = () => { + this.setState({ processing: true }); + redirectingAuthorizedFetch(FACTORY_RESET_ENDPOINT, { method: 'POST' }) + .then(response => { + if (response.status === 200) { + this.props.enqueueSnackbar("Factory reset in progress.", { variant: 'error' }); + this.setState({ processing: false, confirmFactoryReset: false }); + } else { + throw Error("Invalid status code: " + response.status); + } + }) + .catch(error => { + this.props.enqueueSnackbar(error.message || "Problem factory resetting device", { variant: 'error' }); + this.setState({ processing: false, confirmRestart: false }); + }); + } + render() { + const me = this.props.authenticatedContext.me; return ( {this.createListItems()} - - } variant="contained" color="secondary" onClick={this.props.loadData}> - Refresh - - } variant="contained" color="primary" onClick={this.onRestart}> - Restart - - + + + } variant="contained" color="secondary" onClick={this.props.loadData}> + Refresh + + + {me.admin && + + } variant="contained" color="primary" onClick={this.onRestart}> + Restart + + } variant="contained" onClick={this.onFactoryReset}> + Factory reset + + + } + {this.renderRestartDialog()} + {this.renderFactoryResetDialog()} ); } } -export default SystemStatusForm; +export default withAuthenticatedContext(SystemStatusForm); diff --git a/lib/framework/APSettingsService.h b/lib/framework/APSettingsService.h index 22c4e7b..ec82b73 100644 --- a/lib/framework/APSettingsService.h +++ b/lib/framework/APSettingsService.h @@ -15,8 +15,17 @@ #define DNS_PORT 53 -#define AP_DEFAULT_SSID "ESP8266-React" -#define AP_DEFAULT_PASSWORD "esp-react" +#ifndef FACTORY_AP_SSID +#define FACTORY_AP_SSID "ESP8266-React" +#endif + +#ifndef FACTORY_AP_PASSWORD +#define FACTORY_AP_PASSWORD "esp-react" +#endif + +#ifndef FACTORY_AP_PROVISION_MODE +#define FACTORY_AP_PROVISION_MODE AP_MODE_DISCONNECTED +#endif #define AP_SETTINGS_FILE "/config/apSettings.json" #define AP_SETTINGS_SERVICE_PATH "/rest/apSettings" @@ -34,7 +43,7 @@ class APSettings { } static void deserialize(JsonObject& root, APSettings& settings) { - settings.provisionMode = root["provision_mode"] | AP_MODE_ALWAYS; + settings.provisionMode = root["provision_mode"] | FACTORY_AP_PROVISION_MODE; switch (settings.provisionMode) { case AP_MODE_ALWAYS: case AP_MODE_DISCONNECTED: @@ -43,8 +52,8 @@ class APSettings { default: settings.provisionMode = AP_MODE_ALWAYS; } - settings.ssid = root["ssid"] | AP_DEFAULT_SSID; - settings.password = root["password"] | AP_DEFAULT_PASSWORD; + settings.ssid = root["ssid"] | FACTORY_AP_SSID; + settings.password = root["password"] | FACTORY_AP_PASSWORD; } }; diff --git a/lib/framework/ESP8266React.cpp b/lib/framework/ESP8266React.cpp index a86db56..b03a7d1 100644 --- a/lib/framework/ESP8266React.cpp +++ b/lib/framework/ESP8266React.cpp @@ -8,6 +8,7 @@ ESP8266React::ESP8266React(AsyncWebServer* server, FS* fs) : _otaSettingsService(server, fs, &_securitySettingsService), _mqttSettingsService(server, fs, &_securitySettingsService), _restartService(server, &_securitySettingsService), + _factoryResetService(server, fs, &_securitySettingsService), _authenticationService(server, &_securitySettingsService), _wifiScanner(server, &_securitySettingsService), _wifiStatus(server, &_securitySettingsService), diff --git a/lib/framework/ESP8266React.h b/lib/framework/ESP8266React.h index 3603d8b..a4d95f3 100644 --- a/lib/framework/ESP8266React.h +++ b/lib/framework/ESP8266React.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,10 @@ class ESP8266React { return _mqttSettingsService.getMqttClient(); } + void factoryReset() { + _factoryResetService.factoryReset(); + } + private: SecuritySettingsService _securitySettingsService; WiFiSettingsService _wifiSettingsService; @@ -79,6 +84,7 @@ class ESP8266React { OTASettingsService _otaSettingsService; MqttSettingsService _mqttSettingsService; RestartService _restartService; + FactoryResetService _factoryResetService; AuthenticationService _authenticationService; diff --git a/lib/framework/ESPUtils.h b/lib/framework/ESPUtils.h new file mode 100644 index 0000000..834459d --- /dev/null +++ b/lib/framework/ESPUtils.h @@ -0,0 +1,17 @@ +#ifndef ESPUtils_h +#define ESPUtils_h + +#include + +class ESPUtils { + public: + static String defaultDeviceValue(String prefix = "") { +#ifdef ESP32 + return prefix + String((unsigned long)ESP.getEfuseMac(), HEX); +#elif defined(ESP8266) + return prefix + String(ESP.getChipId(), HEX); +#endif + } +}; + +#endif // end ESPUtils diff --git a/lib/framework/FactoryResetService.cpp b/lib/framework/FactoryResetService.cpp new file mode 100644 index 0000000..4f45564 --- /dev/null +++ b/lib/framework/FactoryResetService.cpp @@ -0,0 +1,34 @@ +#include + +using namespace std::placeholders; + +FactoryResetService::FactoryResetService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : fs(fs) { + server->on(FACTORY_RESET_SERVICE_PATH, + HTTP_POST, + securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1), + AuthenticationPredicates::IS_ADMIN)); +} + +void FactoryResetService::handleRequest(AsyncWebServerRequest* request) { + request->onDisconnect(std::bind(&FactoryResetService::factoryReset, this)); + request->send(200); +} + +/** + * Delete function assumes that all files are stored flat, within the config directory + */ +void FactoryResetService::factoryReset() { +#ifdef ESP32 + File root = fs->open(FS_CONFIG_DIRECTORY); + File file; + while (file = root.openNextFile()) { + fs->remove(file.name()); + } +#elif defined(ESP8266) + Dir configDirectory = fs->openDir(FS_CONFIG_DIRECTORY); + while (configDirectory.next()) { + fs->remove(configDirectory.fileName()); + } +#endif + ESP.restart(); +} diff --git a/lib/framework/FactoryResetService.h b/lib/framework/FactoryResetService.h new file mode 100644 index 0000000..81a968e --- /dev/null +++ b/lib/framework/FactoryResetService.h @@ -0,0 +1,31 @@ +#ifndef FactoryResetService_h +#define FactoryResetService_h + +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif + +#include +#include +#include + +#define FS_CONFIG_DIRECTORY "/config" +#define FACTORY_RESET_SERVICE_PATH "/rest/factoryReset" + +class FactoryResetService { + FS* fs; + + public: + FactoryResetService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); + + void factoryReset(); + + private: + void handleRequest(AsyncWebServerRequest* request); +}; + +#endif // end FactoryResetService_h diff --git a/lib/framework/JsonUtils.h b/lib/framework/JsonUtils.h index e34599d..74903d1 100644 --- a/lib/framework/JsonUtils.h +++ b/lib/framework/JsonUtils.h @@ -1,3 +1,6 @@ +#ifndef JsonUtils_h +#define JsonUtils_h + #include #include #include @@ -15,3 +18,5 @@ class JsonUtils { } } }; + +#endif // end JsonUtils diff --git a/lib/framework/MqttPubSub.h b/lib/framework/MqttPubSub.h index 88af8af..2403a90 100644 --- a/lib/framework/MqttPubSub.h +++ b/lib/framework/MqttPubSub.h @@ -22,8 +22,10 @@ class MqttConnector { virtual void onConnect() = 0; -public: - inline AsyncMqttClient* getMqttClient() const { return _mqttClient; } + public: + inline AsyncMqttClient* getMqttClient() const { + return _mqttClient; + } }; template diff --git a/lib/framework/MqttSettingsService.h b/lib/framework/MqttSettingsService.h index 552b1ec..c9aaf87 100644 --- a/lib/framework/MqttSettingsService.h +++ b/lib/framework/MqttSettingsService.h @@ -5,27 +5,54 @@ #include #include #include +#include #define MQTT_RECONNECTION_DELAY 5000 #define MQTT_SETTINGS_FILE "/config/mqttSettings.json" #define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings" -#define MQTT_SETTINGS_SERVICE_DEFAULT_ENABLED false -#define MQTT_SETTINGS_SERVICE_DEFAULT_HOST "test.mosquitto.org" -#define MQTT_SETTINGS_SERVICE_DEFAULT_PORT 1883 -#define MQTT_SETTINGS_SERVICE_DEFAULT_USERNAME "" -#define MQTT_SETTINGS_SERVICE_DEFAULT_PASSWORD "" -#define MQTT_SETTINGS_SERVICE_DEFAULT_CLIENT_ID generateClientId() -#define MQTT_SETTINGS_SERVICE_DEFAULT_KEEP_ALIVE 16 -#define MQTT_SETTINGS_SERVICE_DEFAULT_CLEAN_SESSION true -#define MQTT_SETTINGS_SERVICE_DEFAULT_MAX_TOPIC_LENGTH 128 +#ifndef FACTORY_MQTT_ENABLED +#define FACTORY_MQTT_ENABLED false +#endif + +#ifndef FACTORY_MQTT_HOST +#define FACTORY_MQTT_HOST "test.mosquitto.org" +#endif + +#ifndef FACTORY_MQTT_PORT +#define FACTORY_MQTT_PORT 1883 +#endif + +#ifndef FACTORY_MQTT_USERNAME +#define FACTORY_MQTT_USERNAME "" +#endif + +#ifndef FACTORY_MQTT_PASSWORD +#define FACTORY_MQTT_PASSWORD "" +#endif + +#ifndef FACTORY_MQTT_CLIENT_ID +#define FACTORY_MQTT_CLIENT_ID generateClientId() +#endif + +#ifndef FACTORY_MQTT_KEEP_ALIVE +#define FACTORY_MQTT_KEEP_ALIVE 16 +#endif + +#ifndef FACTORY_MQTT_CLEAN_SESSION +#define FACTORY_MQTT_CLEAN_SESSION true +#endif + +#ifndef FACTORY_MQTT_MAX_TOPIC_LENGTH +#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128 +#endif static String generateClientId() { #ifdef ESP32 - return "esp32-" + String((unsigned long)ESP.getEfuseMac(), HEX); + return ESPUtils::defaultDeviceValue("esp32-"); #elif defined(ESP8266) - return "esp8266-" + String(ESP.getChipId(), HEX); + return ESPUtils::defaultDeviceValue("esp8266-"); #endif } @@ -61,15 +88,15 @@ class MqttSettings { } static void deserialize(JsonObject& root, MqttSettings& settings) { - settings.enabled = root["enabled"] | MQTT_SETTINGS_SERVICE_DEFAULT_ENABLED; - settings.host = root["host"] | MQTT_SETTINGS_SERVICE_DEFAULT_HOST; - settings.port = root["port"] | MQTT_SETTINGS_SERVICE_DEFAULT_PORT; - settings.username = root["username"] | MQTT_SETTINGS_SERVICE_DEFAULT_USERNAME; - settings.password = root["password"] | MQTT_SETTINGS_SERVICE_DEFAULT_PASSWORD; - settings.clientId = root["client_id"] | MQTT_SETTINGS_SERVICE_DEFAULT_CLIENT_ID; - settings.keepAlive = root["keep_alive"] | MQTT_SETTINGS_SERVICE_DEFAULT_KEEP_ALIVE; - settings.cleanSession = root["clean_session"] | MQTT_SETTINGS_SERVICE_DEFAULT_CLEAN_SESSION; - settings.maxTopicLength = root["max_topic_length"] | MQTT_SETTINGS_SERVICE_DEFAULT_MAX_TOPIC_LENGTH; + settings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED; + settings.host = root["host"] | FACTORY_MQTT_HOST; + settings.port = root["port"] | FACTORY_MQTT_PORT; + settings.username = root["username"] | FACTORY_MQTT_USERNAME; + settings.password = root["password"] | FACTORY_MQTT_PASSWORD; + settings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID; + settings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE; + settings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION; + settings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH; } }; diff --git a/lib/framework/NTPSettingsService.h b/lib/framework/NTPSettingsService.h index 1782fc7..c2974bc 100644 --- a/lib/framework/NTPSettingsService.h +++ b/lib/framework/NTPSettingsService.h @@ -11,11 +11,21 @@ #include #endif -// default time zone -#define NTP_SETTINGS_SERVICE_DEFAULT_ENABLED true -#define NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL "Europe/London" -#define NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0" -#define NTP_SETTINGS_SERVICE_DEFAULT_SERVER "time.google.com" +#ifndef FACTORY_NTP_ENABLED +#define FACTORY_NTP_ENABLED true +#endif + +#ifndef FACTORY_NTP_TIME_ZONE_LABEL +#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London" +#endif + +#ifndef FACTORY_NTP_TIME_ZONE_FORMAT +#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0" +#endif + +#ifndef FACTORY_NTP_SERVER +#define FACTORY_NTP_SERVER "time.google.com" +#endif #define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings" @@ -35,10 +45,10 @@ class NTPSettings { } static void deserialize(JsonObject& root, NTPSettings& settings) { - settings.enabled = root["enabled"] | NTP_SETTINGS_SERVICE_DEFAULT_ENABLED; - settings.server = root["server"] | NTP_SETTINGS_SERVICE_DEFAULT_SERVER; - settings.tzLabel = root["tz_label"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL; - settings.tzFormat = root["tz_format"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_FORMAT; + settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED; + settings.server = root["server"] | FACTORY_NTP_SERVER; + settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL; + settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT; } }; diff --git a/lib/framework/OTASettingsService.h b/lib/framework/OTASettingsService.h index 1c7fbb8..87fc3f3 100644 --- a/lib/framework/OTASettingsService.h +++ b/lib/framework/OTASettingsService.h @@ -13,10 +13,17 @@ #include #include -// Emergency defaults -#define DEFAULT_OTA_PORT 8266 -#define DEFAULT_OTA_PASSWORD "esp-react" -#define DEFAULT_OTA_ENABLED true +#ifndef FACTORY_OTA_PORT +#define FACTORY_OTA_PORT 8266 +#endif + +#ifndef FACTORY_OTA_PASSWORD +#define FACTORY_OTA_PASSWORD "esp-react" +#endif + +#ifndef FACTORY_OTA_ENABLED +#define FACTORY_OTA_ENABLED true +#endif #define OTA_SETTINGS_FILE "/config/otaSettings.json" #define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings" @@ -34,9 +41,9 @@ class OTASettings { } static void deserialize(JsonObject& root, OTASettings& settings) { - settings.enabled = root["enabled"] | DEFAULT_OTA_ENABLED; - settings.port = root["port"] | DEFAULT_OTA_PORT; - settings.password = root["password"] | DEFAULT_OTA_PASSWORD; + settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED; + settings.port = root["port"] | FACTORY_OTA_PORT; + settings.password = root["password"] | FACTORY_OTA_PASSWORD; } }; diff --git a/lib/framework/RestartService.cpp b/lib/framework/RestartService.cpp index 8ef5751..3eb0df8 100644 --- a/lib/framework/RestartService.cpp +++ b/lib/framework/RestartService.cpp @@ -8,12 +8,6 @@ RestartService::RestartService(AsyncWebServer* server, SecurityManager* security } void RestartService::restart(AsyncWebServerRequest* request) { - request->onDisconnect([]() { -#ifdef ESP32 - ESP.restart(); -#elif defined(ESP8266) - ESP.reset(); -#endif - }); + request->onDisconnect([]() { ESP.restart(); }); request->send(200); } diff --git a/lib/framework/SecurityManager.h b/lib/framework/SecurityManager.h index 1f3fa11..229ad4f 100644 --- a/lib/framework/SecurityManager.h +++ b/lib/framework/SecurityManager.h @@ -3,10 +3,13 @@ #include #include +#include #include #include -#define DEFAULT_JWT_SECRET "esp8266-react" +#ifndef FACTORY_JWT_SECRET +#define FACTORY_JWT_SECRET ESPUtils::defaultDeviceValue() +#endif #define ACCESS_TOKEN_PARAMATER "access_token" diff --git a/lib/framework/SecuritySettingsService.h b/lib/framework/SecuritySettingsService.h index 02213db..2e0720c 100644 --- a/lib/framework/SecuritySettingsService.h +++ b/lib/framework/SecuritySettingsService.h @@ -5,8 +5,21 @@ #include #include -#define DEFAULT_ADMIN_USERNAME "admin" -#define DEFAULT_GUEST_USERNAME "guest" +#ifndef FACTORY_ADMIN_USERNAME +#define FACTORY_ADMIN_USERNAME "admin" +#endif + +#ifndef FACTORY_ADMIN_PASSWORD +#define FACTORY_ADMIN_PASSWORD "admin" +#endif + +#ifndef FACTORY_GUEST_USERNAME +#define FACTORY_GUEST_USERNAME "guest" +#endif + +#ifndef FACTORY_GUEST_PASSWORD +#define FACTORY_GUEST_PASSWORD "guest" +#endif #define SECURITY_SETTINGS_FILE "/config/securitySettings.json" #define SECURITY_SETTINGS_PATH "/rest/securitySettings" @@ -32,7 +45,7 @@ class SecuritySettings { static void deserialize(JsonObject& root, SecuritySettings& settings) { // secret - settings.jwtSecret = root["jwt_secret"] | DEFAULT_JWT_SECRET; + settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET; // users settings.users.clear(); @@ -41,8 +54,8 @@ class SecuritySettings { settings.users.push_back(User(user["username"], user["password"], user["admin"])); } } else { - settings.users.push_back(User(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_USERNAME, true)); - settings.users.push_back(User(DEFAULT_GUEST_USERNAME, DEFAULT_GUEST_USERNAME, false)); + settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true)); + settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false)); } } }; @@ -64,7 +77,7 @@ class SecuritySettingsService : public StatefulService, public private: HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET); + ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(FACTORY_JWT_SECRET); void configureJWTHandler(); diff --git a/lib/framework/WiFiSettingsService.h b/lib/framework/WiFiSettingsService.h index 6460ccd..c818ab1 100644 --- a/lib/framework/WiFiSettingsService.h +++ b/lib/framework/WiFiSettingsService.h @@ -10,6 +10,18 @@ #define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings" #define WIFI_RECONNECTION_DELAY 1000 * 30 +#ifndef FACTORY_WIFI_SSID +#define FACTORY_WIFI_SSID "" +#endif + +#ifndef FACTORY_WIFI_PASSWORD +#define FACTORY_WIFI_PASSWORD "" +#endif + +#ifndef FACTORY_WIFI_HOSTNAME +#define FACTORY_WIFI_HOSTNAME "" +#endif + class WiFiSettings { public: // core wifi configuration @@ -41,9 +53,9 @@ class WiFiSettings { } static void deserialize(JsonObject& root, WiFiSettings& settings) { - settings.ssid = root["ssid"] | ""; - settings.password = root["password"] | ""; - settings.hostname = root["hostname"] | ""; + settings.ssid = root["ssid"] | FACTORY_WIFI_SSID; + settings.password = root["password"] | FACTORY_WIFI_PASSWORD; + settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME; settings.staticIPConfig = root["static_ip_config"] | false; // extended settings diff --git a/platformio.ini b/platformio.ini index d6bcba1..1d34299 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,9 +1,11 @@ [platformio] +extra_configs = factory_settings.ini default_envs = esp12e ;default_envs = node32s [env] build_flags= + ${factory_settings.build_flags} -D NO_GLOBAL_ARDUINOOTA ; Uncomment ENABLE_CORS to enable Cross-Origin Resource Sharing (required for local React development) ;-D ENABLE_CORS diff --git a/src/LightMqttSettingsService.h b/src/LightMqttSettingsService.h index a21b245..3b6a3f1 100644 --- a/src/LightMqttSettingsService.h +++ b/src/LightMqttSettingsService.h @@ -3,18 +3,11 @@ #include #include +#include #define LIGHT_BROKER_SETTINGS_FILE "/config/brokerSettings.json" #define LIGHT_BROKER_SETTINGS_PATH "/rest/brokerSettings" -static String defaultDeviceValue(String prefix = "") { -#ifdef ESP32 - return prefix + String((unsigned long)ESP.getEfuseMac(), HEX); -#elif defined(ESP8266) - return prefix + String(ESP.getChipId(), HEX); -#endif -} - class LightMqttSettings { public: String mqttPath; @@ -28,9 +21,9 @@ class LightMqttSettings { } static void deserialize(JsonObject& root, LightMqttSettings& settings) { - settings.mqttPath = root["mqtt_path"] | defaultDeviceValue("homeassistant/light/"); - settings.name = root["name"] | defaultDeviceValue("light-"); - settings.uniqueId = root["unique_id"] | defaultDeviceValue("light-"); + settings.mqttPath = root["mqtt_path"] | ESPUtils::defaultDeviceValue("homeassistant/light/"); + settings.name = root["name"] | ESPUtils::defaultDeviceValue("light-"); + settings.uniqueId = root["unique_id"] | ESPUtils::defaultDeviceValue("light-"); } }; diff --git a/src/LightStateService.cpp b/src/LightStateService.cpp index 5853525..b2dd251 100644 --- a/src/LightStateService.cpp +++ b/src/LightStateService.cpp @@ -70,4 +70,4 @@ void LightStateService::registerConfig() { _mqttClient->publish(configTopic.c_str(), 0, false, payload.c_str()); _mqttPubSub.configureTopics(stateTopic, setTopic); -} \ No newline at end of file +}