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:
rjwats 2020-05-20 00:32:49 +01:00 committed by GitHub
parent 51dabb705f
commit a59f32c420
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 410 additions and 1592 deletions

View File

@ -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\" -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 ```ini
---- | ----------- -D FACTORY_WIFI_SSID=\"My Awesome WiFi Network\"
[apSettings.json](data/config/apSettings.json) | Access point settings -D FACTORY_WIFI_PASSWORD=\"secret\"
[mqttSettings.json](data/config/mqttSettings.json) | MQTT connection settings -D FACTORY_WIFI_HOSTNAME=\"awesome_light_controller\"
[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.
### Default access point settings ### 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 * SSID: ESP8266-React
* Password: esp-react * Password: esp-react
### Security settings and user credentials ### 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 Username | Password
-------- | -------- -------- | --------
admin | admin admin | admin
guest | guest 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 ## Building for different devices

View File

@ -1,5 +0,0 @@
{
"provision_mode": 0,
"ssid": "ESP8266-React",
"password": "esp-react"
}

View File

@ -1,11 +0,0 @@
{
"enabled": false,
"host": "test.mosquitto.org",
"port": 1883,
"authenticated": false,
"username": "mqttuser",
"password": "mqttpassword",
"keepAlive": 16,
"cleanSession": true,
"maxTopicLength": 128
}

View File

@ -1,6 +0,0 @@
{
"enabled": true,
"server": "time.google.com",
"tz_label": "Europe/London",
"tz_format": "GMT0BST,M3.5.0/1,M10.5.0"
}

View File

@ -1,5 +0,0 @@
{
"enabled": true,
"port": 8266,
"password": "esp-react"
}

View File

@ -1,15 +0,0 @@
{
"jwt_secret": "esp8266-react",
"users": [
{
"username": "admin",
"password": "admin",
"admin": true
},
{
"username": "guest",
"password": "guest",
"admin": false
}
]
}

View File

@ -1,6 +0,0 @@
{
"ssid": "",
"password": "password",
"hostname": "esp8266-react",
"static_ip_config": false
}

44
factory_settings.ini Normal file
View 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\"

File diff suppressed because it is too large Load Diff

View File

@ -16,3 +16,4 @@ export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn";
export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization"; export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization";
export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings"; export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings";
export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart"; export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart";
export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset";

View 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;

View File

@ -7,6 +7,7 @@ export { default as PasswordValidator } from './PasswordValidator';
export { default as RestFormLoader } from './RestFormLoader'; export { default as RestFormLoader } from './RestFormLoader';
export { default as SectionContent } from './SectionContent'; export { default as SectionContent } from './SectionContent';
export { default as WebSocketFormLoader } from './WebSocketFormLoader'; export { default as WebSocketFormLoader } from './WebSocketFormLoader';
export { default as ErrorButton } from './ErrorButton';
export * from './RestFormLoader'; export * from './RestFormLoader';
export * from './RestController'; export * from './RestController';

View File

@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react'; 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 { List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
import DevicesIcon from '@material-ui/icons/Devices'; 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 ShowChartIcon from '@material-ui/icons/ShowChart';
import SdStorageIcon from '@material-ui/icons/SdStorage'; import SdStorageIcon from '@material-ui/icons/SdStorage';
import DataUsageIcon from '@material-ui/icons/DataUsage'; 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 RefreshIcon from '@material-ui/icons/Refresh';
import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
import { redirectingAuthorizedFetch } from '../authentication'; import { redirectingAuthorizedFetch, AuthenticatedContextProps, withAuthenticatedContext } from '../authentication';
import { RestFormProps, FormButton, FormActions } from '../components'; import { RestFormProps, FormButton, ErrorButton } from '../components';
import { RESTART_ENDPOINT } from '../api'; import { FACTORY_RESET_ENDPOINT, RESTART_ENDPOINT } from '../api';
import { SystemStatus } from './types'; import { SystemStatus } from './types';
interface SystemStatusFormState { interface SystemStatusFormState {
confirmRestart: boolean; confirmRestart: boolean;
confirmFactoryReset: boolean;
processing: boolean; processing: boolean;
} }
type SystemStatusFormProps = RestFormProps<SystemStatus>; type SystemStatusFormProps = AuthenticatedContextProps & RestFormProps<SystemStatus>;
class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusFormState> { class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusFormState> {
state: SystemStatusFormState = { state: SystemStatusFormState = {
confirmRestart: false, confirmRestart: false,
confirmFactoryReset: false,
processing: false processing: false
} }
@ -95,7 +98,7 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
Are you sure you want to restart the device? Are you sure you want to restart the device?
</DialogContent> </DialogContent>
<DialogActions> <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 Restart
</Button> </Button>
<Button variant="contained" onClick={this.onRestartRejected} color="secondary"> <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() { render() {
const me = this.props.authenticatedContext.me;
return ( return (
<Fragment> <Fragment>
<List> <List>
{this.createListItems()} {this.createListItems()}
</List> </List>
<FormActions> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1} padding={1}>
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}> <FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
Refresh Refresh
</FormButton> </FormButton>
<FormButton startIcon={<AutorenewIcon />} variant="contained" color="primary" onClick={this.onRestart}> </Box>
{me.admin &&
<Box flexWrap="none" padding={1} whiteSpace="nowrap">
<FormButton startIcon={<PowerSettingsNewIcon />} variant="contained" color="primary" onClick={this.onRestart}>
Restart Restart
</FormButton> </FormButton>
</FormActions> <ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryReset}>
Factory reset
</ErrorButton>
</Box>
}
</Box>
{this.renderRestartDialog()} {this.renderRestartDialog()}
{this.renderFactoryResetDialog()}
</Fragment> </Fragment>
); );
} }
} }
export default SystemStatusForm; export default withAuthenticatedContext(SystemStatusForm);

View File

@ -15,8 +15,17 @@
#define DNS_PORT 53 #define DNS_PORT 53
#define AP_DEFAULT_SSID "ESP8266-React" #ifndef FACTORY_AP_SSID
#define AP_DEFAULT_PASSWORD "esp-react" #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_FILE "/config/apSettings.json"
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings" #define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
@ -34,7 +43,7 @@ class APSettings {
} }
static void deserialize(JsonObject& root, APSettings& settings) { 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) { switch (settings.provisionMode) {
case AP_MODE_ALWAYS: case AP_MODE_ALWAYS:
case AP_MODE_DISCONNECTED: case AP_MODE_DISCONNECTED:
@ -43,8 +52,8 @@ class APSettings {
default: default:
settings.provisionMode = AP_MODE_ALWAYS; settings.provisionMode = AP_MODE_ALWAYS;
} }
settings.ssid = root["ssid"] | AP_DEFAULT_SSID; settings.ssid = root["ssid"] | FACTORY_AP_SSID;
settings.password = root["password"] | AP_DEFAULT_PASSWORD; settings.password = root["password"] | FACTORY_AP_PASSWORD;
} }
}; };

View File

@ -8,6 +8,7 @@ ESP8266React::ESP8266React(AsyncWebServer* server, FS* fs) :
_otaSettingsService(server, fs, &_securitySettingsService), _otaSettingsService(server, fs, &_securitySettingsService),
_mqttSettingsService(server, fs, &_securitySettingsService), _mqttSettingsService(server, fs, &_securitySettingsService),
_restartService(server, &_securitySettingsService), _restartService(server, &_securitySettingsService),
_factoryResetService(server, fs, &_securitySettingsService),
_authenticationService(server, &_securitySettingsService), _authenticationService(server, &_securitySettingsService),
_wifiScanner(server, &_securitySettingsService), _wifiScanner(server, &_securitySettingsService),
_wifiStatus(server, &_securitySettingsService), _wifiStatus(server, &_securitySettingsService),

View File

@ -16,6 +16,7 @@
#include <APSettingsService.h> #include <APSettingsService.h>
#include <APStatus.h> #include <APStatus.h>
#include <AuthenticationService.h> #include <AuthenticationService.h>
#include <FactoryResetService.h>
#include <MqttSettingsService.h> #include <MqttSettingsService.h>
#include <MqttStatus.h> #include <MqttStatus.h>
#include <NTPSettingsService.h> #include <NTPSettingsService.h>
@ -71,6 +72,10 @@ class ESP8266React {
return _mqttSettingsService.getMqttClient(); return _mqttSettingsService.getMqttClient();
} }
void factoryReset() {
_factoryResetService.factoryReset();
}
private: private:
SecuritySettingsService _securitySettingsService; SecuritySettingsService _securitySettingsService;
WiFiSettingsService _wifiSettingsService; WiFiSettingsService _wifiSettingsService;
@ -79,6 +84,7 @@ class ESP8266React {
OTASettingsService _otaSettingsService; OTASettingsService _otaSettingsService;
MqttSettingsService _mqttSettingsService; MqttSettingsService _mqttSettingsService;
RestartService _restartService; RestartService _restartService;
FactoryResetService _factoryResetService;
AuthenticationService _authenticationService; AuthenticationService _authenticationService;

17
lib/framework/ESPUtils.h Normal file
View 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

View 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();
}

View 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

View File

@ -1,3 +1,6 @@
#ifndef JsonUtils_h
#define JsonUtils_h
#include <Arduino.h> #include <Arduino.h>
#include <IPAddress.h> #include <IPAddress.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
@ -15,3 +18,5 @@ class JsonUtils {
} }
} }
}; };
#endif // end JsonUtils

View File

@ -23,7 +23,9 @@ class MqttConnector {
virtual void onConnect() = 0; virtual void onConnect() = 0;
public: public:
inline AsyncMqttClient* getMqttClient() const { return _mqttClient; } inline AsyncMqttClient* getMqttClient() const {
return _mqttClient;
}
}; };
template <class T> template <class T>

View File

@ -5,27 +5,54 @@
#include <HttpEndpoint.h> #include <HttpEndpoint.h>
#include <FSPersistence.h> #include <FSPersistence.h>
#include <AsyncMqttClient.h> #include <AsyncMqttClient.h>
#include <ESPUtils.h>
#define MQTT_RECONNECTION_DELAY 5000 #define MQTT_RECONNECTION_DELAY 5000
#define MQTT_SETTINGS_FILE "/config/mqttSettings.json" #define MQTT_SETTINGS_FILE "/config/mqttSettings.json"
#define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings" #define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings"
#define MQTT_SETTINGS_SERVICE_DEFAULT_ENABLED false #ifndef FACTORY_MQTT_ENABLED
#define MQTT_SETTINGS_SERVICE_DEFAULT_HOST "test.mosquitto.org" #define FACTORY_MQTT_ENABLED false
#define MQTT_SETTINGS_SERVICE_DEFAULT_PORT 1883 #endif
#define MQTT_SETTINGS_SERVICE_DEFAULT_USERNAME ""
#define MQTT_SETTINGS_SERVICE_DEFAULT_PASSWORD "" #ifndef FACTORY_MQTT_HOST
#define MQTT_SETTINGS_SERVICE_DEFAULT_CLIENT_ID generateClientId() #define FACTORY_MQTT_HOST "test.mosquitto.org"
#define MQTT_SETTINGS_SERVICE_DEFAULT_KEEP_ALIVE 16 #endif
#define MQTT_SETTINGS_SERVICE_DEFAULT_CLEAN_SESSION true
#define MQTT_SETTINGS_SERVICE_DEFAULT_MAX_TOPIC_LENGTH 128 #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() { static String generateClientId() {
#ifdef ESP32 #ifdef ESP32
return "esp32-" + String((unsigned long)ESP.getEfuseMac(), HEX); return ESPUtils::defaultDeviceValue("esp32-");
#elif defined(ESP8266) #elif defined(ESP8266)
return "esp8266-" + String(ESP.getChipId(), HEX); return ESPUtils::defaultDeviceValue("esp8266-");
#endif #endif
} }
@ -61,15 +88,15 @@ class MqttSettings {
} }
static void deserialize(JsonObject& root, MqttSettings& settings) { static void deserialize(JsonObject& root, MqttSettings& settings) {
settings.enabled = root["enabled"] | MQTT_SETTINGS_SERVICE_DEFAULT_ENABLED; settings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
settings.host = root["host"] | MQTT_SETTINGS_SERVICE_DEFAULT_HOST; settings.host = root["host"] | FACTORY_MQTT_HOST;
settings.port = root["port"] | MQTT_SETTINGS_SERVICE_DEFAULT_PORT; settings.port = root["port"] | FACTORY_MQTT_PORT;
settings.username = root["username"] | MQTT_SETTINGS_SERVICE_DEFAULT_USERNAME; settings.username = root["username"] | FACTORY_MQTT_USERNAME;
settings.password = root["password"] | MQTT_SETTINGS_SERVICE_DEFAULT_PASSWORD; settings.password = root["password"] | FACTORY_MQTT_PASSWORD;
settings.clientId = root["client_id"] | MQTT_SETTINGS_SERVICE_DEFAULT_CLIENT_ID; settings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID;
settings.keepAlive = root["keep_alive"] | MQTT_SETTINGS_SERVICE_DEFAULT_KEEP_ALIVE; settings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
settings.cleanSession = root["clean_session"] | MQTT_SETTINGS_SERVICE_DEFAULT_CLEAN_SESSION; settings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
settings.maxTopicLength = root["max_topic_length"] | MQTT_SETTINGS_SERVICE_DEFAULT_MAX_TOPIC_LENGTH; settings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
} }
}; };

View File

@ -11,11 +11,21 @@
#include <sntp.h> #include <sntp.h>
#endif #endif
// default time zone #ifndef FACTORY_NTP_ENABLED
#define NTP_SETTINGS_SERVICE_DEFAULT_ENABLED true #define FACTORY_NTP_ENABLED true
#define NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL "Europe/London" #endif
#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_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_FILE "/config/ntpSettings.json"
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings" #define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
@ -35,10 +45,10 @@ class NTPSettings {
} }
static void deserialize(JsonObject& root, NTPSettings& settings) { static void deserialize(JsonObject& root, NTPSettings& settings) {
settings.enabled = root["enabled"] | NTP_SETTINGS_SERVICE_DEFAULT_ENABLED; settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | NTP_SETTINGS_SERVICE_DEFAULT_SERVER; settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL; settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormat = root["tz_format"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_FORMAT; settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
} }
}; };

View File

@ -13,10 +13,17 @@
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
// Emergency defaults #ifndef FACTORY_OTA_PORT
#define DEFAULT_OTA_PORT 8266 #define FACTORY_OTA_PORT 8266
#define DEFAULT_OTA_PASSWORD "esp-react" #endif
#define DEFAULT_OTA_ENABLED true
#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_FILE "/config/otaSettings.json"
#define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings" #define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings"
@ -34,9 +41,9 @@ class OTASettings {
} }
static void deserialize(JsonObject& root, OTASettings& settings) { static void deserialize(JsonObject& root, OTASettings& settings) {
settings.enabled = root["enabled"] | DEFAULT_OTA_ENABLED; settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED;
settings.port = root["port"] | DEFAULT_OTA_PORT; settings.port = root["port"] | FACTORY_OTA_PORT;
settings.password = root["password"] | DEFAULT_OTA_PASSWORD; settings.password = root["password"] | FACTORY_OTA_PASSWORD;
} }
}; };

View File

@ -8,12 +8,6 @@ RestartService::RestartService(AsyncWebServer* server, SecurityManager* security
} }
void RestartService::restart(AsyncWebServerRequest* request) { void RestartService::restart(AsyncWebServerRequest* request) {
request->onDisconnect([]() { request->onDisconnect([]() { ESP.restart(); });
#ifdef ESP32
ESP.restart();
#elif defined(ESP8266)
ESP.reset();
#endif
});
request->send(200); request->send(200);
} }

View File

@ -3,10 +3,13 @@
#include <ArduinoJsonJWT.h> #include <ArduinoJsonJWT.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <ESPUtils.h>
#include <AsyncJson.h> #include <AsyncJson.h>
#include <list> #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" #define ACCESS_TOKEN_PARAMATER "access_token"

View File

@ -5,8 +5,21 @@
#include <HttpEndpoint.h> #include <HttpEndpoint.h>
#include <FSPersistence.h> #include <FSPersistence.h>
#define DEFAULT_ADMIN_USERNAME "admin" #ifndef FACTORY_ADMIN_USERNAME
#define DEFAULT_GUEST_USERNAME "guest" #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_FILE "/config/securitySettings.json"
#define SECURITY_SETTINGS_PATH "/rest/securitySettings" #define SECURITY_SETTINGS_PATH "/rest/securitySettings"
@ -32,7 +45,7 @@ class SecuritySettings {
static void deserialize(JsonObject& root, SecuritySettings& settings) { static void deserialize(JsonObject& root, SecuritySettings& settings) {
// secret // secret
settings.jwtSecret = root["jwt_secret"] | DEFAULT_JWT_SECRET; settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET;
// users // users
settings.users.clear(); settings.users.clear();
@ -41,8 +54,8 @@ class SecuritySettings {
settings.users.push_back(User(user["username"], user["password"], user["admin"])); settings.users.push_back(User(user["username"], user["password"], user["admin"]));
} }
} else { } else {
settings.users.push_back(User(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_USERNAME, true)); settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true));
settings.users.push_back(User(DEFAULT_GUEST_USERNAME, DEFAULT_GUEST_USERNAME, false)); settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false));
} }
} }
}; };
@ -64,7 +77,7 @@ class SecuritySettingsService : public StatefulService<SecuritySettings>, public
private: private:
HttpEndpoint<SecuritySettings> _httpEndpoint; HttpEndpoint<SecuritySettings> _httpEndpoint;
FSPersistence<SecuritySettings> _fsPersistence; FSPersistence<SecuritySettings> _fsPersistence;
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET); ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(FACTORY_JWT_SECRET);
void configureJWTHandler(); void configureJWTHandler();

View File

@ -10,6 +10,18 @@
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings" #define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
#define WIFI_RECONNECTION_DELAY 1000 * 30 #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 { class WiFiSettings {
public: public:
// core wifi configuration // core wifi configuration
@ -41,9 +53,9 @@ class WiFiSettings {
} }
static void deserialize(JsonObject& root, WiFiSettings& settings) { static void deserialize(JsonObject& root, WiFiSettings& settings) {
settings.ssid = root["ssid"] | ""; settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
settings.password = root["password"] | ""; settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | ""; settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
settings.staticIPConfig = root["static_ip_config"] | false; settings.staticIPConfig = root["static_ip_config"] | false;
// extended settings // extended settings

View File

@ -1,9 +1,11 @@
[platformio] [platformio]
extra_configs = factory_settings.ini
default_envs = esp12e default_envs = esp12e
;default_envs = node32s ;default_envs = node32s
[env] [env]
build_flags= build_flags=
${factory_settings.build_flags}
-D NO_GLOBAL_ARDUINOOTA -D NO_GLOBAL_ARDUINOOTA
; Uncomment ENABLE_CORS to enable Cross-Origin Resource Sharing (required for local React development) ; Uncomment ENABLE_CORS to enable Cross-Origin Resource Sharing (required for local React development)
;-D ENABLE_CORS ;-D ENABLE_CORS

View File

@ -3,18 +3,11 @@
#include <HttpEndpoint.h> #include <HttpEndpoint.h>
#include <FSPersistence.h> #include <FSPersistence.h>
#include <ESPUtils.h>
#define LIGHT_BROKER_SETTINGS_FILE "/config/brokerSettings.json" #define LIGHT_BROKER_SETTINGS_FILE "/config/brokerSettings.json"
#define LIGHT_BROKER_SETTINGS_PATH "/rest/brokerSettings" #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 { class LightMqttSettings {
public: public:
String mqttPath; String mqttPath;
@ -28,9 +21,9 @@ class LightMqttSettings {
} }
static void deserialize(JsonObject& root, LightMqttSettings& settings) { static void deserialize(JsonObject& root, LightMqttSettings& settings) {
settings.mqttPath = root["mqtt_path"] | defaultDeviceValue("homeassistant/light/"); settings.mqttPath = root["mqtt_path"] | ESPUtils::defaultDeviceValue("homeassistant/light/");
settings.name = root["name"] | defaultDeviceValue("light-"); settings.name = root["name"] | ESPUtils::defaultDeviceValue("light-");
settings.uniqueId = root["unique_id"] | defaultDeviceValue("light-"); settings.uniqueId = root["unique_id"] | ESPUtils::defaultDeviceValue("light-");
} }
}; };