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 <kasedy@gmail.com>
This commit is contained in:
parent
51dabb705f
commit
a59f32c420
42
README.md
42
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
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"provision_mode": 0,
|
||||
"ssid": "ESP8266-React",
|
||||
"password": "esp-react"
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"host": "test.mosquitto.org",
|
||||
"port": 1883,
|
||||
"authenticated": false,
|
||||
"username": "mqttuser",
|
||||
"password": "mqttpassword",
|
||||
"keepAlive": 16,
|
||||
"cleanSession": true,
|
||||
"maxTopicLength": 128
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"server": "time.google.com",
|
||||
"tz_label": "Europe/London",
|
||||
"tz_format": "GMT0BST,M3.5.0/1,M10.5.0"
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"port": 8266,
|
||||
"password": "esp-react"
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"jwt_secret": "esp8266-react",
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"admin": true
|
||||
},
|
||||
{
|
||||
"username": "guest",
|
||||
"password": "guest",
|
||||
"admin": false
|
||||
}
|
||||
]
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"ssid": "",
|
||||
"password": "password",
|
||||
"hostname": "esp8266-react",
|
||||
"static_ip_config": false
|
||||
}
|
44
factory_settings.ini
Normal file
44
factory_settings.ini
Normal file
@ -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\"
|
1452
interface/package-lock.json
generated
1452
interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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";
|
||||
|
11
interface/src/components/ErrorButton.tsx
Normal file
11
interface/src/components/ErrorButton.tsx
Normal file
@ -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;
|
@ -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';
|
||||
|
@ -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<SystemStatus>;
|
||||
type SystemStatusFormProps = AuthenticatedContextProps & RestFormProps<SystemStatus>;
|
||||
|
||||
class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusFormState> {
|
||||
|
||||
state: SystemStatusFormState = {
|
||||
confirmRestart: false,
|
||||
confirmFactoryReset: false,
|
||||
processing: false
|
||||
}
|
||||
|
||||
@ -95,7 +98,7 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
|
||||
Are you sure you want to restart the device?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<AutorenewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
|
||||
Restart
|
||||
</Button>
|
||||
<Button variant="contained" onClick={this.onRestartRejected} color="secondary">
|
||||
@ -131,25 +134,83 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
|
||||
});
|
||||
}
|
||||
|
||||
renderFactoryResetDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.confirmFactoryReset}
|
||||
onClose={this.onFactoryResetRejected}
|
||||
>
|
||||
<DialogTitle>Confirm Factory Reset</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
Are you sure you want to reset the device to its factory defaults?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
|
||||
Factory Reset
|
||||
</ErrorButton>
|
||||
<Button variant="contained" onClick={this.onFactoryResetRejected} color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
<List>
|
||||
{this.createListItems()}
|
||||
</List>
|
||||
<FormActions>
|
||||
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</FormButton>
|
||||
<FormButton startIcon={<AutorenewIcon />} variant="contained" color="primary" onClick={this.onRestart}>
|
||||
Restart
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} padding={1}>
|
||||
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</FormButton>
|
||||
</Box>
|
||||
{me.admin &&
|
||||
<Box flexWrap="none" padding={1} whiteSpace="nowrap">
|
||||
<FormButton startIcon={<PowerSettingsNewIcon />} variant="contained" color="primary" onClick={this.onRestart}>
|
||||
Restart
|
||||
</FormButton>
|
||||
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryReset}>
|
||||
Factory reset
|
||||
</ErrorButton>
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
{this.renderRestartDialog()}
|
||||
{this.renderFactoryResetDialog()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SystemStatusForm;
|
||||
export default withAuthenticatedContext(SystemStatusForm);
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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),
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <APSettingsService.h>
|
||||
#include <APStatus.h>
|
||||
#include <AuthenticationService.h>
|
||||
#include <FactoryResetService.h>
|
||||
#include <MqttSettingsService.h>
|
||||
#include <MqttStatus.h>
|
||||
#include <NTPSettingsService.h>
|
||||
@ -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;
|
||||
|
||||
|
17
lib/framework/ESPUtils.h
Normal file
17
lib/framework/ESPUtils.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef ESPUtils_h
|
||||
#define ESPUtils_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
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
|
34
lib/framework/FactoryResetService.cpp
Normal file
34
lib/framework/FactoryResetService.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include <FactoryResetService.h>
|
||||
|
||||
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();
|
||||
}
|
31
lib/framework/FactoryResetService.h
Normal file
31
lib/framework/FactoryResetService.h
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef FactoryResetService_h
|
||||
#define FactoryResetService_h
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <FS.h>
|
||||
|
||||
#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
|
@ -1,3 +1,6 @@
|
||||
#ifndef JsonUtils_h
|
||||
#define JsonUtils_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
#include <ArduinoJson.h>
|
||||
@ -15,3 +18,5 @@ class JsonUtils {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // end JsonUtils
|
||||
|
@ -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 <class T>
|
||||
|
@ -5,27 +5,54 @@
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <ESPUtils.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -11,11 +11,21 @@
|
||||
#include <sntp.h>
|
||||
#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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -13,10 +13,17 @@
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -3,10 +3,13 @@
|
||||
|
||||
#include <ArduinoJsonJWT.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPUtils.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <list>
|
||||
|
||||
#define DEFAULT_JWT_SECRET "esp8266-react"
|
||||
#ifndef FACTORY_JWT_SECRET
|
||||
#define FACTORY_JWT_SECRET ESPUtils::defaultDeviceValue()
|
||||
#endif
|
||||
|
||||
#define ACCESS_TOKEN_PARAMATER "access_token"
|
||||
|
||||
|
@ -5,8 +5,21 @@
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
|
||||
#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<SecuritySettings>, public
|
||||
private:
|
||||
HttpEndpoint<SecuritySettings> _httpEndpoint;
|
||||
FSPersistence<SecuritySettings> _fsPersistence;
|
||||
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
|
||||
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(FACTORY_JWT_SECRET);
|
||||
|
||||
void configureJWTHandler();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -3,18 +3,11 @@
|
||||
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
#include <ESPUtils.h>
|
||||
|
||||
#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-");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -70,4 +70,4 @@ void LightStateService::registerConfig() {
|
||||
_mqttClient->publish(configTopic.c_str(), 0, false, payload.c_str());
|
||||
|
||||
_mqttPubSub.configureTopics(stateTopic, setTopic);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user